diff --git a/Core/Spawn/SPA-110 - Limit Spawning/MOOSE_Test_SPAWN_InitLimit.lua b/Core/Spawn/SPA-110 - Limit Spawning/MOOSE_Test_SPAWN_InitLimit.lua index f9979639ca..44464e2cb7 100644 --- a/Core/Spawn/SPA-110 - Limit Spawning/MOOSE_Test_SPAWN_InitLimit.lua +++ b/Core/Spawn/SPA-110 - Limit Spawning/MOOSE_Test_SPAWN_InitLimit.lua @@ -1,4 +1,4 @@ -env.info('*** MOOSE GITHUB Commit Hash ID: 2025-09-15T09:28:39+02:00-74383e31111c5382ecebfe836062fc15329f6c5d ***') +env.info('*** MOOSE GITHUB Commit Hash ID: 2025-12-31T13:17:27+01:00-e29f545c1e77d741082f308a10b57e056b044e97 ***') if not MOOSE_DEVELOPMENT_FOLDER then MOOSE_DEVELOPMENT_FOLDER='Scripts' end @@ -403,7 +403,8 @@ Tornado="Tornado", Atlas="A400", Lancer="B1-B", Stratofortress="B-52H", -Hercules="C-130", +Herc="C-130", +Hercules="C-130J-30", Super_Hercules="Hercules", Globemaster="C-17", Greyhound="C-2A", @@ -416,6 +417,7 @@ Extender="KC-10", Orion="P-3C", Viking="S-3B", Osprey="V-22", +Intruder="A6E", Badger="H6-J", Bear_J="Tu-142", Bear="Tu-95", @@ -461,6 +463,8 @@ missiles={}, bombs={}, nurs={}, containers={}, +shells={}, +gunmounts={}, droptanks={}, adapters={}, torpedoes={}, @@ -469,728 +473,1207 @@ CH47={}, OH58={}, UH1H={}, AH64D={}, +UH60L={}, } } -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" -ENUMS.Storage.weapons.containers.LANTIRN="weapons.containers.LANTIRN" -ENUMS.Storage.weapons.missiles.AGM_78B="weapons.missiles.AGM_78B" -ENUMS.Storage.weapons.containers.uh_60l_pilot="weapons.containers.uh-60l_pilot" -ENUMS.Storage.weapons.missiles.AIM_92E="weapons.missiles.AIM-92E" -ENUMS.Storage.weapons.missiles.KD_63B="weapons.missiles.KD_63B" -ENUMS.Storage.weapons.bombs.Type_200A="weapons.bombs.Type_200A" -ENUMS.Storage.weapons.missiles.HB_AIM_7E_2="weapons.missiles.HB-AIM-7E-2" -ENUMS.Storage.weapons.containers.Spear="weapons.containers.Spear" -ENUMS.Storage.weapons.missiles.LS_6="weapons.missiles.LS_6" -ENUMS.Storage.weapons.containers.HB_ALE_40_0_120="weapons.containers.HB_ALE_40_0_120" -ENUMS.Storage.weapons.containers.Fantasm="weapons.containers.Fantasm" -ENUMS.Storage.weapons.nurs.FFAR_Mk61="weapons.nurs.FFAR_Mk61" -ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1="weapons.bombs.HB_F4E_GBU15V1" -ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ_167="weapons.containers.HB_F14_EXT_AN_APQ-167" -ENUMS.Storage.weapons.nurs.LWL_RP="weapons.nurs.LWL_RP" -ENUMS.Storage.weapons.bombs.AGM_62_I="weapons.bombs.AGM_62_I" -ENUMS.Storage.weapons.containers.ETHER="weapons.containers.ETHER" -ENUMS.Storage.weapons.containers.TANGAZH="weapons.containers.TANGAZH" -ENUMS.Storage.weapons.bombs.LYSBOMB_11086="weapons.bombs.LYSBOMB 11086" -ENUMS.Storage.weapons.containers.Stub_Wing="weapons.containers.Stub_Wing" -ENUMS.Storage.weapons.missiles.AIM_9E="weapons.missiles.AIM-9E" -ENUMS.Storage.weapons.missiles.C_701T="weapons.missiles.C_701T" -ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP_100" -ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG" -ENUMS.Storage.weapons.missiles.CM_400AKG="weapons.missiles.CM-400AKG" -ENUMS.Storage.weapons.missiles.C_802AK="weapons.missiles.C_802AK" -ENUMS.Storage.weapons.missiles.KD_63="weapons.missiles.KD_63" -ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast="weapons.containers.HB_ORD_Pave_Spike_Fast" -ENUMS.Storage.weapons.missiles.SPIKE_ER2="weapons.missiles.SPIKE_ER2" -ENUMS.Storage.weapons.containers.KINGAL="weapons.containers.KINGAL" -ENUMS.Storage.weapons.containers.LANTIRN_F14_TARGET="weapons.containers.LANTIRN-F14-TARGET" -ENUMS.Storage.weapons.containers.SPS_141="weapons.containers.SPS-141" -ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU-3B_GROUP" -ENUMS.Storage.weapons.containers.HB_ALE_40_30_0="weapons.containers.HB_ALE_40_30_0" -ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL="weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL" -ENUMS.Storage.weapons.containers.ALQ_184="weapons.containers.ALQ-184" -ENUMS.Storage.weapons.missiles.AGM_45B="weapons.missiles.AGM_45B" -ENUMS.Storage.weapons.bombs.BLU_3_GROUP="weapons.bombs.BLU-3_GROUP" -ENUMS.Storage.weapons.missiles.SPIKE_ER="weapons.missiles.SPIKE_ER" -ENUMS.Storage.weapons.nurs.ARAKM70BAPPX="weapons.nurs.ARAKM70BAPPX" -ENUMS.Storage.weapons.bombs.LYSBOMB_11088="weapons.bombs.LYSBOMB 11088" -ENUMS.Storage.weapons.bombs.LYSBOMB_11087="weapons.bombs.LYSBOMB 11087" -ENUMS.Storage.weapons.missiles.KD_20="weapons.missiles.KD_20" -ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank="weapons.droptanks.HB_F-4E_EXT_WingTank" -ENUMS.Storage.weapons.missiles.Rb_04="weapons.missiles.Rb_04" -ENUMS.Storage.weapons.containers.AAQ_33="weapons.containers.AAQ-33" -ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank_EMPTY="weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY" -ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R_EMPTY="weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY" -ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_EMPTY="weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY" -ENUMS.Storage.weapons.containers.uh_60l_copilot="weapons.containers.uh-60l_copilot" -ENUMS.Storage.weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2="weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2" -ENUMS.Storage.weapons.containers.supply_m134="weapons.containers.supply_m134" -ENUMS.Storage.weapons.containers.Seahawk_Pylon="weapons.containers.Seahawk_Pylon" -ENUMS.Storage.weapons.nurs.LWL_MPP="weapons.nurs.LWL_MPP" -ENUMS.Storage.weapons.nurs.S_5KP="weapons.nurs.S_5KP" -ENUMS.Storage.weapons.missiles.AIM_92J="weapons.missiles.AIM-92J" -ENUMS.Storage.weapons.missiles.HB_AIM_7E="weapons.missiles.HB-AIM-7E" -ENUMS.Storage.weapons.containers.ALQ_131="weapons.containers.ALQ-131" -ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS="weapons.containers.HB_F14_EXT_TARPS" -ENUMS.Storage.weapons.containers.MH60_SOAR="weapons.containers.MH60_SOAR" -ENUMS.Storage.weapons.missiles.YJ_83="weapons.missiles.YJ-83" -ENUMS.Storage.weapons.bombs.GBU_8_B="weapons.bombs.GBU_8_B" -ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA="weapons.containers.HB_F14_EXT_ECA" -ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100" -ENUMS.Storage.weapons.nurs.M261_MPSM_Rocket="weapons.nurs.M261_MPSM_Rocket" -ENUMS.Storage.weapons.droptanks.SEAHAWK_120_Fuel_Tank="weapons.droptanks.SEAHAWK_120_Fuel_Tank" -ENUMS.Storage.weapons.containers.SHPIL="weapons.containers.SHPIL" -ENUMS.Storage.weapons.bombs.GBU_39="weapons.bombs.GBU_39" -ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S_5M" -ENUMS.Storage.weapons.containers.HB_ALE_40_15_90="weapons.containers.HB_ALE_40_15_90" -ENUMS.Storage.weapons.missiles.AIM_7E="weapons.missiles.AIM-7E" -ENUMS.Storage.weapons.missiles.AIM_9P3="weapons.missiles.AIM-9P3" -ENUMS.Storage.weapons.missiles.AGM_12B="weapons.missiles.AGM_12B" -ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG" -ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank="weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank" -ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank="weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank" -ENUMS.Storage.weapons.containers.PAVETACK="weapons.containers.PAVETACK" -ENUMS.Storage.weapons.missiles.LS_6_500="weapons.missiles.LS_6_500" -ENUMS.Storage.weapons.bombs.LYSBOMB_11089="weapons.bombs.LYSBOMB 11089" -ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU-4B_GROUP" -ENUMS.Storage.weapons.containers.ah_64d_radar="weapons.containers.ah-64d_radar" -ENUMS.Storage.weapons.containers.F_18_LDT_POD="weapons.containers.F-18-LDT-POD" -ENUMS.Storage.weapons.containers.HB_ALE_40_30_60="weapons.containers.HB_ALE_40_30_60" -ENUMS.Storage.weapons.bombs.LS_6_100="weapons.bombs.LS_6_100" -ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R="weapons.droptanks.HB_F-4E_EXT_WingTank_R" -ENUMS.Storage.weapons.containers.SORBCIJA_R="weapons.containers.SORBCIJA_R" -ENUMS.Storage.weapons.missiles.CATM_65K="weapons.missiles.CATM_65K" -ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike="weapons.containers.HB_ORD_Pave_Spike" -ENUMS.Storage.weapons.containers.RobbieTank1="weapons.containers.RobbieTank1" -ENUMS.Storage.weapons.containers.SKY_SHADOW="weapons.containers.SKY_SHADOW" -ENUMS.Storage.weapons.containers.SORBCIJA_L="weapons.containers.SORBCIJA_L" -ENUMS.Storage.weapons.containers.Pavehawk="weapons.containers.Pavehawk" -ENUMS.Storage.weapons.bombs.BLG66_EG="weapons.bombs.BLG66_EG" -ENUMS.Storage.weapons.missiles.AGM_12C_ED="weapons.missiles.AGM_12C_ED" -ENUMS.Storage.weapons.missiles.AIM_92C="weapons.missiles.AIM-92C" -ENUMS.Storage.weapons.containers.MPS_410="weapons.containers.MPS-410" -ENUMS.Storage.weapons.missiles.HJ_12="weapons.missiles.HJ-12" -ENUMS.Storage.weapons.containers.AAQ_28_LITENING="weapons.containers.AAQ-28_LITENING" -ENUMS.Storage.weapons.containers.F_18_FLIR_POD="weapons.containers.F-18-FLIR-POD" -ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP" -ENUMS.Storage.weapons.containers.UH60L_Jayhawk="weapons.containers.UH60L_Jayhawk" -ENUMS.Storage.weapons.containers.BOZ_100="weapons.containers.BOZ-100" -ENUMS.Storage.weapons.missiles.AGM_78A="weapons.missiles.AGM_78A" -ENUMS.Storage.weapons.missiles.LAU_61_APKWS_M282="weapons.missiles.LAU_61_APKWS_M282" -ENUMS.Storage.weapons.bombs.BAP_100="weapons.bombs.BAP-100" -ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM-802AKG" -ENUMS.Storage.weapons.bombs.BLU_3B_GROUP="weapons.bombs.BLU_3B_GROUP" -ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU-4B_GROUP" -ENUMS.Storage.weapons.nurs.S_5M="weapons.nurs.S_5M" -ENUMS.Storage.weapons.missiles.AGM_12A="weapons.missiles.AGM_12A" -ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank="weapons.droptanks.JAYHAWK_120_Fuel_Tank" -ENUMS.Storage.weapons.bombs.GBU_15_V_1_B="weapons.bombs.GBU_15_V_1_B" -ENUMS.Storage.weapons.missiles.HYDRA_70_M151_APKWS={4,4,8,292} -ENUMS.Storage.weapons.missiles.HYDRA_70_M282_APKWS={4,4,8,293} -ENUMS.Storage.weapons.bombs.BAP100="weapons.bombs.BAP_100" -ENUMS.Storage.weapons.bombs.BLU3B_GROUP="weapons.bombs.BLU-3B_GROUP" -ENUMS.Storage.weapons.missiles.CM_802AKG="weapons.missiles.CM_802AKG" -ENUMS.Storage.weapons.bombs.BLU_4B_GROUP="weapons.bombs.BLU_4B_GROUP" -ENUMS.Storage.weapons.nurs.S5M="weapons.nurs.S-5M" -ENUMS.Storage.weapons.Gazelle.HMP400_100RDS={4,15,46,1771} -ENUMS.Storage.weapons.Gazelle.HMP400_200RDS={4,15,46,1770} -ENUMS.Storage.weapons.Gazelle.HMP400_400RDS={4,15,46,1769} -ENUMS.Storage.weapons.Gazelle.GIAT_M261_AP={4,15,46,1768} -ENUMS.Storage.weapons.Gazelle.GIAT_M261_SAPHEI={4,15,46,1767} -ENUMS.Storage.weapons.Gazelle.GIAT_M261_HE={4,15,46,1766} -ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP={4,15,46,1765} -ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE={4,15,46,1764} -ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR={4,15,47,680} -ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER={4,15,47,679} -ENUMS.Storage.weapons.CH47.CH47_PORT_M60D={4,15,46,2489} -ENUMS.Storage.weapons.CH47.CH47_STBD_M60D={4,15,46,2488} -ENUMS.Storage.weapons.CH47.CH47_AFT_M60D={4,15,46,2490} -ENUMS.Storage.weapons.CH47.CH47_PORT_M134D={4,15,46,2494} -ENUMS.Storage.weapons.CH47.CH47_STBD_M134D={4,15,46,2495} -ENUMS.Storage.weapons.CH47.CH47_AFT_M3M={4,15,46,2496} -ENUMS.Storage.weapons.CH47.CH47_PORT_M240H={4,15,46,2492} -ENUMS.Storage.weapons.CH47.CH47_STBD_M240H={4,15,46,2491} -ENUMS.Storage.weapons.CH47.CH47_AFT_M240H={4,15,46,2493} -ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right={4,15,46,161} -ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left={4,15,46,160} -ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right_Door={4,15,46,175} -ENUMS.Storage.weapons.UH1H.M60_MG_Right_Door={4,15,46,177} -ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left_Door={4,15,46,174} -ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door={4,15,46,176} -ENUMS.Storage.weapons.OH58.FIM92={4,4,7,449} -ENUMS.Storage.weapons.OH58.MG_M3P100={4,15,46,2611} -ENUMS.Storage.weapons.OH58.MG_M3P200={4,15,46,2610} -ENUMS.Storage.weapons.OH58.MG_M3P300={4,15,46,2609} -ENUMS.Storage.weapons.OH58.MG_M3P400={4,15,46,2608} -ENUMS.Storage.weapons.OH58.MG_M3P500={4,15,46,2607} -ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue={4,5,9,488} -ENUMS.Storage.weapons.OH58.Smk_Grenade_Green={4,5,9,489} -ENUMS.Storage.weapons.OH58.Smk_Grenade_Red={4,5,9,487} -ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet={4,5,9,490} -ENUMS.Storage.weapons.OH58.Smk_Grenade_White={4,5,9,492} -ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow={4,5,9,491} -ENUMS.Storage.weapons.AH64D.AN_APG78={4,15,44,2114} -ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank={1,3,43,1700} -ENUMS.Storage.weapons.droptanks.FuelTank_610gal={1,3,43,10} -ENUMS.Storage.weapons.droptanks.FuelTank_370gal={1,3,43,11} -ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_AP_M79={4,15,46,824} -ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_HE_M792={4,15,46,825} -ENUMS.Storage.weapons.containers.AV8BNA_GAU_12_SAPHEI_T={4,15,46,300} +ENUMS.Storage.weapons.containers.APK9='weapons.containers.APK-9' +ENUMS.Storage.weapons.shells.KDA_35_FAPDS='weapons.shells.KDA_35_FAPDS' +ENUMS.Storage.weapons.shells.BR_354N='weapons.shells.BR_354N' +ENUMS.Storage.weapons.droptanks.HB_F4E_EXT_WingTank_EMPTY='weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY' +ENUMS.Storage.weapons.nurs.HYDRA_70_M151_M433='weapons.nurs.HYDRA_70_M151_M433' +ENUMS.Storage.weapons.shells.Rh202_20_HE='weapons.shells.Rh202_20_HE' +ENUMS.Storage.weapons.bombs.GBU_38='weapons.bombs.GBU_38' +ENUMS.Storage.weapons.containers._16c_hts_pod='weapons.containers.16c_hts_pod' +ENUMS.Storage.weapons.missiles.AGM_65G='weapons.missiles.AGM_65G' +ENUMS.Storage.weapons.gunmounts.NR30='weapons.gunmounts.NR-30' +ENUMS.Storage.weapons.gunmounts.MB339_ANM3_L='weapons.gunmounts.{MB339_ANM3_L}' +ENUMS.Storage.weapons.adapters.UB13='weapons.adapters.UB-13' +ENUMS.Storage.weapons.shells.N37_37x155_HEI_T='weapons.shells.N37_37x155_HEI_T' +ENUMS.Storage.weapons.bombs.AN_M30A1='weapons.bombs.AN_M30A1' +ENUMS.Storage.weapons.adapters.APU601='weapons.adapters.APU-60-1' +ENUMS.Storage.weapons.adapters.M2000C_AUF2='weapons.adapters.M-2000C_AUF2' +ENUMS.Storage.weapons.shells.MG_20x82_API='weapons.shells.MG_20x82_API' +ENUMS.Storage.weapons.adapters.Carrier_N1_EM_EF='weapons.adapters.Carrier_N-1_EM_EF' +ENUMS.Storage.weapons.gunmounts.OH58D_M3P_L500='weapons.gunmounts.OH58D_M3P_L500' +ENUMS.Storage.weapons.adapters.RB15pylon='weapons.adapters.RB15pylon' +ENUMS.Storage.weapons.bombs.AGM_62='weapons.bombs.AGM_62' +ENUMS.Storage.weapons.shells.GSH23_23_AP='weapons.shells.GSH23_23_AP' +ENUMS.Storage.weapons.shells.Mauser7_92x57_S_m_K_Ub_m_Zerl='weapons.shells.Mauser7.92x57_S.m.K._Ub.m.Zerl.' +ENUMS.Storage.weapons.shells.M2_12_7='weapons.shells.M2_12_7' +ENUMS.Storage.weapons.shells.M230_HEDPM789='weapons.shells.M230_HEDP M789' +ENUMS.Storage.weapons.adapters.UB_32A='weapons.adapters.UB_32A' +ENUMS.Storage.weapons.shells.L23A1_APFSDS='weapons.shells.L23A1_APFSDS' +ENUMS.Storage.weapons.shells.DEFA553_30HE='weapons.shells.DEFA553_30HE' +ENUMS.Storage.weapons.bombs.LYSBOMB11087='weapons.bombs.LYSBOMB 11087' +ENUMS.Storage.weapons.shells.KS19_100HE='weapons.shells.KS19_100HE' +ENUMS.Storage.weapons.droptanks.M2KC_RPL_522_EMPTY='weapons.droptanks.M2KC_RPL_522_EMPTY' +ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541='weapons.droptanks.M2KC_08_RPL541' +ENUMS.Storage.weapons.shells._50Browning_API_M8_Corsair='weapons.shells.50Browning_API_M8_Corsair' +ENUMS.Storage.weapons.adapters.OH58D_M260='weapons.adapters.OH58D_M260' +ENUMS.Storage.weapons.missiles.Rb05A='weapons.missiles.Rb 05A' +ENUMS.Storage.weapons.adapters.HB_F14_EXT_SHOULDER_PHX_L='weapons.adapters.HB_F14_EXT_SHOULDER_PHX_L' +ENUMS.Storage.weapons.shells.MG_13x64_HEI_T='weapons.shells.MG_13x64_HEI_T' +ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541_EMPTY='weapons.droptanks.M2KC_08_RPL541_EMPTY' +ENUMS.Storage.weapons.missiles.HQ16='weapons.missiles.HQ-16' +ENUMS.Storage.weapons.nurs.SMERCH_9M55F='weapons.nurs.SMERCH_9M55F' +ENUMS.Storage.weapons.nurs.M26='weapons.nurs.M26' +ENUMS.Storage.weapons.shells._2A38_30_AP='weapons.shells.2A38_30_AP' +ENUMS.Storage.weapons.missiles.LS_6='weapons.missiles.LS_6' +ENUMS.Storage.weapons.containers.EclairM_60='weapons.containers.{EclairM_60}' +ENUMS.Storage.weapons.bombs.FAB_100='weapons.bombs.FAB_100' +ENUMS.Storage.weapons.missiles.MALUTKA='weapons.missiles.MALUTKA' +ENUMS.Storage.weapons.containers.HB_ALE_40_0_120='weapons.containers.HB_ALE_40_0_120' +ENUMS.Storage.weapons.bombs.M485_FLARE='weapons.bombs.M485_FLARE' +ENUMS.Storage.weapons.nurs.AGR_20_M282_unguided='weapons.nurs.AGR_20_M282_unguided' +ENUMS.Storage.weapons.droptanks.F15E_Drop_Tank='weapons.droptanks.F-15E_Drop_Tank' +ENUMS.Storage.weapons.shells._20mm_M70LD_SAPHEI='weapons.shells.20mm_M70LD_SAPHEI' +ENUMS.Storage.weapons.adapters.CHAP_Mi28N_ataka='weapons.adapters.CHAP_Mi28N_ataka' +ENUMS.Storage.weapons.bombs.GBU_30='weapons.bombs.GBU_30' +ENUMS.Storage.weapons.bombs.AGM_62_I='weapons.bombs.AGM_62_I' +ENUMS.Storage.weapons.bombs.BETAB500S='weapons.bombs.BETAB-500S' +ENUMS.Storage.weapons.shells.ZTZ_125_HE='weapons.shells.ZTZ_125_HE' +ENUMS.Storage.weapons.shells.HP30_30_AP='weapons.shells.HP30_30_AP' +ENUMS.Storage.weapons.missiles.SM_6='weapons.missiles.SM_6' +ENUMS.Storage.weapons.gunmounts.MINIGUN='weapons.gunmounts.MINIGUN' +ENUMS.Storage.weapons.bombs.CBU_87='weapons.bombs.CBU_87' +ENUMS.Storage.weapons.adapters.B8V20A='weapons.adapters.B-8V20A' +ENUMS.Storage.weapons.containers.AN_ASQ_228='weapons.containers.AN_ASQ_228' +ENUMS.Storage.weapons.missiles.Sea_Dart='weapons.missiles.Sea_Dart' +ENUMS.Storage.weapons.adapters.apu13mt='weapons.adapters.apu-13mt' +ENUMS.Storage.weapons.adapters.HB_ORD_Missile_Well_Adapter='weapons.adapters.HB_ORD_Missile_Well_Adapter' +ENUMS.Storage.weapons.shells.AK176_76='weapons.shells.AK176_76' +ENUMS.Storage.weapons.missiles.X_29T='weapons.missiles.X_29T' +ENUMS.Storage.weapons.nurs.HYDRA_70_MK61='weapons.nurs.HYDRA_70_MK61' +ENUMS.Storage.weapons.shells.M393A3_105_HE='weapons.shells.M393A3_105_HE' +ENUMS.Storage.weapons.bombs.AN_M57='weapons.bombs.AN_M57' +ENUMS.Storage.weapons.missiles.AIM_7='weapons.missiles.AIM_7' +ENUMS.Storage.weapons.gunmounts.GIAT_M621_SAPHEI='weapons.gunmounts.{GIAT_M621_SAPHEI}' +ENUMS.Storage.weapons.containers.MATRAPHIMAT='weapons.containers.MATRA-PHIMAT' +ENUMS.Storage.weapons.shells.M61_20_AP='weapons.shells.M61_20_AP' +ENUMS.Storage.weapons.droptanks.droptank_150_gal='weapons.droptanks.droptank_150_gal' +ENUMS.Storage.weapons.missiles.SA48H6E2='weapons.missiles.SA48H6E2' +ENUMS.Storage.weapons.nurs.HVARUSNMk28Mod4='weapons.nurs.HVAR USN Mk28 Mod4' +ENUMS.Storage.weapons.adapters.KMGU2='weapons.adapters.KMGU-2' +ENUMS.Storage.weapons.missiles.C_701T='weapons.missiles.C_701T' +ENUMS.Storage.weapons.shells.DM53_120_AP='weapons.shells.DM53_120_AP' +ENUMS.Storage.weapons.adapters._9M114PYLON_EMPTY='weapons.adapters.9M114-PYLON_EMPTY' +ENUMS.Storage.weapons.missiles.P_500='weapons.missiles.P_500' +ENUMS.Storage.weapons.bombs.S_8OM_FLARE='weapons.bombs.S_8OM_FLARE' +ENUMS.Storage.weapons.adapters.LAU115C2_LAU127='weapons.adapters.LAU-115C+2_LAU127' +ENUMS.Storage.weapons.shells.M256_120_HE='weapons.shells.M256_120_HE' +ENUMS.Storage.weapons.shells._7_62x51tr='weapons.shells.7_62x51tr' +ENUMS.Storage.weapons.adapters.adapter_gdj_kd63='weapons.adapters.adapter_gdj_kd63' +ENUMS.Storage.weapons.missiles.CM802AKG='weapons.missiles.CM-802AKG' +ENUMS.Storage.weapons.missiles.C_802AK='weapons.missiles.C_802AK' +ENUMS.Storage.weapons.bombs.GBU_39='weapons.bombs.GBU_39' +ENUMS.Storage.weapons.bombs.BETAB500M='weapons.bombs.BETAB-500M' +ENUMS.Storage.weapons.adapters.LAU117='weapons.adapters.LAU-117' +ENUMS.Storage.weapons.missiles.BK90_MJ1='weapons.missiles.BK90_MJ1' +ENUMS.Storage.weapons.missiles.R60='weapons.missiles.R-60' +ENUMS.Storage.weapons.shells.PJ26_76_PFHE='weapons.shells.PJ26_76_PFHE' +ENUMS.Storage.weapons.nurs.AGR_20_M151_unguided='weapons.nurs.AGR_20_M151_unguided' +ENUMS.Storage.weapons.shells.HEDPM430='weapons.shells.HEDPM430' +ENUMS.Storage.weapons.shells.GSH_23_HE='weapons.shells.GSH_23_HE' +ENUMS.Storage.weapons.gunmounts.CC420_GUN_POD='weapons.gunmounts.{CC420_GUN_POD}' +ENUMS.Storage.weapons.shells.Hispano_Mk_II_SAPI='weapons.shells.Hispano_Mk_II_SAP/I' +ENUMS.Storage.weapons.adapters.Spitfire_pilon2L='weapons.adapters.Spitfire_pilon2L' +ENUMS.Storage.weapons.bombs.RBK_500SOAB='weapons.bombs.RBK_500SOAB' +ENUMS.Storage.weapons.bombs.M_117='weapons.bombs.M_117' +ENUMS.Storage.weapons.missiles.SPIKE_ER2='weapons.missiles.SPIKE_ER2' +ENUMS.Storage.weapons.bombs.BDU_45LGB='weapons.bombs.BDU_45LGB' +ENUMS.Storage.weapons.missiles.AGM_65H='weapons.missiles.AGM_65H' +ENUMS.Storage.weapons.adapters.adapter_df4b='weapons.adapters.adapter_df4b' +ENUMS.Storage.weapons.nurs.SNEB_TYPE252_F1B='weapons.nurs.SNEB_TYPE252_F1B' +ENUMS.Storage.weapons.droptanks._800LTank='weapons.droptanks.800L Tank' +ENUMS.Storage.weapons.missiles.X_31A='weapons.missiles.X_31A' +ENUMS.Storage.weapons.containers.LANTIRNF14TARGET='weapons.containers.LANTIRN-F14-TARGET' +ENUMS.Storage.weapons.bombs.CBU_52B='weapons.bombs.CBU_52B' +ENUMS.Storage.weapons.adapters.b52mbd_mk84='weapons.adapters.b52-mbd_mk84' +ENUMS.Storage.weapons.adapters.J11A_twinpylon_l='weapons.adapters.J-11A_twinpylon_l' +ENUMS.Storage.weapons.gunmounts.MB339_DEFA553_R='weapons.gunmounts.{MB339_DEFA553_R}' +ENUMS.Storage.weapons.containers.BARAX='weapons.containers.BARAX' +ENUMS.Storage.weapons.shells.DEFA554_30_HE='weapons.shells.DEFA554_30_HE' +ENUMS.Storage.weapons.droptanks.i16_eft='weapons.droptanks.i16_eft' +ENUMS.Storage.weapons.bombs.BLU3B_GROUP='weapons.bombs.BLU-3B_GROUP' +ENUMS.Storage.weapons.missiles.Sea_Cat='weapons.missiles.Sea_Cat' +ENUMS.Storage.weapons.adapters.aero3b='weapons.adapters.aero-3b' +ENUMS.Storage.weapons.nurs.SNEB_TYPE251_F1B='weapons.nurs.SNEB_TYPE251_F1B' +ENUMS.Storage.weapons.missiles.FIM_92C='weapons.missiles.FIM_92C' +ENUMS.Storage.weapons.missiles.SM_2ER='weapons.missiles.SM_2ER' +ENUMS.Storage.weapons.missiles.AGM_114K='weapons.missiles.AGM_114K' +ENUMS.Storage.weapons.bombs.AB_250_2_SD_10A='weapons.bombs.AB_250_2_SD_10A' +ENUMS.Storage.weapons.missiles.X_65='weapons.missiles.X_65' +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4='weapons.bombs.British_GP_500LB_Bomb_Mk4' +ENUMS.Storage.weapons.shells._50Browning_Ball_M2='weapons.shells.50Browning_Ball_M2' +ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS='weapons.containers.HB_F14_EXT_TARPS' +ENUMS.Storage.weapons.gunmounts.PKT_7_62='weapons.gunmounts.PKT_7_62' +ENUMS.Storage.weapons.shells._50Browning_I_M1='weapons.shells.50Browning_I_M1' +ENUMS.Storage.weapons.shells.British303_Ball_Mk8='weapons.shells.British303_Ball_Mk8' +ENUMS.Storage.weapons.adapters.F4PILON='weapons.adapters.F4-PILON' +ENUMS.Storage.weapons.missiles.P_77='weapons.missiles.P_77' +ENUMS.Storage.weapons.missiles.SA9M338K='weapons.missiles.SA9M338K' +ENUMS.Storage.weapons.shells.ZTZ_7_62='weapons.shells.ZTZ_7_62' +ENUMS.Storage.weapons.shells.Mauser7_92x57_B='weapons.shells.Mauser7.92x57_B.' +ENUMS.Storage.weapons.missiles.X_28='weapons.missiles.X_28' +ENUMS.Storage.weapons.missiles.KD_20='weapons.missiles.KD_20' +ENUMS.Storage.weapons.missiles.TGM_65G='weapons.missiles.TGM_65G' +ENUMS.Storage.weapons.adapters.mbd4='weapons.adapters.mbd-4' +ENUMS.Storage.weapons.shells.Mauser7_92x57_S_m_K_='weapons.shells.Mauser7.92x57_S.m.K.' +ENUMS.Storage.weapons.missiles.M39A1='weapons.missiles.M39A1' +ENUMS.Storage.weapons.adapters.m559='weapons.adapters.m559' +ENUMS.Storage.weapons.missiles.AGM_12B='weapons.missiles.AGM_12B' +ENUMS.Storage.weapons.shells.M39_20_HEI='weapons.shells.M39_20_HEI' +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4_Short='weapons.bombs.British_GP_500LB_Bomb_Mk4_Short' +ENUMS.Storage.weapons.missiles.Rb15F='weapons.missiles.Rb 15F' +ENUMS.Storage.weapons.missiles.AIM_120C='weapons.missiles.AIM_120C' +ENUMS.Storage.weapons.shells.Mauser7_92x57_SmK_Lspurweiss="weapons.shells.Mauser7.92x57_S.m.K._L'spur(weiss)" +ENUMS.Storage.weapons.nurs.S_5KP='weapons.nurs.S_5KP' +ENUMS.Storage.weapons.bombs.GBU_31_V_4B='weapons.bombs.GBU_31_V_4B' +ENUMS.Storage.weapons.missiles.HQ7B='weapons.missiles.HQ-7B' +ENUMS.Storage.weapons.bombs.ODAB500PM='weapons.bombs.ODAB-500PM' +ENUMS.Storage.weapons.bombs.BAP100='weapons.bombs.BAP-100' +ENUMS.Storage.weapons.shells.MK_108_MGsch='weapons.shells.MK_108_MGsch' +ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk1_Short='weapons.bombs.British_MC_500LB_Bomb_Mk1_Short' +ENUMS.Storage.weapons.adapters.BRU42_LS_LAU131='weapons.adapters.BRU-42_LS_(LAU-131)' +ENUMS.Storage.weapons.containers.SHPIL='weapons.containers.SHPIL' +ENUMS.Storage.weapons.torpedoes.Mark_46='weapons.torpedoes.Mark_46' +ENUMS.Storage.weapons.bombs.SAB_100MN='weapons.bombs.SAB_100MN' +ENUMS.Storage.weapons.missiles.SA3M9M='weapons.missiles.SA3M9M' +ENUMS.Storage.weapons.adapters.LAU61='weapons.adapters.LAU-61' +ENUMS.Storage.weapons.adapters.mer2='weapons.adapters.mer2' +ENUMS.Storage.weapons.shells.ship_Bofors_40mm_HE='weapons.shells.ship_Bofors_40mm_HE' +ENUMS.Storage.weapons.nurs.S24A='weapons.nurs.S-24A' +ENUMS.Storage.weapons.shells.GSh_30_2K_AP_Tr='weapons.shells.GSh_30_2K_AP_Tr' +ENUMS.Storage.weapons.missiles.AIM7F='weapons.missiles.AIM-7F' +ENUMS.Storage.weapons.shells.M383='weapons.shells.M383' +ENUMS.Storage.weapons.nurs.HYDRA_70_M257='weapons.nurs.HYDRA_70_M257' +ENUMS.Storage.weapons.droptanks.PTB_580G_F1='weapons.droptanks.PTB_580G_F1' +ENUMS.Storage.weapons.gunmounts.C101DEFA553='weapons.gunmounts.{C-101-DEFA553}' +ENUMS.Storage.weapons.missiles.MICA_R='weapons.missiles.MICA_R' +ENUMS.Storage.weapons.shells.M53_APT_RED='weapons.shells.M53_APT_RED' +ENUMS.Storage.weapons.missiles.AIM9P5='weapons.missiles.AIM-9P5' +ENUMS.Storage.weapons.adapters._306M2='weapons.adapters.30-6-M2' +ENUMS.Storage.weapons.shells._75mm_AA_JAP='weapons.shells.75mm_AA_JAP' +ENUMS.Storage.weapons.nurs.TinyTim='weapons.nurs.Tiny Tim' +ENUMS.Storage.weapons.missiles.X_22='weapons.missiles.X_22' +ENUMS.Storage.weapons.nurs.S25O='weapons.nurs.S-25-O' +ENUMS.Storage.weapons.missiles.X_101='weapons.missiles.X_101' +ENUMS.Storage.weapons.missiles.AIM_54A_Mk47='weapons.missiles.AIM_54A_Mk47' +ENUMS.Storage.weapons.containers.ECM_POD_L_175V='weapons.containers.{ECM_POD_L_175V}' +ENUMS.Storage.weapons.shells._2A28_73='weapons.shells.2A28_73' +ENUMS.Storage.weapons.shells.GAU8_30_AP='weapons.shells.GAU8_30_AP' +ENUMS.Storage.weapons.shells.British303_Ball_Mk1c='weapons.shells.British303_Ball_Mk1c' +ENUMS.Storage.weapons.missiles.AIM_9='weapons.missiles.AIM_9' +ENUMS.Storage.weapons.missiles.SD10='weapons.missiles.SD-10' +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.gunmounts.defa_553='weapons.gunmounts.defa_553' +ENUMS.Storage.weapons.nurs.BRM1_90MM_UG='weapons.nurs.BRM1_90MM_UG' +ENUMS.Storage.weapons.gunmounts.CH47_STBD_M60D='weapons.gunmounts.{CH47_STBD_M60D}' +ENUMS.Storage.weapons.adapters.LAU10='weapons.adapters.LAU-10' +ENUMS.Storage.weapons.shells.L31_120mm_HESH='weapons.shells.L31_120mm_HESH' +ENUMS.Storage.weapons.gunmounts.CH47_AFT_M60D='weapons.gunmounts.{CH47_AFT_M60D}' +ENUMS.Storage.weapons.shells._20mm_M53_API='weapons.shells.20mm_M53_API' +ENUMS.Storage.weapons.adapters.HB_F14_EXT_LAU7='weapons.adapters.HB_F14_EXT_LAU-7' +ENUMS.Storage.weapons.shells.CHAP_76_PFHE='weapons.shells.CHAP_76_PFHE' +ENUMS.Storage.weapons.bombs.KAB_500KrOD='weapons.bombs.KAB_500KrOD' +ENUMS.Storage.weapons.adapters.PU_9S846_STRELEC='weapons.adapters.PU_9S846_STRELEC' +ENUMS.Storage.weapons.containers.EclairM_51='weapons.containers.{EclairM_51}' +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike='weapons.containers.HB_ORD_Pave_Spike' +ENUMS.Storage.weapons.shells.MINGR55_NO_TRC='weapons.shells.MINGR55_NO_TRC' +ENUMS.Storage.weapons.nurs.PG_9V='weapons.nurs.PG_9V' +ENUMS.Storage.weapons.gunmounts.M61A1='weapons.gunmounts.M-61A1' +ENUMS.Storage.weapons.nurs.PG_16V='weapons.nurs.PG_16V' +ENUMS.Storage.weapons.shells.British303_G_Mk4='weapons.shells.British303_G_Mk4' +ENUMS.Storage.weapons.missiles.SA5B55='weapons.missiles.SA5B55' +ENUMS.Storage.weapons.adapters.b52_CSRL_ALCM='weapons.adapters.b-52_CSRL_ALCM' +ENUMS.Storage.weapons.adapters._9M114PILON='weapons.adapters.9M114-PILON' +ENUMS.Storage.weapons.shells._50Browning_APIT_M20_Corsair='weapons.shells.50Browning_APIT_M20_Corsair' +ENUMS.Storage.weapons.shells.PJ87_100_PFHE='weapons.shells.PJ87_100_PFHE' +ENUMS.Storage.weapons.bombs._2503='weapons.bombs.250-3' +ENUMS.Storage.weapons.shells._2A42_30_AP='weapons.shells.2A42_30_AP' +ENUMS.Storage.weapons.shells._37mm_Type_100_JAP='weapons.shells.37mm_Type_100_JAP' +ENUMS.Storage.weapons.droptanks.oiltank='weapons.droptanks.oiltank' +ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D='weapons.droptanks.AV8BNA_AERO1D' +ENUMS.Storage.weapons.containers.smoke_pod='weapons.containers.smoke_pod' +ENUMS.Storage.weapons.missiles.AGM_12A='weapons.missiles.AGM_12A' +ENUMS.Storage.weapons.missiles.MICA_T='weapons.missiles.MICA_T' +ENUMS.Storage.weapons.droptanks._1100LTankEmpty='weapons.droptanks.1100L Tank Empty' +ENUMS.Storage.weapons.adapters.CHAP_Mi28N_igla='weapons.adapters.CHAP_Mi28N_igla' +ENUMS.Storage.weapons.bombs.GBU_15_V_1_B='weapons.bombs.GBU_15_V_1_B' +ENUMS.Storage.weapons.missiles.Rb24='weapons.missiles.Rb 24' +ENUMS.Storage.weapons.missiles.RB75='weapons.missiles.RB75' +ENUMS.Storage.weapons.shells.M2_12_7_T='weapons.shells.M2_12_7_T' +ENUMS.Storage.weapons.shells._2A42_30_HE='weapons.shells.2A42_30_HE' +ENUMS.Storage.weapons.containers.HVAR_rocket='weapons.containers.HVAR_rocket' +ENUMS.Storage.weapons.gunmounts.GIAT_M621_APHE='weapons.gunmounts.{GIAT_M621_APHE}' +ENUMS.Storage.weapons.nurs.SNEB_TYPE253_F1B='weapons.nurs.SNEB_TYPE253_F1B' +ENUMS.Storage.weapons.shells.M230_HEIM799='weapons.shells.M230_HEI M799' +ENUMS.Storage.weapons.containers.HB_F14_EXT_BRU34='weapons.containers.HB_F14_EXT_BRU34' +ENUMS.Storage.weapons.shells.Sprgr_34_L48='weapons.shells.Sprgr_34_L48' +ENUMS.Storage.weapons.shells._7_62x39='weapons.shells.7_62x39' +ENUMS.Storage.weapons.containers.LANTIRN='weapons.containers.LANTIRN' +ENUMS.Storage.weapons.shells.GSH23_23_HE='weapons.shells.GSH23_23_HE' +ENUMS.Storage.weapons.bombs.KAB_1500Kr='weapons.bombs.KAB_1500Kr' +ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk1='weapons.bombs.British_MC_250LB_Bomb_Mk1' +ENUMS.Storage.weapons.gunmounts.ANM3='weapons.gunmounts.{AN-M3}' +ENUMS.Storage.weapons.droptanks.Spitfire_tank_1='weapons.droptanks.Spitfire_tank_1' +ENUMS.Storage.weapons.missiles.AGM_78B='weapons.missiles.AGM_78B' +ENUMS.Storage.weapons.adapters.hj12launchertube='weapons.adapters.hj12-launcher-tube' +ENUMS.Storage.weapons.shells.M242_25_HE_M792='weapons.shells.M242_25_HE_M792' +ENUMS.Storage.weapons.shells.M46='weapons.shells.M46' +ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY='weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY' +ENUMS.Storage.weapons.missiles.AIM_120='weapons.missiles.AIM_120' +ENUMS.Storage.weapons.missiles.Rb15FforA_I__='weapons.missiles.Rb 15F (for A.I.)' +ENUMS.Storage.weapons.gunmounts.NR23='weapons.gunmounts.NR-23' +ENUMS.Storage.weapons.missiles.Vikhr_M='weapons.missiles.Vikhr_M' +ENUMS.Storage.weapons.bombs.Mk_83='weapons.bombs.Mk_83' +ENUMS.Storage.weapons.adapters._9m114pilon='weapons.adapters.9m114-pilon' +ENUMS.Storage.weapons.bombs.RBK_500U_OAB_2_5RT='weapons.bombs.RBK_500U_OAB_2_5RT' +ENUMS.Storage.weapons.shells._50Browning_AP_M2_Corsair='weapons.shells.50Browning_AP_M2_Corsair' +ENUMS.Storage.weapons.shells.British303_G_Mk2='weapons.shells.British303_G_Mk2' +ENUMS.Storage.weapons.gunmounts.UPK_23_25='weapons.gunmounts.UPK_23_25' +ENUMS.Storage.weapons.shells.M242_25_AP_M919='weapons.shells.M242_25_AP_M919' +ENUMS.Storage.weapons.bombs.Type_200A='weapons.bombs.Type_200A' +ENUMS.Storage.weapons.containers.SORBCIJA_L='weapons.containers.SORBCIJA_L' +ENUMS.Storage.weapons.shells.M322_120_AP='weapons.shells.M322_120_AP' +ENUMS.Storage.weapons.missiles.AGR_20A='weapons.missiles.AGR_20A' +ENUMS.Storage.weapons.missiles._9M723='weapons.missiles.9M723' +ENUMS.Storage.weapons.bombs.BKF_AO2_5RT='weapons.bombs.BKF_AO2_5RT' +ENUMS.Storage.weapons.missiles.P_33E='weapons.missiles.P_33E' +ENUMS.Storage.weapons.adapters.b52mbd_m117='weapons.adapters.b52-mbd_m117' +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.adapters.OH58D_HRACK_R='weapons.adapters.OH58D_HRACK_R' +ENUMS.Storage.weapons.missiles.AGM_78A='weapons.missiles.AGM_78A' +ENUMS.Storage.weapons.bombs.FAB_100SV='weapons.bombs.FAB_100SV' +ENUMS.Storage.weapons.adapters.F4E_dual_LAU7='weapons.adapters.F4E_dual_LAU7' +ENUMS.Storage.weapons.shells.CHAP_76_HE_T='weapons.shells.CHAP_76_HE_T' +ENUMS.Storage.weapons.adapters.HB_F14_EXT_SPARROW_PYLON='weapons.adapters.HB_F14_EXT_SPARROW_PYLON' +ENUMS.Storage.weapons.missiles.P_27PE='weapons.missiles.P_27PE' +ENUMS.Storage.weapons.shells._2A38_30_HE='weapons.shells.2A38_30_HE' +ENUMS.Storage.weapons.nurs.WGr21='weapons.nurs.WGr21' +ENUMS.Storage.weapons.droptanks.HB_F4E_EXT_WingTank_R_EMPTY='weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY' +ENUMS.Storage.weapons.shells.British303_G_Mk5='weapons.shells.British303_G_Mk5' +ENUMS.Storage.weapons.gunmounts.SUU_23_POD='weapons.gunmounts.{SUU_23_POD}' +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk1='weapons.bombs.British_GP_250LB_Bomb_Mk1' +ENUMS.Storage.weapons.bombs.RBK_500U_BETAB_M='weapons.bombs.RBK_500U_BETAB_M' +ENUMS.Storage.weapons.gunmounts.SA342_M134_SIDE_R='weapons.gunmounts.{SA342_M134_SIDE_R}' +ENUMS.Storage.weapons.adapters.LAU_127='weapons.adapters.LAU_127' +ENUMS.Storage.weapons.missiles.TGM_65D='weapons.missiles.TGM_65D' +ENUMS.Storage.weapons.shells.ZTZ_125_AP='weapons.shells.ZTZ_125_AP' +ENUMS.Storage.weapons.missiles.P_73='weapons.missiles.P_73' +ENUMS.Storage.weapons.gunmounts.M134_R='weapons.gunmounts.M134_R' +ENUMS.Storage.weapons.shells.M39_20_TP='weapons.shells.M39_20_TP' +ENUMS.Storage.weapons.shells.GAU8_30_HE='weapons.shells.GAU8_30_HE' +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.gunmounts.OH_58_BRAUNING='weapons.gunmounts.OH_58_BRAUNING' +ENUMS.Storage.weapons.shells.KDA_35_AP='weapons.shells.KDA_35_AP' +ENUMS.Storage.weapons.shells.CL3143_120_AP='weapons.shells.CL3143_120_AP' +ENUMS.Storage.weapons.shells.M61_20_TP_T='weapons.shells.M61_20_TP_T' +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.containers.US_M10_SMOKE_TANK_BLUE='weapons.containers.{US_M10_SMOKE_TANK_BLUE}' +ENUMS.Storage.weapons.missiles.GAR8='weapons.missiles.GAR-8' +ENUMS.Storage.weapons.missiles.ASM_N_2='weapons.missiles.ASM_N_2' +ENUMS.Storage.weapons.bombs.BR_250='weapons.bombs.BR_250' +ENUMS.Storage.weapons.containers.F18FLIRPOD='weapons.containers.F-18-FLIR-POD' +ENUMS.Storage.weapons.containers.EclairM_42='weapons.containers.{EclairM_42}' +ENUMS.Storage.weapons.missiles.P_27P='weapons.missiles.P_27P' +ENUMS.Storage.weapons.bombs.British_SAP_500LB_Bomb_Mk5='weapons.bombs.British_SAP_500LB_Bomb_Mk5' +ENUMS.Storage.weapons.shells.NR23_23x115_API='weapons.shells.NR23_23x115_API' +ENUMS.Storage.weapons.shells.KPVT_14_5='weapons.shells.KPVT_14_5' +ENUMS.Storage.weapons.gunmounts.M60_SIDE_L='weapons.gunmounts.M60_SIDE_L' +ENUMS.Storage.weapons.nurs.S5M1_HEFRAG_FFAR='weapons.nurs.S5M1_HEFRAG_FFAR' +ENUMS.Storage.weapons.bombs.SAB_100_FLARE='weapons.bombs.SAB_100_FLARE' +ENUMS.Storage.weapons.adapters.HB_F4E_BRU42='weapons.adapters.HB_F-4E_BRU-42' +ENUMS.Storage.weapons.containers.F15E_AAQ33_XR_ATPSE='weapons.containers.F-15E_AAQ-33_XR_ATP-SE' +ENUMS.Storage.weapons.adapters.MBD267='weapons.adapters.MBD-2-67' +ENUMS.Storage.weapons.adapters.OH58D_SRACK_R='weapons.adapters.OH58D_SRACK_R' +ENUMS.Storage.weapons.adapters.UB16='weapons.adapters.UB-16' +ENUMS.Storage.weapons.nurs.HYDRA_70_WTU1B='weapons.nurs.HYDRA_70_WTU1B' +ENUMS.Storage.weapons.shells.L14A2_30_APDS='weapons.shells.L14A2_30_APDS' +ENUMS.Storage.weapons.missiles.SVIR='weapons.missiles.SVIR' +ENUMS.Storage.weapons.adapters.APU170='weapons.adapters.APU-170' +ENUMS.Storage.weapons.missiles.AIM9E='weapons.missiles.AIM-9E' +ENUMS.Storage.weapons.adapters.adapter_df4a='weapons.adapters.adapter_df4a' +ENUMS.Storage.weapons.missiles.Super_530F='weapons.missiles.Super_530F' +ENUMS.Storage.weapons.adapters.Rocket_Launcher_4_5inch='weapons.adapters.Rocket_Launcher_4_5inch' +ENUMS.Storage.weapons.adapters.JF17_PF12_twin='weapons.adapters.JF-17_PF12_twin' +ENUMS.Storage.weapons.adapters.CLB_30='weapons.adapters.CLB_30' +ENUMS.Storage.weapons.gunmounts.KORD_12_7_MI24_R='weapons.gunmounts.KORD_12_7_MI24_R' +ENUMS.Storage.weapons.gunmounts.KORD_12_7_MI24_L='weapons.gunmounts.KORD_12_7_MI24_L' +ENUMS.Storage.weapons.adapters._9M120_pylon='weapons.adapters.9M120_pylon' +ENUMS.Storage.weapons.missiles.REFLEX='weapons.missiles.REFLEX' +ENUMS.Storage.weapons.adapters.oro57k_edm='weapons.adapters.oro-57k.edm' +ENUMS.Storage.weapons.bombs.GBU_31_V_2B='weapons.bombs.GBU_31_V_2B' +ENUMS.Storage.weapons.missiles.P_40T='weapons.missiles.P_40T' +ENUMS.Storage.weapons.adapters.suu25='weapons.adapters.suu-25' +ENUMS.Storage.weapons.missiles.GB6='weapons.missiles.GB-6' +ENUMS.Storage.weapons.missiles.DWS39_MJ1='weapons.missiles.DWS39_MJ1' +ENUMS.Storage.weapons.bombs.FAB_250='weapons.bombs.FAB_250' +ENUMS.Storage.weapons.bombs.SD_500_A='weapons.bombs.SD_500_A' +ENUMS.Storage.weapons.gunmounts.M60_SIDE_R='weapons.gunmounts.M60_SIDE_R' +ENUMS.Storage.weapons.shells.L21A1_30_HE='weapons.shells.L21A1_30_HE' +ENUMS.Storage.weapons.missiles.KD_63='weapons.missiles.KD_63' +ENUMS.Storage.weapons.gunmounts.GAU_12_Equalizer_HE='weapons.gunmounts.{GAU_12_Equalizer_HE}' +ENUMS.Storage.weapons.bombs.ROCKEYE='weapons.bombs.ROCKEYE' +ENUMS.Storage.weapons.shells.GSh_30_2K_HE='weapons.shells.GSh_30_2K_HE' +ENUMS.Storage.weapons.adapters.LAU3='weapons.adapters.LAU-3' +ENUMS.Storage.weapons.shells.M39_20_API='weapons.shells.M39_20_API' +ENUMS.Storage.weapons.nurs.HVAR='weapons.nurs.HVAR' +ENUMS.Storage.weapons.adapters.F15E_LAU117='weapons.adapters.F-15E_LAU-117' +ENUMS.Storage.weapons.adapters.SA342_LAU_HOT3_2x='weapons.adapters.SA342_LAU_HOT3_2x' +ENUMS.Storage.weapons.droptanks.PTB800MIG21='weapons.droptanks.PTB-800-MIG21' +ENUMS.Storage.weapons.missiles.AGM_114='weapons.missiles.AGM_114' +ENUMS.Storage.weapons.shells._2A46M_125_AP='weapons.shells.2A46M_125_AP' +ENUMS.Storage.weapons.droptanks.MB339_TT320_L='weapons.droptanks.MB339_TT320_L' +ENUMS.Storage.weapons.shells.M61_20_HEIT_RED='weapons.shells.M61_20_HEIT_RED' +ENUMS.Storage.weapons.shells.KS19_100AP='weapons.shells.KS19_100AP' +ENUMS.Storage.weapons.containers.KINGAL='weapons.containers.KINGAL' +ENUMS.Storage.weapons.nurs.RS82='weapons.nurs.RS-82' +ENUMS.Storage.weapons.missiles.HOT2='weapons.missiles.HOT2' +ENUMS.Storage.weapons.adapters.Schloss_500XIIC='weapons.adapters.Schloss_500XIIC' +ENUMS.Storage.weapons.droptanks.fueltank450='weapons.droptanks.fueltank450' +ENUMS.Storage.weapons.missiles.X_59M='weapons.missiles.X_59M' +ENUMS.Storage.weapons.droptanks.PTB450='weapons.droptanks.PTB-450' +ENUMS.Storage.weapons.containers.SPS141='weapons.containers.SPS-141' +ENUMS.Storage.weapons.adapters.mbd3='weapons.adapters.mbd-3' +ENUMS.Storage.weapons.bombs.OFAB100120TU='weapons.bombs.OFAB-100-120TU' +ENUMS.Storage.weapons.shells._20mm_M56_HEI='weapons.shells.20mm_M56_HEI' +ENUMS.Storage.weapons.containers.HB_ALE_40_30_0='weapons.containers.HB_ALE_40_30_0' +ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL='weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL' +ENUMS.Storage.weapons.shells.M61='weapons.shells.M61' +ENUMS.Storage.weapons.missiles.PL12='weapons.missiles.PL-12' +ENUMS.Storage.weapons.missiles.R3R='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.GBU_10='weapons.bombs.GBU_10' +ENUMS.Storage.weapons.adapters.b52mbd_agm86='weapons.adapters.b52-mbd_agm86' +ENUMS.Storage.weapons.adapters.Spitfire_pilon2R='weapons.adapters.Spitfire_pilon2R' +ENUMS.Storage.weapons.adapters.apu602_R='weapons.adapters.apu-60-2_R' +ENUMS.Storage.weapons.shells._50Browning_APIT_M20='weapons.shells.50Browning_APIT_M20' +ENUMS.Storage.weapons.bombs.FAB_50='weapons.bombs.FAB_50' +ENUMS.Storage.weapons.shells._2A46M_125_HE='weapons.shells.2A46M_125_HE' +ENUMS.Storage.weapons.containers.sa342_dipole_antenna='weapons.containers.sa342_dipole_antenna' +ENUMS.Storage.weapons.shells._50Browning_T_M1='weapons.shells.50Browning_T_M1' +ENUMS.Storage.weapons.bombs.OFAB100Jupiter='weapons.bombs.OFAB-100 Jupiter' +ENUMS.Storage.weapons.adapters.MER5E='weapons.adapters.MER-5E' +ENUMS.Storage.weapons.shells.NR30_30x155_APT='weapons.shells.NR30_30x155_APT' +ENUMS.Storage.weapons.containers.ALQ184='weapons.containers.ALQ-184' +ENUMS.Storage.weapons.missiles.AGM_45B='weapons.missiles.AGM_45B' +ENUMS.Storage.weapons.containers.SKY_SHADOW='weapons.containers.SKY_SHADOW' +ENUMS.Storage.weapons.gunmounts.FN_HMP400_200='weapons.gunmounts.{FN_HMP400_200}' +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_GREEN='weapons.containers.{US_M10_SMOKE_TANK_GREEN}' +ENUMS.Storage.weapons.bombs.BLU3_GROUP='weapons.bombs.BLU-3_GROUP' +ENUMS.Storage.weapons.adapters.gdjiv1='weapons.adapters.gdj-iv1' +ENUMS.Storage.weapons.missiles.SPIKE_ER='weapons.missiles.SPIKE_ER' +ENUMS.Storage.weapons.shells.AK630_30_AP='weapons.shells.AK630_30_AP' +ENUMS.Storage.weapons.missiles.AGM_65L='weapons.missiles.AGM_65L' +ENUMS.Storage.weapons.gunmounts.MG_151_20='weapons.gunmounts.MG_151_20' +ENUMS.Storage.weapons.droptanks.PTB490MIG21='weapons.droptanks.PTB-490-MIG21' +ENUMS.Storage.weapons.shells.MG_20x82_HEI_T='weapons.shells.MG_20x82_HEI_T' +ENUMS.Storage.weapons.adapters._143M2='weapons.adapters.14-3-M2' +ENUMS.Storage.weapons.adapters.OH58D_Gorgona='weapons.adapters.OH-58D_Gorgona' +ENUMS.Storage.weapons.missiles.Rb_04='weapons.missiles.Rb_04' +ENUMS.Storage.weapons.nurs.C_8CM_RD='weapons.nurs.C_8CM_RD' +ENUMS.Storage.weapons.missiles.AKD10='weapons.missiles.AKD-10' +ENUMS.Storage.weapons.missiles.X_29L='weapons.missiles.X_29L' +ENUMS.Storage.weapons.containers.F14LANTIRNTP='weapons.containers.{F14-LANTIRN-TP}' +ENUMS.Storage.weapons.adapters.apu6='weapons.adapters.apu-6' +ENUMS.Storage.weapons.bombs.AO_2_5RT='weapons.bombs.AO_2_5RT' +ENUMS.Storage.weapons.shells.L23_120_AP='weapons.shells.L23_120_AP' +ENUMS.Storage.weapons.missiles.AIM9L='weapons.missiles.AIM-9L' +ENUMS.Storage.weapons.containers.ALQ131='weapons.containers.ALQ-131' +ENUMS.Storage.weapons.shells._25mm_AA_JAP='weapons.shells.25mm_AA_JAP' +ENUMS.Storage.weapons.nurs.C_8='weapons.nurs.C_8' +ENUMS.Storage.weapons.missiles.YJ83='weapons.missiles.YJ-83' +ENUMS.Storage.weapons.shells.MK_108_HEI='weapons.shells.MK_108_HEI' +ENUMS.Storage.weapons.droptanks.PTB400_MIG19='weapons.droptanks.PTB400_MIG19' +ENUMS.Storage.weapons.adapters.BRU42_LS='weapons.adapters.BRU-42_LS' +ENUMS.Storage.weapons.adapters.M299_AGM114='weapons.adapters.M299_AGM114' +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_ORANGE='weapons.containers.{US_M10_SMOKE_TANK_ORANGE}' +ENUMS.Storage.weapons.adapters.B1B_Conventional_Rotary_Launcher='weapons.adapters.B-1B_Conventional_Rotary_Launcher' +ENUMS.Storage.weapons.nurs.S_5M='weapons.nurs.S_5M' +ENUMS.Storage.weapons.shells.MG_13x64_I_T='weapons.shells.MG_13x64_I_T' +ENUMS.Storage.weapons.bombs.CBU_99='weapons.bombs.CBU_99' +ENUMS.Storage.weapons.bombs.LUU_2B='weapons.bombs.LUU_2B' +ENUMS.Storage.weapons.containers.aaq28LEFTlitening='weapons.containers.aaq-28LEFT litening' +ENUMS.Storage.weapons.containers.F4PILON='weapons.containers.F4-PILON' +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.adapters.LAU105='weapons.adapters.LAU-105' +ENUMS.Storage.weapons.nurs.FFARMk1HE='weapons.nurs.FFAR Mk1 HE' +ENUMS.Storage.weapons.shells.M39_20_TP_T='weapons.shells.M39_20_TP_T' +ENUMS.Storage.weapons.containers.FAS='weapons.containers.FAS' +ENUMS.Storage.weapons.missiles.X_31P='weapons.missiles.X_31P' +ENUMS.Storage.weapons.missiles.R13M='weapons.missiles.R-13M' +ENUMS.Storage.weapons.missiles.AGM_154B='weapons.missiles.AGM_154B' +ENUMS.Storage.weapons.bombs.BAT120='weapons.bombs.BAT-120' +ENUMS.Storage.weapons.shells.OF_350='weapons.shells.OF_350' +ENUMS.Storage.weapons.adapters.BRU42_LS_LAU68='weapons.adapters.BRU-42_LS_(LAU-68)' +ENUMS.Storage.weapons.shells.M134_7_62_T='weapons.shells.M134_7_62_T' +ENUMS.Storage.weapons.shells.DM33_120_AP='weapons.shells.DM33_120_AP' +ENUMS.Storage.weapons.shells.M256_120_HE_L55='weapons.shells.M256_120_HE_L55' +ENUMS.Storage.weapons.shells.Hispano_Mk_II_Mk_Z_Ball='weapons.shells.Hispano_Mk_II_Mk_Z_Ball' +ENUMS.Storage.weapons.containers.aispodt50_r='weapons.containers.ais-pod-t50_r' +ENUMS.Storage.weapons.bombs.GBU_11='weapons.bombs.GBU_11' +ENUMS.Storage.weapons.gunmounts.m3_browning='weapons.gunmounts.m3_browning' +ENUMS.Storage.weapons.containers.ah64d_radar='weapons.containers.ah-64d_radar' +ENUMS.Storage.weapons.shells.YakB_12_7='weapons.shells.YakB_12_7' +ENUMS.Storage.weapons.nurs.HYDRA_70_M151='weapons.nurs.HYDRA_70_M151' +ENUMS.Storage.weapons.droptanks.fueltank200='weapons.droptanks.fueltank200' +ENUMS.Storage.weapons.adapters.ER4_Rack='weapons.adapters.ER4_Rack' +ENUMS.Storage.weapons.containers.HB_ALE_40_30_60='weapons.containers.HB_ALE_40_30_60' +ENUMS.Storage.weapons.bombs.LS_6_100='weapons.bombs.LS_6_100' +ENUMS.Storage.weapons.containers.SORBCIJA_R='weapons.containers.SORBCIJA_R' +ENUMS.Storage.weapons.missiles.R13M1='weapons.missiles.R-13M1' +ENUMS.Storage.weapons.missiles.ALARM='weapons.missiles.ALARM' +ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC='weapons.gunmounts.{AKAN_NO_TRC}' +ENUMS.Storage.weapons.missiles.RS2US='weapons.missiles.RS2US' +ENUMS.Storage.weapons.shells.M230_30='weapons.shells.M230_30' +ENUMS.Storage.weapons.bombs.BLG66_EG='weapons.bombs.BLG66_EG' +ENUMS.Storage.weapons.bombs.FAB_500='weapons.bombs.FAB_500' +ENUMS.Storage.weapons.adapters.lau118a='weapons.adapters.lau-118a' +ENUMS.Storage.weapons.missiles.BGM_109B='weapons.missiles.BGM_109B' +ENUMS.Storage.weapons.missiles.LD10='weapons.missiles.LD-10' +ENUMS.Storage.weapons.shells._120_EXPL_F1_120mm_HE='weapons.shells.120_EXPL_F1_120mm_HE' +ENUMS.Storage.weapons.missiles.ROLAND_R='weapons.missiles.ROLAND_R' +ENUMS.Storage.weapons.droptanks.PTB300_MIG15='weapons.droptanks.PTB300_MIG15' +ENUMS.Storage.weapons.missiles.SPIKE_ERA='weapons.missiles.SPIKE_ERA' +ENUMS.Storage.weapons.adapters.b52_suu67='weapons.adapters.b-52_suu67' +ENUMS.Storage.weapons.shells.VOG17='weapons.shells.VOG17' +ENUMS.Storage.weapons.adapters.JF17_GDJII19L='weapons.adapters.JF-17_GDJ-II19L' +ENUMS.Storage.weapons.containers.F4U1D_SMOKE_WHITE='weapons.containers.{F4U1D_SMOKE_WHITE}' +ENUMS.Storage.weapons.shells.GSh_30_2K_AP='weapons.shells.GSh_30_2K_AP' +ENUMS.Storage.weapons.shells.M61_20_PGU30='weapons.shells.M61_20_PGU30' +ENUMS.Storage.weapons.nurs.HYDRA_70_M274='weapons.nurs.HYDRA_70_M274' +ENUMS.Storage.weapons.bombs.Mk_84='weapons.bombs.Mk_84' +ENUMS.Storage.weapons.bombs.BDU_50LD='weapons.bombs.BDU_50LD' +ENUMS.Storage.weapons.gunmounts.A20_TopTurret_M2_R='weapons.gunmounts.A20_TopTurret_M2_R' +ENUMS.Storage.weapons.gunmounts.MG_131='weapons.gunmounts.MG_131' +ENUMS.Storage.weapons.adapters.AUF2_RACK='weapons.adapters.AUF2_RACK' +ENUMS.Storage.weapons.missiles.Mistral='weapons.missiles.Mistral' +ENUMS.Storage.weapons.bombs.LUU_2BB='weapons.bombs.LUU_2BB' +ENUMS.Storage.weapons.adapters.JF17_GDJII19R='weapons.adapters.JF-17_GDJ-II19R' +ENUMS.Storage.weapons.shells.PGU32_SAPHEI_T='weapons.shells.PGU32_SAPHEI_T' +ENUMS.Storage.weapons.adapters.F15E_LAU88='weapons.adapters.F-15E_LAU-88' +ENUMS.Storage.weapons.missiles.AGM_154='weapons.missiles.AGM_154' +ENUMS.Storage.weapons.gunmounts.A20_TopTurret_M2_L='weapons.gunmounts.A20_TopTurret_M2_L' +ENUMS.Storage.weapons.missiles.TOW2='weapons.missiles.TOW2' +ENUMS.Storage.weapons.shells.British303_B_Mk6z='weapons.shells.British303_B_Mk6z' +ENUMS.Storage.weapons.bombs.P50T='weapons.bombs.P-50T' +ENUMS.Storage.weapons.shells._5_56x45_NOtr='weapons.shells.5_56x45_NOtr' +ENUMS.Storage.weapons.missiles.SA9M333='weapons.missiles.SA9M333' +ENUMS.Storage.weapons.nurs.HYDRA_70_M259='weapons.nurs.HYDRA_70_M259' +ENUMS.Storage.weapons.shells._50Browning_API_M8='weapons.shells.50Browning_API_M8' +ENUMS.Storage.weapons.missiles.AGM_84E='weapons.missiles.AGM_84E' +ENUMS.Storage.weapons.droptanks.FuelTank_350L='weapons.droptanks.FuelTank_350L' +ENUMS.Storage.weapons.adapters._9k121='weapons.adapters.9k121' +ENUMS.Storage.weapons.missiles.KD_63B='weapons.missiles.KD_63B' +ENUMS.Storage.weapons.droptanks.FuelTank_150L='weapons.droptanks.FuelTank_150L' +ENUMS.Storage.weapons.shells._5_45x39='weapons.shells.5_45x39' +ENUMS.Storage.weapons.missiles.AIM_54C_Mk60='weapons.missiles.AIM_54C_Mk60' +ENUMS.Storage.weapons.missiles.CATM_9M='weapons.missiles.CATM_9M' +ENUMS.Storage.weapons.droptanks.Drop_Tank_300_Liter='weapons.droptanks.Drop_Tank_300_Liter' +ENUMS.Storage.weapons.gunmounts.GUV_VOG='weapons.gunmounts.GUV_VOG' +ENUMS.Storage.weapons.bombs.Mk_83AIR='weapons.bombs.Mk_83AIR' +ENUMS.Storage.weapons.adapters.MAK79_VAR_4='weapons.adapters.MAK-79_VAR_4' +ENUMS.Storage.weapons.shells.M39_20_HEI_T='weapons.shells.M39_20_HEI_T' +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.shells.CHAP_125_3BM69_APFSDS_T='weapons.shells.CHAP_125_3BM69_APFSDS_T' +ENUMS.Storage.weapons.adapters.BRU_42A='weapons.adapters.BRU_42A' +ENUMS.Storage.weapons.missiles.TGM_65H='weapons.missiles.TGM_65H' +ENUMS.Storage.weapons.bombs.GBU_27='weapons.bombs.GBU_27' +ENUMS.Storage.weapons.adapters.APU1240='weapons.adapters.APU-12-40' +ENUMS.Storage.weapons.droptanks.F4U1D_Drop_Tank_Aux='weapons.droptanks.F4U-1D_Drop_Tank_Aux' +ENUMS.Storage.weapons.shells.DM12_L55_120mm_HEAT_MP_T='weapons.shells.DM12_L55_120mm_HEAT_MP_T' +ENUMS.Storage.weapons.shells.British303_O_Mk1='weapons.shells.British303_O_Mk1' +ENUMS.Storage.weapons.missiles.HBAIM7E2='weapons.missiles.HB-AIM-7E-2' +ENUMS.Storage.weapons.containers.Spear='weapons.containers.Spear' +ENUMS.Storage.weapons.bombs.BetAB_500='weapons.bombs.BetAB_500' +ENUMS.Storage.weapons.adapters.HB_F14_EXT_BRU34='weapons.adapters.HB_F14_EXT_BRU34' +ENUMS.Storage.weapons.missiles.Rb24J='weapons.missiles.Rb 24J' +ENUMS.Storage.weapons.shells.M256_120_AP='weapons.shells.M256_120_AP' +ENUMS.Storage.weapons.bombs.SAMP250HD='weapons.bombs.SAMP250HD' +ENUMS.Storage.weapons.containers.alq184long='weapons.containers.alq-184long' +ENUMS.Storage.weapons.shells.UOF412_100HE='weapons.shells.UOF412_100HE' +ENUMS.Storage.weapons.bombs.Mk_83CT='weapons.bombs.Mk_83CT' +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_YELLOW='weapons.nurs.SNEB_TYPE254_F1B_YELLOW' +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.gunmounts.HispanoMkII='weapons.gunmounts.HispanoMkII' +ENUMS.Storage.weapons.missiles.C_701IR='weapons.missiles.C_701IR' +ENUMS.Storage.weapons.missiles.P_9M133='weapons.missiles.P_9M133' +ENUMS.Storage.weapons.containers.HB_ALE_40_0_0='weapons.containers.HB_ALE_40_0_0' +ENUMS.Storage.weapons.missiles.KONKURS='weapons.missiles.KONKURS' +ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1='weapons.bombs.HB_F4E_GBU15V1' +ENUMS.Storage.weapons.bombs.SC_50='weapons.bombs.SC_50' +ENUMS.Storage.weapons.bombs.AN_M66='weapons.bombs.AN_M66' +ENUMS.Storage.weapons.adapters.UB32='weapons.adapters.UB-32' +ENUMS.Storage.weapons.adapters.HB_ORD_LAU88='weapons.adapters.HB_ORD_LAU-88' +ENUMS.Storage.weapons.bombs.RBK_250='weapons.bombs.RBK_250' +ENUMS.Storage.weapons.shells._6_5mm_Type_91_JAP='weapons.shells.6_5mm_Type_91_JAP' +ENUMS.Storage.weapons.gunmounts.ADEN_GUNPOD='weapons.gunmounts.{ADEN_GUNPOD}' +ENUMS.Storage.weapons.bombs.MK106='weapons.bombs.MK106' +ENUMS.Storage.weapons.bombs.RBK_250S='weapons.bombs.RBK_250S' +ENUMS.Storage.weapons.shells.M61_20_PGU28='weapons.shells.M61_20_PGU28' +ENUMS.Storage.weapons.gunmounts.OH58D_M3P='weapons.gunmounts.OH58D_M3P' +ENUMS.Storage.weapons.containers.EclairM_15='weapons.containers.{EclairM_15}' +ENUMS.Storage.weapons.containers.EclairM_33='weapons.containers.{EclairM_33}' +ENUMS.Storage.weapons.shells.NR30_30x155_APHE='weapons.shells.NR30_30x155_APHE' +ENUMS.Storage.weapons.gunmounts.GAU_12_Equalizer='weapons.gunmounts.{GAU_12_Equalizer}' +ENUMS.Storage.weapons.bombs.IAB500='weapons.bombs.IAB-500' +ENUMS.Storage.weapons.bombs.OH58D_Green_Smoke_Grenade='weapons.bombs.OH58D_Green_Smoke_Grenade' +ENUMS.Storage.weapons.adapters.ptab2_5ko_block1='weapons.adapters.ptab-2_5ko_block1' +ENUMS.Storage.weapons.shells._7_7mm_Type_97_JAP='weapons.shells.7_7mm_Type_97_JAP' +ENUMS.Storage.weapons.missiles.R_530F_IR='weapons.missiles.R_530F_IR' +ENUMS.Storage.weapons.bombs.FAB250M54='weapons.bombs.FAB-250M54' +ENUMS.Storage.weapons.missiles.RIM_116A='weapons.missiles.RIM_116A' +ENUMS.Storage.weapons.shells.PINK_PROJECTILE='weapons.shells.PINK_PROJECTILE' +ENUMS.Storage.weapons.shells.CHAP_76_HESH_T='weapons.shells.CHAP_76_HESH_T' +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.Sea_Eagle='weapons.missiles.Sea_Eagle' +ENUMS.Storage.weapons.shells.PKT_7_62='weapons.shells.PKT_7_62' +ENUMS.Storage.weapons.missiles.PL5EII='weapons.missiles.PL-5EII' +ENUMS.Storage.weapons.bombs.GBU_16='weapons.bombs.GBU_16' +ENUMS.Storage.weapons.shells.OFL_120F2_AP='weapons.shells.OFL_120F2_AP' +ENUMS.Storage.weapons.missiles.AIM_54A_Mk60='weapons.missiles.AIM_54A_Mk60' +ENUMS.Storage.weapons.bombs.CBU_97='weapons.bombs.CBU_97' +ENUMS.Storage.weapons.adapters.XM158='weapons.adapters.XM158' +ENUMS.Storage.weapons.containers.M2KC_AGF='weapons.containers.{M2KC_AGF}' +ENUMS.Storage.weapons.adapters.CBLS200='weapons.adapters.CBLS-200' +ENUMS.Storage.weapons.containers.SPRD99='weapons.containers.SPRD-99' +ENUMS.Storage.weapons.missiles.DWS39_MJ1_MJ2='weapons.missiles.DWS39_MJ1_MJ2' +ENUMS.Storage.weapons.bombs.BDU_33='weapons.bombs.BDU_33' +ENUMS.Storage.weapons.missiles.TOW='weapons.missiles.TOW' +ENUMS.Storage.weapons.gunmounts.OH58D_M3P_L400='weapons.gunmounts.OH58D_M3P_L400' +ENUMS.Storage.weapons.bombs.KAB_1500LG='weapons.bombs.KAB_1500LG' +ENUMS.Storage.weapons.shells.MK45_127mm_AP_Essex='weapons.shells.MK45_127mm_AP_Essex' +ENUMS.Storage.weapons.shells.M61_20_HE_gr='weapons.shells.M61_20_HE_gr' +ENUMS.Storage.weapons.missiles.BRM1_90MM='weapons.missiles.BRM-1_90MM' +ENUMS.Storage.weapons.missiles.Ataka_9M120F='weapons.missiles.Ataka_9M120F' +ENUMS.Storage.weapons.adapters.lau88='weapons.adapters.lau-88' +ENUMS.Storage.weapons.missiles.Sea_Wolf='weapons.missiles.Sea_Wolf' +ENUMS.Storage.weapons.shells.M61_20_PGU27='weapons.shells.M61_20_PGU27' +ENUMS.Storage.weapons.missiles.CM400AKG='weapons.missiles.CM-400AKG' +ENUMS.Storage.weapons.containers.F15E_AAQ14_LANTIRN='weapons.containers.F-15E_AAQ-14_LANTIRN' +ENUMS.Storage.weapons.containers.wmd7='weapons.containers.wmd7' +ENUMS.Storage.weapons.missiles.AIM7E2='weapons.missiles.AIM-7E-2' +ENUMS.Storage.weapons.shells.Utes_12_7x108='weapons.shells.Utes_12_7x108' +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast='weapons.containers.HB_ORD_Pave_Spike_Fast' +ENUMS.Storage.weapons.adapters.MAK79_VAR_2='weapons.adapters.MAK-79_VAR_2' +ENUMS.Storage.weapons.missiles.AGM_65D='weapons.missiles.AGM_65D' +ENUMS.Storage.weapons.missiles.AGM_86='weapons.missiles.AGM_86' +ENUMS.Storage.weapons.shells.British303_G_Mk3='weapons.shells.British303_G_Mk3' +ENUMS.Storage.weapons.shells.M61_20_AP_gr='weapons.shells.M61_20_AP_gr' +ENUMS.Storage.weapons.adapters.UB_32A_24='weapons.adapters.UB_32A_24' +ENUMS.Storage.weapons.containers.F15E_AAQ28_LITENING='weapons.containers.F-15E_AAQ-28_LITENING' +ENUMS.Storage.weapons.bombs.OH58D_Blue_Smoke_Grenade='weapons.bombs.OH58D_Blue_Smoke_Grenade' +ENUMS.Storage.weapons.bombs.KAB_500Kr='weapons.bombs.KAB_500Kr' +ENUMS.Storage.weapons.containers.SPS141100='weapons.containers.SPS-141-100' +ENUMS.Storage.weapons.missiles.AIM9JULI='weapons.missiles.AIM-9JULI' +ENUMS.Storage.weapons.droptanks.MB339_TT500_R='weapons.droptanks.MB339_TT500_R' +ENUMS.Storage.weapons.adapters.towpilon='weapons.adapters.tow-pilon' +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_YELLOW='weapons.nurs.SNEB_TYPE254_H1_YELLOW' +ENUMS.Storage.weapons.missiles.M30='weapons.missiles.M30' +ENUMS.Storage.weapons.bombs.Durandal='weapons.bombs.Durandal' +ENUMS.Storage.weapons.adapters.apu7='weapons.adapters.apu-7' +ENUMS.Storage.weapons.nurs.C_8CM_VT='weapons.nurs.C_8CM_VT' +ENUMS.Storage.weapons.containers.aispodt50='weapons.containers.ais-pod-t50' +ENUMS.Storage.weapons.shells.M485_155_IL='weapons.shells.M485_155_IL' +ENUMS.Storage.weapons.bombs.RN24='weapons.bombs.RN-24' +ENUMS.Storage.weapons.shells._2A64_152='weapons.shells.2A64_152' +ENUMS.Storage.weapons.containers._='weapons.containers.' +ENUMS.Storage.weapons.shells.M61_20_HE='weapons.shells.M61_20_HE' +ENUMS.Storage.weapons.gunmounts.CPG_M4='weapons.gunmounts.CPG_M4' +ENUMS.Storage.weapons.shells._57mm_Type_90_JAP='weapons.shells.57mm_Type_90_JAP' +ENUMS.Storage.weapons.missiles.ADM_141A='weapons.missiles.ADM_141A' +ENUMS.Storage.weapons.containers.KBpod='weapons.containers.KBpod' +ENUMS.Storage.weapons.shells.DEFA554_30_HE_TRACERS='weapons.shells.DEFA554_30_HE_TRACERS' +ENUMS.Storage.weapons.missiles.SA_IRIS_T_SL='weapons.missiles.SA_IRIS_T_SL' +ENUMS.Storage.weapons.missiles.R55='weapons.missiles.R-55' +ENUMS.Storage.weapons.adapters.BRU42_HS='weapons.adapters.BRU-42_HS' +ENUMS.Storage.weapons.shells.Hispano_Mk_II_MKIIZ_AP='weapons.shells.Hispano_Mk_II_MKIIZ_AP' +ENUMS.Storage.weapons.missiles.SA2V755='weapons.missiles.SA2V755' +ENUMS.Storage.weapons.missiles.PL8B='weapons.missiles.PL-8B' +ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_100gal='weapons.droptanks.Mosquito_Drop_Tank_100gal' +ENUMS.Storage.weapons.shells.MG_13x64_HE='weapons.shells.MG_13x64_HE' +ENUMS.Storage.weapons.shells.Hispano_Mk_II_Tracer_G='weapons.shells.Hispano_Mk_II_Tracer_G' +ENUMS.Storage.weapons.nurs.SNEB_TYPE253_H1='weapons.nurs.SNEB_TYPE253_H1' +ENUMS.Storage.weapons.nurs.ARAKM70BAPPX='weapons.nurs.ARAKM70BAPPX' +ENUMS.Storage.weapons.adapters.TER9A='weapons.adapters.TER-9A' +ENUMS.Storage.weapons.missiles._9M317='weapons.missiles.9M317' +ENUMS.Storage.weapons.adapters.LAU115C='weapons.adapters.LAU-115C' +ENUMS.Storage.weapons.gunmounts.M134_L='weapons.gunmounts.M134_L' +ENUMS.Storage.weapons.shells._20mm_M220_Tracer='weapons.shells.20mm_M220_Tracer' +ENUMS.Storage.weapons.containers.EclairM_06='weapons.containers.{EclairM_06}' +ENUMS.Storage.weapons.bombs.RBK_500AO='weapons.bombs.RBK_500AO' +ENUMS.Storage.weapons.shells.Bofors_40mm_Essex='weapons.shells.Bofors_40mm_Essex' +ENUMS.Storage.weapons.containers.MB339_Vinten='weapons.containers.MB339_Vinten' +ENUMS.Storage.weapons.nurs.ARAKM70BHE='weapons.nurs.ARAKM70BHE' +ENUMS.Storage.weapons.bombs.FAB250M62='weapons.bombs.FAB-250-M62' +ENUMS.Storage.weapons.missiles.Rb04E='weapons.missiles.Rb 04E' +ENUMS.Storage.weapons.droptanks.PTB400_MIG15='weapons.droptanks.PTB400_MIG15' +ENUMS.Storage.weapons.bombs.PTAB_2_5KO='weapons.bombs.PTAB_2_5KO' +ENUMS.Storage.weapons.adapters.M2000C_LRF4_edm='weapons.adapters.M-2000C_LRF4.edm' +ENUMS.Storage.weapons.missiles.AIM_9X='weapons.missiles.AIM_9X' +ENUMS.Storage.weapons.shells.MG_13x64_I='weapons.shells.MG_13x64_I' +ENUMS.Storage.weapons.bombs.GBU_8_B='weapons.bombs.GBU_8_B' +ENUMS.Storage.weapons.missiles.SA9M31='weapons.missiles.SA9M31' +ENUMS.Storage.weapons.containers.rightSeat='weapons.containers.rightSeat' +ENUMS.Storage.weapons.shells.Pzgr_3940='weapons.shells.Pzgr_39/40' +ENUMS.Storage.weapons.shells._2A60_120='weapons.shells.2A60_120' +ENUMS.Storage.weapons.bombs.GBU_17='weapons.bombs.GBU_17' +ENUMS.Storage.weapons.missiles.HHQ9='weapons.missiles.HHQ-9' +ENUMS.Storage.weapons.bombs.Mk_84AIR_GP='weapons.bombs.Mk_84AIR_GP' +ENUMS.Storage.weapons.bombs.RBK_250_275_AO_1SCH='weapons.bombs.RBK_250_275_AO_1SCH' +ENUMS.Storage.weapons.missiles.AIM7E='weapons.missiles.AIM-7E' +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.shells.MK_108_MGsch_T='weapons.shells.MK_108_MGsch_T' +ENUMS.Storage.weapons.missiles.GB6HE='weapons.missiles.GB-6-HE' +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_RED='weapons.nurs.SNEB_TYPE254_H1_RED' +ENUMS.Storage.weapons.shells.HP30_30_HE='weapons.shells.HP30_30_HE' +ENUMS.Storage.weapons.bombs.RN28='weapons.bombs.RN-28' +ENUMS.Storage.weapons.shells.L31A7_HESH='weapons.shells.L31A7_HESH' +ENUMS.Storage.weapons.shells.GSH301_30_AP='weapons.shells.GSH301_30_AP' +ENUMS.Storage.weapons.bombs.RBK_500U='weapons.bombs.RBK_500U' +ENUMS.Storage.weapons.droptanks.HB_F4E_EXT_Center_Fuel_Tank='weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank' +ENUMS.Storage.weapons.containers.F15E_AAQ13_LANTIRN='weapons.containers.F-15E_AAQ-13_LANTIRN' +ENUMS.Storage.weapons.droptanks._800LTankEmpty='weapons.droptanks.800L Tank Empty' +ENUMS.Storage.weapons.missiles.MIM_104='weapons.missiles.MIM_104' +ENUMS.Storage.weapons.shells.MG_13x64_API='weapons.shells.MG_13x64_API' +ENUMS.Storage.weapons.shells.M61_20_TP='weapons.shells.M61_20_TP' +ENUMS.Storage.weapons.bombs.LUU_19='weapons.bombs.LUU_19' +ENUMS.Storage.weapons.shells.M55A2_TP_RED='weapons.shells.M55A2_TP_RED' +ENUMS.Storage.weapons.adapters.su27twinpylon='weapons.adapters.su-27-twinpylon' +ENUMS.Storage.weapons.nurs.M26HE='weapons.nurs.M26HE' +ENUMS.Storage.weapons.bombs.BLU4B_GROUP='weapons.bombs.BLU-4B_GROUP' +ENUMS.Storage.weapons.nurs.HYDRA_70_M156='weapons.nurs.HYDRA_70_M156' +ENUMS.Storage.weapons.shells.DM23_105_AP='weapons.shells.DM23_105_AP' +ENUMS.Storage.weapons.missiles.SM_1='weapons.missiles.SM_1' +ENUMS.Storage.weapons.missiles.OH58D_FIM_92='weapons.missiles.OH58D_FIM_92' +ENUMS.Storage.weapons.containers.F18LDTPOD='weapons.containers.F-18-LDT-POD' +ENUMS.Storage.weapons.missiles.AGM_65K='weapons.missiles.AGM_65K' +ENUMS.Storage.weapons.shells.Bofors_40mm_HE='weapons.shells.Bofors_40mm_HE' +ENUMS.Storage.weapons.missiles.HAWK_RAKETA='weapons.missiles.HAWK_RAKETA' +ENUMS.Storage.weapons.shells._7_62x54='weapons.shells.7_62x54' +ENUMS.Storage.weapons.shells.DM12_120mm_HEAT_MP_T='weapons.shells.DM12_120mm_HEAT_MP_T' +ENUMS.Storage.weapons.bombs.AN_M64='weapons.bombs.AN_M64' +ENUMS.Storage.weapons.containers.rearCargoSeats='weapons.containers.rearCargoSeats' +ENUMS.Storage.weapons.bombs.AN_M65='weapons.bombs.AN_M65' +ENUMS.Storage.weapons.missiles.Rb74='weapons.missiles.Rb 74' +ENUMS.Storage.weapons.shells.DEFA553_30AP='weapons.shells.DEFA553_30AP' +ENUMS.Storage.weapons.nurs.S5M='weapons.nurs.S-5M' +ENUMS.Storage.weapons.gunmounts.M134_SIDE_R='weapons.gunmounts.M134_SIDE_R' +ENUMS.Storage.weapons.missiles.HJ12='weapons.missiles.HJ-12' +ENUMS.Storage.weapons.shells.PLZ_155_HE='weapons.shells.PLZ_155_HE' +ENUMS.Storage.weapons.adapters.BRU_33A='weapons.adapters.BRU_33A' +ENUMS.Storage.weapons.nurs.ARAKM70BAP='weapons.nurs.ARAKM70BAP' +ENUMS.Storage.weapons.missiles.MMagicII='weapons.missiles.MMagicII' +ENUMS.Storage.weapons.nurs.HYDRA_70_M282='weapons.nurs.HYDRA_70_M282' +ENUMS.Storage.weapons.nurs.ARF8M3HEI='weapons.nurs.ARF8M3HEI' +ENUMS.Storage.weapons.shells._76mm_AA_JAP='weapons.shells.76mm_AA_JAP' +ENUMS.Storage.weapons.missiles.Igla_1E='weapons.missiles.Igla_1E' +ENUMS.Storage.weapons.nurs.SMERCH_9M55K='weapons.nurs.SMERCH_9M55K' +ENUMS.Storage.weapons.nurs.C_24='weapons.nurs.C_24' +ENUMS.Storage.weapons.shells.GSH301_30_HE='weapons.shells.GSH301_30_HE' +ENUMS.Storage.weapons.nurs.SNEB_TYPE256_H1='weapons.nurs.SNEB_TYPE256_H1' +ENUMS.Storage.weapons.adapters.T45_PMBR='weapons.adapters.T45_PMBR' +ENUMS.Storage.weapons.containers.EclairM_24='weapons.containers.{EclairM_24}' +ENUMS.Storage.weapons.droptanks.MB339_TT500_L='weapons.droptanks.MB339_TT500_L' +ENUMS.Storage.weapons.bombs.BKF_PTAB2_5KO='weapons.bombs.BKF_PTAB2_5KO' +ENUMS.Storage.weapons.shells.Br303='weapons.shells.Br303' +ENUMS.Storage.weapons.shells.DANA_152='weapons.shells.DANA_152' +ENUMS.Storage.weapons.nurs.S5MO_HEFRAG_FFAR='weapons.nurs.S5MO_HEFRAG_FFAR' +ENUMS.Storage.weapons.missiles.AIM9P3='weapons.missiles.AIM-9P3' +ENUMS.Storage.weapons.gunmounts.GAU_12='weapons.gunmounts.GAU_12' +ENUMS.Storage.weapons.shells.MK45_127='weapons.shells.MK45_127' +ENUMS.Storage.weapons.nurs.C_8CM_GN='weapons.nurs.C_8CM_GN' +ENUMS.Storage.weapons.nurs.C_13='weapons.nurs.C_13' +ENUMS.Storage.weapons.gunmounts.OH58D_M3P_L300='weapons.gunmounts.OH58D_M3P_L300' +ENUMS.Storage.weapons.missiles.AGM_65A='weapons.missiles.AGM_65A' +ENUMS.Storage.weapons.containers.AV8BNA_ALQ164='weapons.containers.AV8BNA_ALQ164' +ENUMS.Storage.weapons.bombs.OH58D_Red_Smoke_Grenade='weapons.bombs.OH58D_Red_Smoke_Grenade' +ENUMS.Storage.weapons.bombs.FAB_1500='weapons.bombs.FAB_1500' +ENUMS.Storage.weapons.shells.M230_TPM788='weapons.shells.M230_TP M788' +ENUMS.Storage.weapons.containers.leftSeat='weapons.containers.leftSeat' +ENUMS.Storage.weapons.missiles.Kormoran='weapons.missiles.Kormoran' +ENUMS.Storage.weapons.adapters.boz100='weapons.adapters.boz-100' +ENUMS.Storage.weapons.nurs.HYDRA_70_MK1='weapons.nurs.HYDRA_70_MK1' +ENUMS.Storage.weapons.shells.Utes_12_7x108_T='weapons.shells.Utes_12_7x108_T' +ENUMS.Storage.weapons.missiles.AGM_154A='weapons.missiles.AGM_154A' +ENUMS.Storage.weapons.adapters.MBD3LAU68='weapons.adapters.MBD-3-LAU-68' +ENUMS.Storage.weapons.nurs.C_8CM_WH='weapons.nurs.C_8CM_WH' +ENUMS.Storage.weapons.missiles.MatraSuper530D='weapons.missiles.Matra Super 530D' +ENUMS.Storage.weapons.bombs.BDU_50HD='weapons.bombs.BDU_50HD' +ENUMS.Storage.weapons.adapters.M2000c_BAP_Rack='weapons.adapters.M-2000c_BAP_Rack' +ENUMS.Storage.weapons.shells._7_92x57sS='weapons.shells.7_92x57sS' +ENUMS.Storage.weapons.shells.M20_50_aero_APIT='weapons.shells.M20_50_aero_APIT' +ENUMS.Storage.weapons.bombs.KAB_500='weapons.bombs.KAB_500' +ENUMS.Storage.weapons.gunmounts.AKAN='weapons.gunmounts.{AKAN}' +ENUMS.Storage.weapons.shells._20MM_M242_HEIT='weapons.shells.20MM_M242_HEI-T' +ENUMS.Storage.weapons.gunmounts.GAU_12_Equalizer_AP='weapons.gunmounts.{GAU_12_Equalizer_AP}' +ENUMS.Storage.weapons.gunmounts.FN_HMP400='weapons.gunmounts.{FN_HMP400}' +ENUMS.Storage.weapons.containers.dlpod_akg='weapons.containers.dlpod_akg' +ENUMS.Storage.weapons.droptanks.PTB600_MIG15='weapons.droptanks.PTB600_MIG15' +ENUMS.Storage.weapons.adapters.apu602_L='weapons.adapters.apu-60-2_L' +ENUMS.Storage.weapons.missiles.SeaSparrow='weapons.missiles.SeaSparrow' +ENUMS.Storage.weapons.droptanks._='weapons.droptanks.' +ENUMS.Storage.weapons.adapters.lau117='weapons.adapters.lau-117' +ENUMS.Storage.weapons.shells.M197_20='weapons.shells.M197_20' +ENUMS.Storage.weapons.shells.Br303_tr='weapons.shells.Br303_tr' +ENUMS.Storage.weapons.adapters.MBD3LAU61='weapons.adapters.MBD-3-LAU-61' +ENUMS.Storage.weapons.bombs.British_SAP_250LB_Bomb_Mk5='weapons.bombs.British_SAP_250LB_Bomb_Mk5' +ENUMS.Storage.weapons.adapters.apu68m3='weapons.adapters.apu-68m3' +ENUMS.Storage.weapons.shells.British303_Ball_Mk6='weapons.shells.British303_Ball_Mk6' +ENUMS.Storage.weapons.shells._7_62x54_NOTRACER='weapons.shells.7_62x54_NOTRACER' +ENUMS.Storage.weapons.nurs.SNEB_TYPE250_F1B='weapons.nurs.SNEB_TYPE250_F1B' +ENUMS.Storage.weapons.shells.ZTZ_14_5='weapons.shells.ZTZ_14_5' +ENUMS.Storage.weapons.bombs.CBU_105='weapons.bombs.CBU_105' +ENUMS.Storage.weapons.droptanks.FW190_FuelTank='weapons.droptanks.FW-190_Fuel-Tank' +ENUMS.Storage.weapons.missiles.X_58='weapons.missiles.X_58' +ENUMS.Storage.weapons.bombs.LYSBOMB11089='weapons.bombs.LYSBOMB 11089' +ENUMS.Storage.weapons.containers.PAVETACK='weapons.containers.PAVETACK' +ENUMS.Storage.weapons.bombs.GBU_24='weapons.bombs.GBU_24' +ENUMS.Storage.weapons.gunmounts.FN_HMP400_100='weapons.gunmounts.{FN_HMP400_100}' +ENUMS.Storage.weapons.missiles.AIM7MH='weapons.missiles.AIM-7MH' +ENUMS.Storage.weapons.adapters.rb05pylon='weapons.adapters.rb05pylon' +ENUMS.Storage.weapons.shells.DEFA553_30APIT='weapons.shells.DEFA553_30APIT' +ENUMS.Storage.weapons.shells.Flak18_Sprgr_39='weapons.shells.Flak18_Sprgr_39' +ENUMS.Storage.weapons.missiles.X_35='weapons.missiles.X_35' +ENUMS.Storage.weapons.bombs.BL_755='weapons.bombs.BL_755' +ENUMS.Storage.weapons.containers.ETHER='weapons.containers.ETHER' +ENUMS.Storage.weapons.droptanks.F4U1D_Drop_Tank_Mk5='weapons.droptanks.F4U-1D_Drop_Tank_Mk5' +ENUMS.Storage.weapons.containers.CE2_SMOKE_WHITE='weapons.containers.{CE2_SMOKE_WHITE}' +ENUMS.Storage.weapons.bombs.Mk_82Y='weapons.bombs.Mk_82Y' +ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk2='weapons.bombs.British_MC_500LB_Bomb_Mk2' +ENUMS.Storage.weapons.adapters.HB_ORD_SUU_7='weapons.adapters.HB_ORD_SUU_7' +ENUMS.Storage.weapons.shells.MK75_76='weapons.shells.MK75_76' +ENUMS.Storage.weapons.shells.M68_105_AP='weapons.shells.M68_105_AP' +ENUMS.Storage.weapons.missiles.SA57E6='weapons.missiles.SA57E6' +ENUMS.Storage.weapons.missiles.AGM_86C='weapons.missiles.AGM_86C' +ENUMS.Storage.weapons.missiles.P_24T='weapons.missiles.P_24T' +ENUMS.Storage.weapons.adapters.OH58D_HRACK_L='weapons.adapters.OH58D_HRACK_L' +ENUMS.Storage.weapons.gunmounts.MK_108='weapons.gunmounts.MK_108' +ENUMS.Storage.weapons.adapters.APU68='weapons.adapters.APU-68' +ENUMS.Storage.weapons.shells.British303_G_Mk6z='weapons.shells.British303_G_Mk6z' +ENUMS.Storage.weapons.containers.aispodt50_l='weapons.containers.ais-pod-t50_l' +ENUMS.Storage.weapons.gunmounts.N37='weapons.gunmounts.N-37' +ENUMS.Storage.weapons.missiles.X_555='weapons.missiles.X_555' +ENUMS.Storage.weapons.bombs.FAB500M54='weapons.bombs.FAB-500M54' +ENUMS.Storage.weapons.containers.AN_AAQ_33='weapons.containers.AN_AAQ_33' +ENUMS.Storage.weapons.containers.M2KC_AAF='weapons.containers.{M2KC_AAF}' +ENUMS.Storage.weapons.shells.NR23_23x115_HEI_T='weapons.shells.NR23_23x115_HEI_T' +ENUMS.Storage.weapons.shells.KPVT_14_5_T='weapons.shells.KPVT_14_5_T' +ENUMS.Storage.weapons.shells.M56A3_HE_RED='weapons.shells.M56A3_HE_RED' +ENUMS.Storage.weapons.bombs.FAB500SL='weapons.bombs.FAB-500SL' +ENUMS.Storage.weapons.bombs.KAB_500S='weapons.bombs.KAB_500S' +ENUMS.Storage.weapons.bombs.SAMP400LD='weapons.bombs.SAMP400LD' +ENUMS.Storage.weapons.bombs.BDU_45B='weapons.bombs.BDU_45B' +ENUMS.Storage.weapons.adapters.APU73='weapons.adapters.APU-73' +ENUMS.Storage.weapons.missiles._9M723_HE='weapons.missiles.9M723_HE' +ENUMS.Storage.weapons.bombs.GBU_15_V_31_B='weapons.bombs.GBU_15_V_31_B' +ENUMS.Storage.weapons.adapters.CHAP_Tu95MS_rotary_launcher='weapons.adapters.CHAP_Tu95MS_rotary_launcher' +ENUMS.Storage.weapons.droptanks.HB_F4E_EXT_WingTank='weapons.droptanks.HB_F-4E_EXT_WingTank' +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.nurs.C_25='weapons.nurs.C_25' +ENUMS.Storage.weapons.adapters.MAK79_VAR_3='weapons.adapters.MAK-79_VAR_3' +ENUMS.Storage.weapons.adapters._9m120='weapons.adapters.9m120' +ENUMS.Storage.weapons.shells.GSh_30_2K_HE_Tr='weapons.shells.GSh_30_2K_HE_Tr' +ENUMS.Storage.weapons.adapters.hf20_pod='weapons.adapters.hf20_pod' +ENUMS.Storage.weapons.missiles.AGM_122='weapons.missiles.AGM_122' +ENUMS.Storage.weapons.missiles.P_60='weapons.missiles.P_60' +ENUMS.Storage.weapons.shells.K307_155HE='weapons.shells.K307_155HE' +ENUMS.Storage.weapons.shells.A222_130='weapons.shells.A222_130' +ENUMS.Storage.weapons.nurs.Zuni_127='weapons.nurs.Zuni_127' +ENUMS.Storage.weapons.missiles.AIM9J='weapons.missiles.AIM-9J' +ENUMS.Storage.weapons.shells.BK_27='weapons.shells.BK_27' +ENUMS.Storage.weapons.adapters.M272_AGM114='weapons.adapters.M272_AGM114' +ENUMS.Storage.weapons.shells.M242_25_AP_M791='weapons.shells.M242_25_AP_M791' +ENUMS.Storage.weapons.adapters.HB_F14_EXT_SHOULDER_PHX_R='weapons.adapters.HB_F14_EXT_SHOULDER_PHX_R' +ENUMS.Storage.weapons.gunmounts.OH58D_M3P_L100='weapons.gunmounts.OH58D_M3P_L100' +ENUMS.Storage.weapons.bombs.BetAB_500ShP='weapons.bombs.BetAB_500ShP' +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.Ataka_9M220='weapons.missiles.Ataka_9M220' +ENUMS.Storage.weapons.adapters.rb04pylon='weapons.adapters.rb04pylon' +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.adapters.M2000C_LRF4='weapons.adapters.M-2000C_LRF4' +ENUMS.Storage.weapons.shells.HESH_105='weapons.shells.HESH_105' +ENUMS.Storage.weapons.gunmounts.CH47_PORT_M240H='weapons.gunmounts.{CH47_PORT_M240H}' +ENUMS.Storage.weapons.containers.SMOKE_WHITE='weapons.containers.{SMOKE_WHITE}' +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk4='weapons.bombs.British_GP_250LB_Bomb_Mk4' +ENUMS.Storage.weapons.gunmounts.GIAT_M621_HEAP='weapons.gunmounts.{GIAT_M621_HEAP}' +ENUMS.Storage.weapons.nurs.ARF8M3TPSM='weapons.nurs.ARF8M3TPSM' +ENUMS.Storage.weapons.nurs.M8rocket='weapons.nurs.M8rocket' +ENUMS.Storage.weapons.missiles.X_25MR='weapons.missiles.X_25MR' +ENUMS.Storage.weapons.droptanks.fueltank230='weapons.droptanks.fueltank230' +ENUMS.Storage.weapons.droptanks.PTB490CMIG21='weapons.droptanks.PTB-490C-MIG21' +ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541='weapons.droptanks.M2KC_02_RPL541' +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_GREEN='weapons.nurs.SNEB_TYPE254_F1B_GREEN' +ENUMS.Storage.weapons.adapters.mbd='weapons.adapters.mbd' +ENUMS.Storage.weapons.droptanks.HB_F4E_EXT_Center_Fuel_Tank_EMPTY='weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY' +ENUMS.Storage.weapons.missiles.AGM_84D='weapons.missiles.AGM_84D' +ENUMS.Storage.weapons.bombs.M257_FLARE='weapons.bombs.M257_FLARE' +ENUMS.Storage.weapons.missiles.AGM_84A='weapons.missiles.AGM_84A' +ENUMS.Storage.weapons.gunmounts.GIAT_M621_HE='weapons.gunmounts.{GIAT_M621_HE}' +ENUMS.Storage.weapons.missiles.AIM_54C_Mk47='weapons.missiles.AIM_54C_Mk47' +ENUMS.Storage.weapons.containers.MPS410='weapons.containers.MPS-410' +ENUMS.Storage.weapons.missiles.HY2='weapons.missiles.HY-2' +ENUMS.Storage.weapons.bombs.Mk_81='weapons.bombs.Mk_81' +ENUMS.Storage.weapons.shells.Oerlikon_20mm_Essex='weapons.shells.Oerlikon_20mm_Essex' +ENUMS.Storage.weapons.adapters.PylonM71='weapons.adapters.PylonM71' +ENUMS.Storage.weapons.droptanks._1100LTank='weapons.droptanks.1100L Tank' +ENUMS.Storage.weapons.bombs.BAP_100='weapons.bombs.BAP_100' +ENUMS.Storage.weapons.gunmounts.PK3='weapons.gunmounts.{PK-3}' +ENUMS.Storage.weapons.adapters.b52_CRL_mod1='weapons.adapters.b-52_CRL_mod1' +ENUMS.Storage.weapons.adapters._9m120m='weapons.adapters.9m120m' +ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541_EMPTY='weapons.droptanks.M2KC_02_RPL541_EMPTY' +ENUMS.Storage.weapons.bombs.BR_500='weapons.bombs.BR_500' +ENUMS.Storage.weapons.adapters._9K114_Shturm='weapons.adapters.9K114_Shturm' +ENUMS.Storage.weapons.adapters.f4pilon='weapons.adapters.f4-pilon' +ENUMS.Storage.weapons.gunmounts.SPPU_22='weapons.gunmounts.SPPU_22' +ENUMS.Storage.weapons.gunmounts.GSh232taildefense='weapons.gunmounts.GSh-23-2 tail defense' +ENUMS.Storage.weapons.bombs.FAB250M54TU='weapons.bombs.FAB-250M54TU' +ENUMS.Storage.weapons.nurs.HYDRA_70_M229='weapons.nurs.HYDRA_70_M229' +ENUMS.Storage.weapons.shells.M185_155='weapons.shells.M185_155' +ENUMS.Storage.weapons.adapters.sa342_ATAM_Tube_2x='weapons.adapters.sa342_ATAM_Tube_2x' +ENUMS.Storage.weapons.shells._53UBR281U='weapons.shells.53-UBR-281U' +ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_H1='weapons.nurs.SNEB_TYPE259E_H1' +ENUMS.Storage.weapons.bombs.SAB_250_200='weapons.bombs.SAB_250_200' +ENUMS.Storage.weapons.missiles.ADM_141B='weapons.missiles.ADM_141B' +ENUMS.Storage.weapons.adapters.kmgu2='weapons.adapters.kmgu-2' +ENUMS.Storage.weapons.adapters.B20='weapons.adapters.B-20' +ENUMS.Storage.weapons.containers.U22A='weapons.containers.U22A' +ENUMS.Storage.weapons.shells.M246_20_HE_gr='weapons.shells.M246_20_HE_gr' +ENUMS.Storage.weapons.nurs.SNEB_TYPE251_H1='weapons.nurs.SNEB_TYPE251_H1' +ENUMS.Storage.weapons.missiles.Kh25MP_PRGS1VP='weapons.missiles.Kh25MP_PRGS1VP' +ENUMS.Storage.weapons.adapters.BR21Gerat='weapons.adapters.BR21-Gerat' +ENUMS.Storage.weapons.adapters.apu13u2='weapons.adapters.apu-13u-2' +ENUMS.Storage.weapons.shells.Hispano_Mk_II_MKI_HEI='weapons.shells.Hispano_Mk_II_MKI_HE/I' +ENUMS.Storage.weapons.nurs.FFAR_Mk61='weapons.nurs.FFAR_Mk61' +ENUMS.Storage.weapons.containers.BRD4250='weapons.containers.BRD-4-250' +ENUMS.Storage.weapons.containers.HB_ORD_MER='weapons.containers.HB_ORD_MER' +ENUMS.Storage.weapons.droptanks.PTB_1200_F1='weapons.droptanks.PTB_1200_F1' +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk1='weapons.bombs.British_GP_500LB_Bomb_Mk1' +ENUMS.Storage.weapons.missiles.AGM_119='weapons.missiles.AGM_119' +ENUMS.Storage.weapons.shells._3BM59_125_AP='weapons.shells.3BM59_125_AP' +ENUMS.Storage.weapons.shells.M2_50_aero_AP='weapons.shells.M2_50_aero_AP' +ENUMS.Storage.weapons.adapters.LAU68='weapons.adapters.LAU-68' +ENUMS.Storage.weapons.nurs.C_5='weapons.nurs.C_5' +ENUMS.Storage.weapons.nurs.S24B='weapons.nurs.S-24B' +ENUMS.Storage.weapons.adapters.HB_F4E_LAU34='weapons.adapters.HB_F-4E_LAU-34' +ENUMS.Storage.weapons.shells._2A18_122='weapons.shells.2A18_122' +ENUMS.Storage.weapons.missiles.SCUD_RAKETA='weapons.missiles.SCUD_RAKETA' +ENUMS.Storage.weapons.adapters.b52_HSAB='weapons.adapters.b-52_HSAB' +ENUMS.Storage.weapons.nurs.R4M='weapons.nurs.R4M' +ENUMS.Storage.weapons.bombs.LYSBOMB11086='weapons.bombs.LYSBOMB 11086' +ENUMS.Storage.weapons.bombs.SD_250_Stg='weapons.bombs.SD_250_Stg' +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.bombs.AB_500_1_SD_10A='weapons.bombs.AB_500_1_SD_10A' +ENUMS.Storage.weapons.containers.Eclair='weapons.containers.{Eclair}' +ENUMS.Storage.weapons.gunmounts.MB339_ANM3_R='weapons.gunmounts.{MB339_ANM3_R}' +ENUMS.Storage.weapons.shells.N37_37x155_API_T='weapons.shells.N37_37x155_API_T' +ENUMS.Storage.weapons.shells.MG_20x82_MGsch='weapons.shells.MG_20x82_MGsch' +ENUMS.Storage.weapons.containers.fullCargoSeats='weapons.containers.fullCargoSeats' +ENUMS.Storage.weapons.shells.British303_G_Mk1='weapons.shells.British303_G_Mk1' +ENUMS.Storage.weapons.adapters.LR25='weapons.adapters.LR-25' +ENUMS.Storage.weapons.gunmounts.CH47_PORT_M134D='weapons.gunmounts.{CH47_PORT_M134D}' +ENUMS.Storage.weapons.containers.MIG21_SMOKE_RED='weapons.containers.{MIG21_SMOKE_RED}' +ENUMS.Storage.weapons.shells.M53_AP_RED='weapons.shells.M53_AP_RED' +ENUMS.Storage.weapons.gunmounts.GIAT_M621_AP='weapons.gunmounts.{GIAT_M621_AP}' +ENUMS.Storage.weapons.nurs.C_8CM='weapons.nurs.C_8CM' +ENUMS.Storage.weapons.missiles.P_40R='weapons.missiles.P_40R' +ENUMS.Storage.weapons.missiles.YJ12='weapons.missiles.YJ-12' +ENUMS.Storage.weapons.missiles.CM_802AKG='weapons.missiles.CM_802AKG' +ENUMS.Storage.weapons.missiles.SA9M38M1='weapons.missiles.SA9M38M1' +ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D_EMPTY='weapons.droptanks.AV8BNA_AERO1D_EMPTY' +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk5='weapons.bombs.British_GP_250LB_Bomb_Mk5' +ENUMS.Storage.weapons.shells.British303_Ball_Mk7='weapons.shells.British303_Ball_Mk7' +ENUMS.Storage.weapons.bombs.GBU_43='weapons.bombs.GBU_43' +ENUMS.Storage.weapons.missiles.AGM_88='weapons.missiles.AGM_88' +ENUMS.Storage.weapons.droptanks.droptank_110_gal='weapons.droptanks.droptank_110_gal' +ENUMS.Storage.weapons.missiles.GB6SFW='weapons.missiles.GB-6-SFW' +ENUMS.Storage.weapons.bombs.SAB_250_FLARE='weapons.bombs.SAB_250_FLARE' +ENUMS.Storage.weapons.adapters.ao2_5rt_block1='weapons.adapters.ao-2_5rt_block1' +ENUMS.Storage.weapons.shells._7_62x51='weapons.shells.7_62x51' +ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC='weapons.gunmounts.AKAN_NO_TRC' +ENUMS.Storage.weapons.bombs.LYSBOMB11088='weapons.bombs.LYSBOMB 11088' +ENUMS.Storage.weapons.shells.PKT_7_62_T='weapons.shells.PKT_7_62_T' +ENUMS.Storage.weapons.gunmounts.BrowningM2='weapons.gunmounts.BrowningM2' +ENUMS.Storage.weapons.containers.MIG21_SMOKE_WHITE='weapons.containers.{MIG21_SMOKE_WHITE}' +ENUMS.Storage.weapons.adapters.BRU_57='weapons.adapters.BRU_57' +ENUMS.Storage.weapons.bombs.MK_82AIR='weapons.bombs.MK_82AIR' +ENUMS.Storage.weapons.missiles.R_550='weapons.missiles.R_550' +ENUMS.Storage.weapons.shells.Hispano_Mk_II_APT='weapons.shells.Hispano_Mk_II_AP/T' +ENUMS.Storage.weapons.adapters.MAK79_VAR_1='weapons.adapters.MAK-79_VAR_1' +ENUMS.Storage.weapons.nurs.GRAD_9M22U='weapons.nurs.GRAD_9M22U' +ENUMS.Storage.weapons.gunmounts.CH47_AFT_M240H='weapons.gunmounts.{CH47_AFT_M240H}' +ENUMS.Storage.weapons.shells.GSH_23_AP='weapons.shells.GSH_23_AP' +ENUMS.Storage.weapons.missiles.SA9M33='weapons.missiles.SA9M33' +ENUMS.Storage.weapons.missiles.YJ83K='weapons.missiles.YJ-83K' +ENUMS.Storage.weapons.shells.MG_13x64_APT='weapons.shells.MG_13x64_APT' +ENUMS.Storage.weapons.missiles.AIM7P='weapons.missiles.AIM-7P' +ENUMS.Storage.weapons.shells._50Browning_Ball_M2_Corsair='weapons.shells.50Browning_Ball_M2_Corsair' +ENUMS.Storage.weapons.nurs.C_8CM_BU='weapons.nurs.C_8CM_BU' +ENUMS.Storage.weapons.bombs.OH58D_Yellow_Smoke_Grenade='weapons.bombs.OH58D_Yellow_Smoke_Grenade' +ENUMS.Storage.weapons.bombs.GBU_32_V_2B='weapons.bombs.GBU_32_V_2B' +ENUMS.Storage.weapons.nurs.SNEB_TYPE257_F1B='weapons.nurs.SNEB_TYPE257_F1B' +ENUMS.Storage.weapons.missiles.Rb04EforA_I__='weapons.missiles.Rb 04E (for A.I.)' +ENUMS.Storage.weapons.containers.MB339_SMOKEPOD='weapons.containers.MB339_SMOKE-POD' +ENUMS.Storage.weapons.containers.HB_F14_EXT_LAU7='weapons.containers.HB_F14_EXT_LAU-7' +ENUMS.Storage.weapons.missiles.P_27T='weapons.missiles.P_27T' +ENUMS.Storage.weapons.adapters.B1B_28store_Conventional_Bomb_Module='weapons.adapters.B-1B_28-store_Conventional_Bomb_Module' +ENUMS.Storage.weapons.shells.GAU8_30_TP='weapons.shells.GAU8_30_TP' +ENUMS.Storage.weapons.droptanks.LNS_VIG_XTANK='weapons.droptanks.LNS_VIG_XTANK' +ENUMS.Storage.weapons.shells._2A7_23_HE='weapons.shells.2A7_23_HE' +ENUMS.Storage.weapons.shells.MINGR55='weapons.shells.MINGR55' +ENUMS.Storage.weapons.gunmounts.M230='weapons.gunmounts.M230' +ENUMS.Storage.weapons.shells.MK45_127mm_Essex='weapons.shells.MK45_127mm_Essex' +ENUMS.Storage.weapons.droptanks.F15E_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.torpedoes.LTF_5B='weapons.torpedoes.LTF_5B' +ENUMS.Storage.weapons.adapters.HB_F4E_LAU117='weapons.adapters.HB_F4E_LAU117' +ENUMS.Storage.weapons.containers.HB_ORD_Missile_Well_Adapter='weapons.containers.HB_ORD_Missile_Well_Adapter' +ENUMS.Storage.weapons.bombs.SC_500_J='weapons.bombs.SC_500_J' +ENUMS.Storage.weapons.adapters.AKU58='weapons.adapters.AKU-58' +ENUMS.Storage.weapons.missiles.PL8A='weapons.missiles.PL-8A' +ENUMS.Storage.weapons.gunmounts.MB339_DEFA553_L='weapons.gunmounts.{MB339_DEFA553_L}' +ENUMS.Storage.weapons.adapters.UB1657UMP='weapons.adapters.UB-16-57UMP' +ENUMS.Storage.weapons.droptanks.fuel_tank_230='weapons.droptanks.fuel_tank_230' +ENUMS.Storage.weapons.nurs.SNEB_TYPE257_H1='weapons.nurs.SNEB_TYPE257_H1' +ENUMS.Storage.weapons.missiles.RB75B='weapons.missiles.RB75B' +ENUMS.Storage.weapons.shells.NR30_30x155_HEI_T='weapons.shells.NR30_30x155_HEI_T' +ENUMS.Storage.weapons.adapters.apu68um3='weapons.adapters.apu-68um3' +ENUMS.Storage.weapons.missiles.R_550_M1='weapons.missiles.R_550_M1' +ENUMS.Storage.weapons.adapters.OH58D_SRACK_L='weapons.adapters.OH58D_SRACK_L' +ENUMS.Storage.weapons.gunmounts.M61='weapons.gunmounts.M-61' +ENUMS.Storage.weapons.missiles.X_41='weapons.missiles.X_41' +ENUMS.Storage.weapons.gunmounts.GSH_23='weapons.gunmounts.GSH_23' +ENUMS.Storage.weapons.missiles.R_530F_EM='weapons.missiles.R_530F_EM' +ENUMS.Storage.weapons.containers.kg600='weapons.containers.kg600' +ENUMS.Storage.weapons.missiles.M31='weapons.missiles.M31' +ENUMS.Storage.weapons.bombs._2502='weapons.bombs.250-2' +ENUMS.Storage.weapons.gunmounts.M134_SIDE_L='weapons.gunmounts.M134_SIDE_L' +ENUMS.Storage.weapons.missiles.AGM_65B='weapons.missiles.AGM_65B' +ENUMS.Storage.weapons.adapters.BRD4250='weapons.adapters.BRD-4-250' +ENUMS.Storage.weapons.bombs.SAMP250LD='weapons.bombs.SAMP250LD' +ENUMS.Storage.weapons.containers.AAQ28_LITENING='weapons.containers.AAQ-28_LITENING' +ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_50gal='weapons.droptanks.Mosquito_Drop_Tank_50gal' +ENUMS.Storage.weapons.shells._7_92x57_Smkl='weapons.shells.7_92x57_Smkl' +ENUMS.Storage.weapons.shells.M68_105_HE='weapons.shells.M68_105_HE' +ENUMS.Storage.weapons.containers.SPRD_99Twin='weapons.containers.SPRD_99Twin' +ENUMS.Storage.weapons.missiles.HBAIM7E='weapons.missiles.HB-AIM-7E' +ENUMS.Storage.weapons.shells.M2_12_7_TG='weapons.shells.M2_12_7_TG' +ENUMS.Storage.weapons.torpedoes.YU6='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.shells.M256_120_AP_L55='weapons.shells.M256_120_AP_L55' +ENUMS.Storage.weapons.gunmounts.GUV_YakB_GSHP='weapons.gunmounts.GUV_YakB_GSHP' +ENUMS.Storage.weapons.bombs.GBU_29='weapons.bombs.GBU_29' +ENUMS.Storage.weapons.gunmounts.GIAT_M261='weapons.gunmounts.GIAT_M261' +ENUMS.Storage.weapons.missiles.R3S='weapons.missiles.R-3S' +ENUMS.Storage.weapons.adapters.LAU131='weapons.adapters.LAU-131' +ENUMS.Storage.weapons.gunmounts.KORD_12_7='weapons.gunmounts.KORD_12_7' +ENUMS.Storage.weapons.shells.M230_ADEMDEFA='weapons.shells.M230_ADEM/DEFA' +ENUMS.Storage.weapons.missiles.SM_2='weapons.missiles.SM_2' +ENUMS.Storage.weapons.gunmounts.CH47_STBD_M134D='weapons.gunmounts.{CH47_STBD_M134D}' +ENUMS.Storage.weapons.missiles.P_27TE='weapons.missiles.P_27TE' +ENUMS.Storage.weapons.missiles.X_25ML='weapons.missiles.X_25ML' +ENUMS.Storage.weapons.containers.lau105='weapons.containers.lau-105' +ENUMS.Storage.weapons.droptanks.FPU_8A='weapons.droptanks.FPU_8A' +ENUMS.Storage.weapons.bombs.BLG66='weapons.bombs.BLG66' +ENUMS.Storage.weapons.shells._2A33_152='weapons.shells.2A33_152' +ENUMS.Storage.weapons.nurs.MO_10104M='weapons.nurs.MO_10104M' +ENUMS.Storage.weapons.shells.M825A1_155_SM='weapons.shells.M825A1_155_SM' +ENUMS.Storage.weapons.gunmounts.AKAN='weapons.gunmounts.AKAN' +ENUMS.Storage.weapons.gunmounts.Browning303MkII='weapons.gunmounts.Browning303MkII' +ENUMS.Storage.weapons.nurs.URAGAN_9M27F='weapons.nurs.URAGAN_9M27F' +ENUMS.Storage.weapons.nurs.FFARMk5HEAT='weapons.nurs.FFAR Mk5 HEAT' +ENUMS.Storage.weapons.nurs.ARF8M3API='weapons.nurs.ARF8M3API' +ENUMS.Storage.weapons.shells._3UBM11_100mm_AP='weapons.shells.3UBM11_100mm_AP' +ENUMS.Storage.weapons.containers.ASO2='weapons.containers.ASO-2' +ENUMS.Storage.weapons.shells.DEFA552_30='weapons.shells.DEFA552_30' +ENUMS.Storage.weapons.gunmounts.DEFA_553='weapons.gunmounts.DEFA_553' +ENUMS.Storage.weapons.missiles.AGM_45A='weapons.missiles.AGM_45A' +ENUMS.Storage.weapons.missiles.Super_530D='weapons.missiles.Super_530D' +ENUMS.Storage.weapons.adapters.mbd3u668='weapons.adapters.mbd3-u6-68' +ENUMS.Storage.weapons.adapters.BRU_55='weapons.adapters.BRU_55' +ENUMS.Storage.weapons.adapters.SA342_Telson8='weapons.adapters.SA342_Telson8' +ENUMS.Storage.weapons.adapters.c25pu='weapons.adapters.c-25pu' +ENUMS.Storage.weapons.bombs.FAB500TA='weapons.bombs.FAB-500TA' +ENUMS.Storage.weapons.bombs.SAMP125LD='weapons.bombs.SAMP125LD' +ENUMS.Storage.weapons.bombs.LYSBOMB_CANDLE='weapons.bombs.LYSBOMB_CANDLE' +ENUMS.Storage.weapons.missiles.AGM_65F='weapons.missiles.AGM_65F' +ENUMS.Storage.weapons.shells._50Browning_AP_M2='weapons.shells.50Browning_AP_M2' +ENUMS.Storage.weapons.shells._5_56x45='weapons.shells.5_56x45' +ENUMS.Storage.weapons.adapters.MatraF1Rocket='weapons.adapters.Matra-F1-Rocket' +ENUMS.Storage.weapons.missiles.LS_6_500='weapons.missiles.LS_6_500' +ENUMS.Storage.weapons.missiles.SA9M311='weapons.missiles.SA9M311' +ENUMS.Storage.weapons.shells.AK100_100='weapons.shells.AK100_100' +ENUMS.Storage.weapons.bombs.BLG66_BELOUGA='weapons.bombs.BLG66_BELOUGA' +ENUMS.Storage.weapons.bombs.BIN_200='weapons.bombs.BIN_200' +ENUMS.Storage.weapons.containers.pl5eii='weapons.containers.pl5eii' +ENUMS.Storage.weapons.gunmounts.CH47_STBD_M240H='weapons.gunmounts.{CH47_STBD_M240H}' +ENUMS.Storage.weapons.droptanks.Spitfire_slipper_tank='weapons.droptanks.Spitfire_slipper_tank' +ENUMS.Storage.weapons.missiles.HOT3_MBDA='weapons.missiles.HOT3_MBDA' +ENUMS.Storage.weapons.shells._53UOR281U='weapons.shells.53-UOR-281U' +ENUMS.Storage.weapons.bombs.BDU_50LGB='weapons.bombs.BDU_50LGB' +ENUMS.Storage.weapons.gunmounts.SHKAS_GUN='weapons.gunmounts.SHKAS_GUN' +ENUMS.Storage.weapons.shells.British303_W_Mk1z='weapons.shells.British303_W_Mk1z' +ENUMS.Storage.weapons.adapters.J11A_twinpylon_r='weapons.adapters.J-11A_twinpylon_r' +ENUMS.Storage.weapons.missiles.P_700='weapons.missiles.P_700' +ENUMS.Storage.weapons.missiles.SA5V28='weapons.missiles.SA5V28' +ENUMS.Storage.weapons.missiles.MIM_72G='weapons.missiles.MIM_72G' +ENUMS.Storage.weapons.adapters.CLB_4='weapons.adapters.CLB_4' +ENUMS.Storage.weapons.droptanks.PTB_200_F86F35='weapons.droptanks.PTB_200_F86F35' +ENUMS.Storage.weapons.shells.Mauser7_92x57_SmK_Lspurgelb="weapons.shells.Mauser7.92x57_S.m.K._L'spur(gelb)" +ENUMS.Storage.weapons.droptanks.PTB_1500_MIG29A='weapons.droptanks.PTB_1500_MIG29A' +ENUMS.Storage.weapons.bombs.GBU_31='weapons.bombs.GBU_31' +ENUMS.Storage.weapons.missiles.Kh66_Grom='weapons.missiles.Kh-66_Grom' +ENUMS.Storage.weapons.containers.HB_ALE_40_15_90='weapons.containers.HB_ALE_40_15_90' +ENUMS.Storage.weapons.containers.U22='weapons.containers.U22' +ENUMS.Storage.weapons.adapters.Spitfire_pilon1='weapons.adapters.Spitfire_pilon1' +ENUMS.Storage.weapons.bombs.OH58D_Violet_Smoke_Grenade='weapons.bombs.OH58D_Violet_Smoke_Grenade' +ENUMS.Storage.weapons.adapters.adapter_gdj_yj83k='weapons.adapters.adapter_gdj_yj83k' +ENUMS.Storage.weapons.adapters.M299='weapons.adapters.M299' +ENUMS.Storage.weapons.adapters.HB_ORD_MER='weapons.adapters.HB_ORD_MER' +ENUMS.Storage.weapons.shells.Mauser7_92x57_SmKH='weapons.shells.Mauser7.92x57_S.m.K.H.' +ENUMS.Storage.weapons.gunmounts.HMP400='weapons.gunmounts.HMP400' +ENUMS.Storage.weapons.containers.F15E_AXQ14_DATALINK='weapons.containers.F-15E_AXQ-14_DATALINK' +ENUMS.Storage.weapons.adapters._9m114_pylon2='weapons.adapters.9m114_pylon2' +ENUMS.Storage.weapons.bombs.BEER_BOMB='weapons.bombs.BEER_BOMB' +ENUMS.Storage.weapons.nurs.C_8OFP2='weapons.nurs.C_8OFP2' +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_RED='weapons.nurs.SNEB_TYPE254_F1B_RED' +ENUMS.Storage.weapons.nurs.C_8OM='weapons.nurs.C_8OM' +ENUMS.Storage.weapons.shells.M61_20_HE_INVIS='weapons.shells.M61_20_HE_INVIS' +ENUMS.Storage.weapons.droptanks.HB_F4E_EXT_WingTank_R='weapons.droptanks.HB_F-4E_EXT_WingTank_R' +ENUMS.Storage.weapons.missiles.CATM_65K='weapons.missiles.CATM_65K' +ENUMS.Storage.weapons.nurs.FFARM156WP='weapons.nurs.FFAR M156 WP' +ENUMS.Storage.weapons.bombs.MK_82SNAKEYE='weapons.bombs.MK_82SNAKEYE' +ENUMS.Storage.weapons.shells.Rh202_20_AP='weapons.shells.Rh202_20_AP' +ENUMS.Storage.weapons.adapters.M261='weapons.adapters.M261' +ENUMS.Storage.weapons.bombs.KAB_1500T='weapons.bombs.KAB_1500T' +ENUMS.Storage.weapons.shells.M339_120mm_HEAT_MP_T='weapons.shells.M339_120mm_HEAT_MP_T' +ENUMS.Storage.weapons.shells.UOF_17_100HE='weapons.shells.UOF_17_100HE' +ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_F1B='weapons.nurs.SNEB_TYPE259E_F1B' +ENUMS.Storage.weapons.shells._5_45x39_NOtr='weapons.shells.5_45x39_NOtr' +ENUMS.Storage.weapons.gunmounts.CH47_AFT_M3M='weapons.gunmounts.{CH47_AFT_M3M}' +ENUMS.Storage.weapons.containers.Fantasm='weapons.containers.Fantasm' +ENUMS.Storage.weapons.missiles.AGM_12C_ED='weapons.missiles.AGM_12C_ED' +ENUMS.Storage.weapons.droptanks.PTB760_MIG19='weapons.droptanks.PTB760_MIG19' +ENUMS.Storage.weapons.missiles.SA9M330='weapons.missiles.SA9M330' +ENUMS.Storage.weapons.missiles.BK90_MJ1_MJ2='weapons.missiles.BK90_MJ1_MJ2' +ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ167='weapons.containers.HB_F14_EXT_AN_APQ-167' +ENUMS.Storage.weapons.containers.MB339_TravelPod='weapons.containers.MB339_TravelPod' +ENUMS.Storage.weapons.gunmounts.OH58D_M3P_L200='weapons.gunmounts.OH58D_M3P_L200' +ENUMS.Storage.weapons.adapters.B1B_10store_Conventional_Bomb_Module='weapons.adapters.B-1B_10-store_Conventional_Bomb_Module' +ENUMS.Storage.weapons.shells.GSH23_23_HE_T='weapons.shells.GSH23_23_HE_T' +ENUMS.Storage.weapons.containers.TANGAZH='weapons.containers.TANGAZH' +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.gunmounts.CH47_PORT_M60D='weapons.gunmounts.{CH47_PORT_M60D}' +ENUMS.Storage.weapons.missiles.M48='weapons.missiles.M48' +ENUMS.Storage.weapons.shells.MAUZER30_30='weapons.shells.MAUZER30_30' +ENUMS.Storage.weapons.adapters.tu22m3mbd='weapons.adapters.tu-22m3-mbd' +ENUMS.Storage.weapons.gunmounts.DEFA554='weapons.gunmounts.DEFA 554' +ENUMS.Storage.weapons.droptanks.F4U1D_Drop_Tank_Mk6='weapons.droptanks.F4U-1D_Drop_Tank_Mk6' +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_YELLOW='weapons.containers.{US_M10_SMOKE_TANK_YELLOW}' +ENUMS.Storage.weapons.bombs.HEBOMB='weapons.bombs.HEBOMB' +ENUMS.Storage.weapons.nurs.Rkt_901_HE='weapons.nurs.Rkt_90-1_HE' +ENUMS.Storage.weapons.adapters.HB_F14_EXT_BRU42='weapons.adapters.HB_F14_EXT_BRU42' +ENUMS.Storage.weapons.missiles.YJ62='weapons.missiles.YJ-62' +ENUMS.Storage.weapons.shells.KDA_35_HE='weapons.shells.KDA_35_HE' +ENUMS.Storage.weapons.shells._2A7_23_AP='weapons.shells.2A7_23_AP' +ENUMS.Storage.weapons.adapters.SA342_LAU_HOT3_1x='weapons.adapters.SA342_LAU_HOT3_1x' +ENUMS.Storage.weapons.missiles.P_9M117='weapons.missiles.P_9M117' +ENUMS.Storage.weapons.adapters.MBD267U='weapons.adapters.MBD-2-67U' +ENUMS.Storage.weapons.shells.AK630_30_HE='weapons.shells.AK630_30_HE' +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk5='weapons.bombs.British_GP_500LB_Bomb_Mk5' +ENUMS.Storage.weapons.bombs.LUU_2AB='weapons.bombs.LUU_2AB' +ENUMS.Storage.weapons.missiles.BK90_MJ2='weapons.missiles.BK90_MJ2' +ENUMS.Storage.weapons.shells.British303_B_Mk4z='weapons.shells.British303_B_Mk4z' +ENUMS.Storage.weapons.adapters.BRU_41A='weapons.adapters.BRU_41A' +ENUMS.Storage.weapons.bombs.BDU_45='weapons.bombs.BDU_45' +ENUMS.Storage.weapons.adapters.b20='weapons.adapters.b-20' +ENUMS.Storage.weapons.missiles.Rapier='weapons.missiles.Rapier' +ENUMS.Storage.weapons.missiles.P_24R='weapons.missiles.P_24R' +ENUMS.Storage.weapons.missiles.AGM_84S='weapons.missiles.AGM_84S' +ENUMS.Storage.weapons.adapters.C25PU='weapons.adapters.C-25PU' +ENUMS.Storage.weapons.containers.BOZ100='weapons.containers.BOZ-100' +ENUMS.Storage.weapons.missiles.AGM_65E='weapons.missiles.AGM_65E' +ENUMS.Storage.weapons.adapters._9M120_pylon2='weapons.adapters.9M120_pylon2' +ENUMS.Storage.weapons.shells.YakB_12_7_T='weapons.shells.YakB_12_7_T' +ENUMS.Storage.weapons.containers.IRDeflector='weapons.containers.IRDeflector' +ENUMS.Storage.weapons.missiles.AIM9P='weapons.missiles.AIM-9P' +ENUMS.Storage.weapons.missiles.SA5B27='weapons.missiles.SA5B27' +ENUMS.Storage.weapons.bombs.SC_500_L2='weapons.bombs.SC_500_L2' +ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA='weapons.containers.HB_F14_EXT_ECA' +ENUMS.Storage.weapons.bombs.SAMP400HD='weapons.bombs.SAMP400HD' +ENUMS.Storage.weapons.adapters.ARAKM70B='weapons.adapters.ARAKM70B' +ENUMS.Storage.weapons.adapters.M260='weapons.adapters.M260' +ENUMS.Storage.weapons.shells.Mauser7_92x57_PmK='weapons.shells.Mauser7.92x57_P.m.K.' +ENUMS.Storage.weapons.missiles.RB75T='weapons.missiles.RB75T' +ENUMS.Storage.weapons.missiles.YJ82='weapons.missiles.YJ-82' +ENUMS.Storage.weapons.bombs.FAB500M54TU='weapons.bombs.FAB-500M54TU' +ENUMS.Storage.weapons.bombs.OH58D_White_Smoke_Grenade='weapons.bombs.OH58D_White_Smoke_Grenade' +ENUMS.Storage.weapons.missiles.X_29TE='weapons.missiles.X_29TE' +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.adapters.lau105='weapons.adapters.lau-105' +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE='weapons.containers.{US_M10_SMOKE_TANK_WHITE}' +ENUMS.Storage.weapons.bombs.Mk_82='weapons.bombs.Mk_82' +ENUMS.Storage.weapons.adapters.BRU42_LS_SUU25='weapons.adapters.BRU-42_LS_(SUU-25)' +ENUMS.Storage.weapons.missiles.Aster_30_Blk_1='weapons.missiles.Aster 30 Blk 1' +ENUMS.Storage.weapons.missiles.Aster_30_Blk_1NT='weapons.missiles.Aster 30 Blk 1NT' +ENUMS.Storage.weapons.missiles.Aster_30_Blk_2='weapons.missiles.Aster 30 Blk 2' +ENUMS.Storage.weapons.missiles.SA9M83M='weapons.missiles.SA9M83M' +ENUMS.Storage.weapons.gunmounts.C130_M4_Rifle='weapons.gunmounts.C130_M4_Rifle' +ENUMS.Storage.weapons.gunmounts.C130_M18_Sidearm_='weapons.gunmounts.{C130-M18-Sidearm}' +ENUMS.Storage.weapons.gunmounts.C130_Cargo_Bay_M4='weapons.gunmounts.{C130-Cargo-Bay-M4}' +ENUMS.Storage.weapons.gunmounts.C130_M18_Sidearm='weapons.gunmounts.C130_M18_Sidearm' +ENUMS.Storage.weapons.droptanks.C130J_Ext_Tank_R='weapons.droptanks.C130J_Ext_Tank_R' +ENUMS.Storage.weapons.droptanks.C130J_Ext_Tank_L='weapons.droptanks.C130J_Ext_Tank_L' +ENUMS.Storage.weapons.missiles.SAHQ2='weapons.missiles.SAHQ2' +ENUMS.Storage.weapons.missiles.Strela_2='weapons.missiles.Strela-2' +ENUMS.Storage.weapons.missiles.Strela_2M='weapons.missiles.Strela-2M' +ENUMS.Storage.weapons.missiles.Strela_3='weapons.missiles.Strela-3' +ENUMS.Storage.weapons.missiles.SA9M83='weapons.missiles.SA9M83' +ENUMS.Storage.weapons.missiles.SAV601P='weapons.missiles.SAV601P' +ENUMS.Storage.weapons.missiles.SA2V759='weapons.missiles.SA2V759' +ENUMS.Storage.weapons.missiles.SA9M317='weapons.missiles.SA9M317' +ENUMS.Storage.weapons.missiles.SA9M82M='weapons.missiles.SA9M82M' +ENUMS.Storage.weapons.missiles.SA9M82='weapons.missiles.SA9M82' +ENUMS.Storage.weapons.missiles.Igla_S='weapons.missiles.Igla_S' +ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC='weapons.gunmounts.{AKAN_NO_TRC}' +ENUMS.Storage.weapons.gunmounts.AKAN='weapons.gunmounts.{AKAN}' +ENUMS.Storage.weapons.shells.M882_9x19='weapons.shells.9x19_m882' +ENUMS.Storage.weapons.gunmounts.UH60LGAU19='weapons.gunmounts.UH-60L GAU-19' +ENUMS.Storage.weapons.gunmounts.UH60L_M134='weapons.gunmounts.UH60L_M134' +ENUMS.Storage.weapons.gunmounts.UH60_M134='weapons.gunmounts.UH60_M134' +ENUMS.Storage.weapons.adapters.uh60l_lwl12='weapons.adapters.uh60l_lwl12' +ENUMS.Storage.weapons.droptanks.uh60l_iafts='weapons.droptanks.uh60l_iafts' +ENUMS.Storage.weapons.gunmounts.UH60_GAU19_LEFT='weapons.gunmounts.{UH60_GAU19_LEFT}' +ENUMS.Storage.weapons.gunmounts.UH60_GAU19_RIGHT='weapons.gunmounts.{UH60_GAU19_RIGHT}' +ENUMS.Storage.weapons.gunmounts.UH60_M134_LEFT='weapons.gunmounts.{UH60_M134_LEFT}' +ENUMS.Storage.weapons.gunmounts.UH60_M134_RIGHT='weapons.gunmounts.{UH60_M134_RIGHT}' +ENUMS.Storage.weapons.gunmounts.UH60L_M134_GUNNER='weapons.gunmounts.{UH60L_M134_GUNNER}' +ENUMS.Storage.weapons.gunmounts.UH60L_M60_GUNNER='weapons.gunmounts.{UH60L_M60_GUNNER}' +ENUMS.Storage.weapons.gunmounts.UH60L_M2_GUNNER='weapons.gunmounts.{UH60L_M2_GUNNER}' +ENUMS.Storage.weapons.gunmounts.UH60_M230_LEFT='weapons.gunmounts.{UH60_M230_LEFT}' +ENUMS.Storage.weapons.gunmounts.UH60_M230_RIGHT='weapons.gunmounts.{UH60_M230_RIGHT}' ENUMS.FARPType={ FARP="FARP", INVISIBLE="INVISIBLE", @@ -1203,6 +1686,18 @@ ENUMS.FARPObjectTypeNamesAndShape={ [ENUMS.FARPType.HELIPADSINGLE]={TypeName="SINGLE_HELIPAD",ShapeName="FARP"}, [ENUMS.FARPType.PADSINGLE]={TypeName="FARP_SINGLE_01",ShapeName="FARP_SINGLE_01"}, } +ENUMS.FrequencyBand={ +HF=0, +VHF_LOW=1, +VHF_HI=2, +UHF=3, +} +ENUMS.ModulationType={ +AM=0, +FM=1, +AMFM=2, +DISCARD=-1, +} SMOKECOLOR=trigger.smokeColor FLARECOLOR=trigger.flareColor BIGSMOKEPRESET={ @@ -1387,6 +1882,23 @@ Spur=21, Stetson=22, Wrath=23, }, +Intruder={ +Raygun=4, +Heartless=5, +Viceroy=6, +Cupcake=7, +["Flying Tiger"]=8, +["Flying Ace"]=9, +Buckeye=10, +Goldplate=11, +Phoenix=12, +Electron=13, +Rustler=14, +Vixen=15, +Jackal=16, +Milestone=17, +Devil=18, +}, } UTILS={ _MarkID=1 @@ -2457,6 +2969,11 @@ if value==Callsign then return name end end +for name,value in pairs(CALLSIGN.Intruder)do +if value==Callsign then +return name +end +end return"Ghostrider" end function UTILS.GMTToLocalTimeDifference() @@ -2635,6 +3152,18 @@ 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 type_name=="C-130J-30"and(unit:getDrawArgumentValue(86)==1)then +BASE:T(unit_name.." rear doors are open") +return true +end +if type_name=="C-130J-30"and(unit:getDrawArgumentValue(87)==1)then +BASE:T(unit_name.." Side door(s) are open") +return true +end +if type_name=="C-130J-30"and(unit:getDrawArgumentValue(88)==1)then +BASE:T(unit_name.." Paratroop door(s) are open") +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 @@ -2659,6 +3188,14 @@ if type_name=="UH-60L"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -3303,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3860,7 +4407,57 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +4475,61 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4558,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -4417,6 +5045,185 @@ end end return nil end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -7703,7 +8510,7 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName=Event.TgtDCSUnitName Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) @@ -7726,10 +8533,12 @@ 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -9543,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -9553,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9600,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9918,6 +10737,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9979,6 +10808,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -10607,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10629,7 +11468,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true self.ScanData.Units[ZoneObject]=ZoneObject end end -if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then +if ObjectCategory==Object.Category.SCENERY then local SceneryType=ZoneObject:getTypeName() local SceneryName=ZoneObject:getName() self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} @@ -11392,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -12071,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -12385,6 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -12426,6 +13283,20 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +end +return self +end function SET_BASE:_Find(ObjectName) local ObjectFound=self.Set[ObjectName] return ObjectFound @@ -12738,7 +13609,8 @@ return true end function SET_BASE:IsInSet(Object) local outcome=false -local name=Object:GetName() +if Object==nil then return false end +local name=(Object~=nil and Object.GetName)and Object:GetName()or"none" self:ForEach( function(object) if object:GetName()==name then @@ -12909,16 +13781,6 @@ self.Filter.Zones[zonename]=Zone end return self end -function SET_GROUP:FilterCoalitions(Coalitions,Clear) -if Clear or(not self.Filter.Coalitions)then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_GROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -13373,16 +14235,6 @@ function SET_UNIT:FindUnit(UnitName) local UnitFound=self.Set[UnitName] return UnitFound end -function SET_UNIT:FilterCoalitions(Coalitions) -self.Filter.Coalitions={} -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_UNIT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14027,18 +14879,6 @@ 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={} @@ -14465,18 +15305,6 @@ self.Filter.Playernames[playername]=playername end return self end -function SET_CLIENT: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_CLIENT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14650,8 +15478,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -14866,18 +15693,6 @@ function SET_PLAYER:FindClient(PlayerName) local ClientFound=self.Set[PlayerName] return ClientFound end -function SET_PLAYER: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_PLAYER:FilterZones(Zones) if not self.Filter.Zones then self.Filter.Zones={} @@ -15131,18 +15946,6 @@ function SET_AIRBASE:GetRandomAirbase() local RandomAirbase=self:GetRandom() return RandomAirbase end -function SET_AIRBASE: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_AIRBASE:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -15303,18 +16106,6 @@ function SET_CARGO:FindCargo(CargoName) local CargoFound=self.Set[CargoName] return CargoFound end -function SET_CARGO: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_CARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -15934,16 +16725,6 @@ self.Filter.Prefixes[Prefix]=Prefix end return self end -function SET_OPSZONE:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_OPSZONE:FilterOnce() for ObjectName,Object in pairs(self.Database)do self:Remove(ObjectName,true) @@ -16160,6 +16941,18 @@ self:Remove(RemoveGroupName) end return self end +function SET_OPSGROUP:CountAlive() +local CountG=0 +local CountU=0 +local Set=self:GetSet() +for GroupID,GroupData in pairs(Set)do +if GroupData and GroupData:IsAlive()then +CountG=CountG+1 +CountU=CountU+GroupData:GetGroup():CountAliveUnits() +end +end +return CountG,CountU +end function SET_OPSGROUP:FindGroup(GroupName) local GroupFound=self.Set[GroupName] return GroupFound @@ -16176,18 +16969,6 @@ function SET_OPSGROUP:FindNavyGroup(GroupName) local GroupFound=self:FindGroup(GroupName) return GroupFound end -function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear) -if Clear or 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_OPSGROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -16669,18 +17450,6 @@ DCargoInclude=DCargoInclude and MClientFunc end return DCargoInclude end -function SET_DYNAMICCARGO: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_DYNAMICCARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -17606,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -17814,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -18693,41 +19480,6 @@ return nil 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 -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 -end -do VELOCITY={ ClassName="VELOCITY", } @@ -20107,6 +20859,9 @@ SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-Curren end end if self.SpawnRandomizeUnits then +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -21032,6 +21787,11 @@ min=9 max=18 ctable=CALLSIGN.F15E end +if SpawnTemplate.units[1].type=="A6E"then +min=4 +max=18 +ctable=CALLSIGN.Intruder +end local callsignnr=math.random(min,max) local callsignname="Enfield" for name,value in pairs(ctable)do @@ -21547,6 +22307,18 @@ function SPAWNSTATIC:InitCargo(IsCargo) self.InitStaticCargo=IsCargo return self end +function SPAWNSTATIC:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWNSTATIC:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWNSTATIC:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead return self @@ -21640,6 +22412,15 @@ end if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end +if self.SpawnHiddenOnPlanner then +Template.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +Template.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +Template.hidden=self.SpawnHiddenOnMap +end if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -21705,7 +22486,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21713,8 +22494,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -21962,20 +22741,17 @@ MARKEROPS_BASE={ ClassName="MARKEROPS", Tag="mytag", Keywords={}, -version="0.1.4", +version="0.1.5", debug=false, Casesensitive=true, } -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) 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","*") @@ -22038,9 +22814,9 @@ end end function MARKEROPS_BASE:_MatchTag(Eventtext) local matches=false -if not self.Casesensitive then +if self.Casesensitive==false then local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then +if string.find(string.lower(Eventtext),type,1,true)then matches=true end else @@ -22076,6 +22852,14 @@ self:UnHandleEvent(EVENTS.MarkAdded) self:UnHandleEvent(EVENTS.MarkChange) self:UnHandleEvent(EVENTS.MarkRemoved) end +function MARKEROPS_BASE:SwitchCaseSensitiveOff() +self.Casesensitive=false +return self +end +function MARKEROPS_BASE:SwitchCaseSensitiveOn() +self.Casesensitive=true +return self +end TEXTANDSOUND={ ClassName="TEXTANDSOUND", version="0.0.1", @@ -22180,7 +22964,7 @@ ClassName="PATHLINE", lid=nil, points={}, } -PATHLINE.version="0.1.1" +PATHLINE.version="0.2.0" function PATHLINE:New(Name) local self=BASE:Inherit(self,BASE:New()) self.name=Name or"Unknown Path" @@ -22279,12 +23063,26 @@ return point.vec2 end return nil end +function PATHLINE:GetLength(Project2D) +local l=0 +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +if Project2D then +l=l+UTILS.VecDist2D(p1.vec2,p2.vec2) +else +l=l+UTILS.VecDist3D(p1.vec3,p2.vec3) +end +end +return l +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) +UTILS.RemoveMark(point.markerID) end else if point.markerID then @@ -22295,6 +23093,31 @@ local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m trigger.action.markToAll(point.markerID,text,point.vec3,"") end end +return self +end +function PATHLINE:DrawLine(Recipient,Color,LineType) +Recipient=Recipient or-1 +Color=Color or{1,0,0,1.0} +LineType=LineType or 1 +local ReadOnly=false +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +p1.lineID=UTILS.GetMarkID() +trigger.action.lineToAll(Recipient,p1.lineID,p1.vec3,p2.vec3,Color,LineType,ReadOnly,"") +end +return self +end +function PATHLINE:UnDrawLine(Delay) +local np=#self.points +for _,_point in pairs(self.points)do +local p=_point +if p.lineID then +UTILS.RemoveMark(p.lineID,Delay) +end +end +return self end function PATHLINE:_CreatePoint(Vec) local point={} @@ -22833,6 +23656,529 @@ end end return self end +VECTOR={ +ClassName="VECTOR", +verbose=0, +} +VECTOR.version="0.1.0" +_VECTORID=0 +VECTOR.__index=VECTOR +function VECTOR:New(x,y,z) +if z==nil then +self=setmetatable({x=x or 0,y=0,z=y or 0},VECTOR) +else +self=setmetatable({x=x or 0,y=y or 0,z=z or 0},VECTOR) +end +_VECTORID=_VECTORID+1 +self.uid=_VECTORID +return self +end +function VECTOR:NewFromVec(Vec) +local vector=VECTOR:New(Vec.x,Vec.y,Vec.z) +return vector +end +function VECTOR:NewFromPolar(r,phi) +local Phi=math.rad(phi) +local x=r*math.cos(phi) +local y=r*math.sin(phi) +local v=VECTOR:New(x,y) +return self +end +function VECTOR:NewFromSpherical(r,theta,phi) +local sinPhi=math.sin(math.rad(phi)) +local cosPhi=math.cos(math.rad(phi)) +local sinTheta=math.sin(math.rad(theta)) +local cosTheta=math.cos(math.rad(theta)) +local x=r*sinTheta*cosPhi +local y=r*sinTheta*sinPhi +local z=r*cosTheta +local v=VECTOR:New(x,y,z) +return self +end +function VECTOR:NewDirectionalVector(a,b) +local x=b.x-a.x +local y +local z +if a.z and b.z then +y=b.y-a.y +z=b.z-a.z +elseif b.z then +y=b.y-0 +z=b.z-a.y +elseif a.z then +y=0-a.y +z=b.y-a.z +else +y=b.y-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:NewFromLLDD(Latitude,Longitude,Altitude) +local vec3=coord.LLtoLO(Latitude,Longitude) +self=VECTOR:NewFromVec(vec3) +if Altitude then +self.y=Altitude +end +return self +end +function VECTOR:NewFromLLDMS(Latitude,Longitude,Altitude) +local lat=UTILS.LLDMSstringToDD(Latitude) +local lon=UTILS.LLDMSstringToDD(Longitude) +self=VECTOR:NewFromLLDD(lat,lon,Altitude) +return self +end +function VECTOR:GetVec2() +local vec={x=self.x,y=self.z} +return vec +end +function VECTOR:GetVec3(OnSurface) +local x=self.x +local y=OnSurface and land.getHeight({x=self.x,y=self.z})or self.y +local z=self.z +local vec={x=x,y=y,z=z} +return vec +end +function VECTOR:GetCoordinate(OnSurface) +local vec3=self:GetVec3(OnSurface) +local coordinate=COORDINATE:NewFromVec3(vec3) +return coordinate +end +function VECTOR:GetDistance(Vector,Only2D) +local dx=self.x-Vector.x +local dy=0 +local dz=0 +if Vector.z then +if not Only2D then +dy=self.y-Vector.y +end +dz=self.z-Vector.z +else +dy=0 +dz=self.z-Vector.y +end +local dist=math.sqrt(dx*dx+dy*dy+dz*dz) +return dist +end +function VECTOR:GetDirectionalVectorTo(a) +local x=a.x-self.x +local y=0 +local z=nil +if a.z then +y=a.y-self.y +z=a.z-self.z +else +y=a.y-self.z +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetDirectionalVectorFrom(a) +local x=self.x-a.x +local y +local z +if a.z then +y=self.y-a.y +z=self.z-a.z +else +y=self.z-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetLength() +local l=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) +return l +end +function VECTOR:GetHeading(To360) +local heading=math.atan2(self.z,self.x) +heading=math.deg(heading) +if To360==nil or To360==true then +heading=UTILS.AdjustHeading360(heading) +else +if heading==360.0 then +heading=0.0 +end +end +return heading +end +function VECTOR:GetHeadingTo(Vector) +local a=self:GetDirectionalVectorTo(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetHeadingFrom(Vector) +local a=self:GetDirectionalVectorFrom(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetLatitudeLongitude() +local vec3=self:GetVec3() +local latitude,longitude,altitude=coord.LOtoLL(vec3) +return latitude,longitude +end +function VECTOR:GetMGRS() +local lat,long=self:GetLatitudeLongitude() +local mgrs=coord.LLtoMGRS(lat,long) +return mgrs +end +function VECTOR:GetHeadingDelta(Vector) +local h1=self:GetHeading(false) +local h2=Vector:GetHeading(false) +local delta=h2-h1 +return delta +end +function VECTOR:GetIntermediateVector(Vector,Fraction) +local f=Fraction or 0.5 +local vec=self:GetDirectionalVectorTo(Vector) +local length=vec:GetLength() +vec:SetLength(f*length) +vec=self+vec +return vec +end +function VECTOR:SetLength(Length) +self:Normalize() +local v=self*Length +self:Replace(v) +return self +end +function VECTOR:SetX(x) +self.x=x or 0 +return self +end +function VECTOR:SetY(y) +if y==nil then +y=self:GetSurfaceHeight() +end +self.y=y +return self +end +function VECTOR:SetZ(z) +self.z=z or 0 +return self +end +function VECTOR:AddVec(Vec) +self.x=self.x+Vec.x +if Vec.z then +self.y=self.y+Vec.y +self.z=self.z+Vec.z +else +self.z=self.z+Vec.y +end +return self +end +function VECTOR:SubVec(Vec) +self.x=self.x-Vec.x +if Vec.z then +self.y=self.y-Vec.y +self.z=self.z-Vec.z +else +self.z=self.z-Vec.y +end +return self +end +function VECTOR:Dot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Rot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Copy() +local c=VECTOR:New(self.x,self.y,self.z) +return c +end +function VECTOR:Replace(Vector,Project2D) +self.x=Vector.x +if Vector.z then +self.y=Vector.y +self.z=Vector.z +else +if Project2D then +self.y=0 +end +self.z=Vector.y +end +return self +end +function VECTOR:Normalize() +local l=self:GetLength() +if l~=0 then +self:Replace(self/l) +end +return self +end +function VECTOR:Translate(Distance,Heading,Copy) +Distance=Distance or 1000 +local alpha=math.rad(Heading or 0) +local vector=Copy and self:Copy()or self +vector.x=Distance*math.cos(alpha)+vector.x +vector.z=Distance*math.sin(alpha)+vector.z +return vector +end +function VECTOR:Rotate2D(Angle,Copy) +local phi=-math.rad(Angle or 0) +local sinPhi=math.sin(phi) +local cosPhi=math.cos(phi) +local X=self.z +local Y=self.x +local z=X*cosPhi-Y*sinPhi +local x=X*sinPhi+Y*cosPhi +if Copy then +local vector=VECTOR:New(x,self.y,z) +return vector +else +self:SetX(x) +self:SetZ(z) +return self +end +end +function VECTOR: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) +local text="MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +return text +end +function VECTOR:GetSurfaceType() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +return s +end +function VECTOR:GetSurfaceTypeName() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +for name,id in land.SurfaceType()do +if id==s then +return name +end +end +return"unknown" +end +function VECTOR:IsVisible(Vec) +local vec1=self:GetVec3() +local vec2={x=Vec.x,Vec.y,Vec.z} +local los=land.isVisible(vec1,vec2) +return los +end +function VECTOR:GetClosestRoad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('roads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetClosestRailroad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('railroads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetPathOnRoad(Vec) +local vec1=self:GetVec2() +local vec2=Vec:GetVec2() +local vec2points=land.findPathOnRoads("roads",vec1.x,vec1.y,vec2.x,vec2.y) +local path=nil +if vec2points then +path=PATHLINE:NewFromVec2Array("Road",vec2points) +end +return path +end +function VECTOR:GetProfile(Vec3) +local vec3=self:GetVec3() +local vec3s=land.profile(vec3,Vec3) +local profile=nil +if vec3s then +profile=PATHLINE:NewFromVec3Array("Profile",vec3s) +end +return profile +end +function VECTOR:GetInterceptPoint(DirectionVector,Distance) +local vec3=self:GetVec3() +local ip3=land.getIP(vec3,DirectionVector,Distance or 1000) +local ipvector=nil +if ip3 then +ipvector=VECTOR:New(ip3.x,ip3.y,ip3.z) +end +return ipvector +end +function VECTOR:GetSurfaceHeight() +local vec2=self:GetVec2() +local h=land.getHeight(vec2) +return h +end +function VECTOR:GetSurfaceHeightAndDepth() +local vec2=self:GetVec2() +local h,d=land.getSurfaceHeightWithSeabed(vec2) +return h,d +end +function VECTOR:GetWindVector(WithTurbulence) +local vec3=self:GetVec3() +local wind=nil +if WithTurbulence then +wind=atmosphere.getWindWithTurbulence(vec3) +else +wind=atmosphere.getWind(vec3) +end +local vector=VECTOR:New(wind) +return vector +end +function VECTOR:GetTemperaturAndPressure() +local vec3=self:GetVec3() +local t,p=atmosphere.getTemperatureAndPressure(vec3) +return t,p +end +function VECTOR:Smoke(Color,Duration) +local vec3=self:GetVec3() +Color=Color or 0 +local name=string.format("Vector-Smoke-%d",self.uid) +trigger.action.smoke(vec3,Color,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -22932,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -23079,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23116,6 +24483,16 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetPointVec2() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23147,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -23154,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -23314,12 +24693,18 @@ end BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -23439,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -23934,6 +25329,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -26196,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -27852,33 +29288,45 @@ end function GROUP:GetTypeName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +end return nil end function GROUP:GetNatoReportingName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +end return"Bogey" end function GROUP:GetPlayerName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +end return nil end function GROUP:GetCallsign() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -27898,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -28549,7 +30006,7 @@ function GROUP:GetTaskMission() return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) end function GROUP:GetTaskRoute() -if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) else return{} @@ -28563,7 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -28585,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -28828,6 +30285,10 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +return self +end function GROUP:SetCommandInvisible(switch) return self:CommandSetInvisible(switch) end @@ -29954,6 +31415,20 @@ end function UNIT:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits=Enabled end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -30261,6 +31736,7 @@ self.Life0=Life0 else self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -30273,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -30338,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30348,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30357,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30446,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -30454,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -30476,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -30486,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -30504,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -30511,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -30524,18 +32028,15 @@ AIRBASE.Normandy={ ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -["Hawkinge"]="Hawkinge", } 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -30578,13 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -30600,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -30612,16 +32115,17 @@ AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", -["Marka"]="Marka", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -30638,17 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Ben_Gurion"]="Ben Gurion", -["Hatzor"]="Hatzor", -["Palmachim"]="Palmachim", -["Tel_Nof"]="Tel Nof", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -30656,8 +32157,7 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } -AIRBASE.MarianaIslandsWWII= -{ +AIRBASE.MarianaIslandsWWII={ ["Agana"]="Agana", ["Airfield_3"]="Airfield 3", ["Charon_Kanoa"]="Charon Kanoa", @@ -30720,6 +32220,7 @@ AIRBASE.Sinai={ ["Cairo_West"]="Cairo West", ["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["El_Minya"]="El Minya", @@ -30731,8 +32232,11 @@ AIRBASE.Sinai={ ["Inshas_Airbase"]="Inshas Airbase", ["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", ["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", ["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", @@ -30746,45 +32250,50 @@ AIRBASE.Sinai={ ["Sde_Dov"]="Sde Dov", ["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", ["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", ["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", ["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", ["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", ["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", ["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", ["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", ["Severomorsk_1"]="Severomorsk-1", ["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", ["Vidsel"]="Vidsel", ["Vuojarvi"]="Vuojarvi", -["Andoya"]="Andoya", -["Alakurtti"]="Alakurtti", -["Kittila"]="Kittila", -["Bardufoss"]="Bardufoss", -["Alta"]="Alta", -["Sodankyla"]="Sodankyla", -["Enontekio"]="Enontekio", -["Evenes"]="Evenes", -["Hosio"]="Hosio", -["Kilpyavr"]="Kilpyavr", -["Afrikanda"]="Afrikanda", -["Kalevala"]="Kalevala", -["Koshka_Yavr"]="Koshka Yavr", -["Poduzhemye"]="Poduzhemye", -["Luostari_Pechenga"]="Luostari Pechenga", } AIRBASE.Afghanistan={ ["Bagram"]="Bagram", @@ -30794,6 +32303,10 @@ AIRBASE.Afghanistan={ ["Camp_Bastion_Heliport"]="Camp Bastion Heliport", ["Chaghcharan"]="Chaghcharan", ["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", ["Farah"]="Farah", ["Gardez"]="Gardez", ["Ghazni_Heliport"]="Ghazni Heliport", @@ -30803,7 +32316,6 @@ AIRBASE.Afghanistan={ ["Kandahar"]="Kandahar", ["Kandahar_Heliport"]="Kandahar Heliport", ["Khost"]="Khost", -["Khost_Heliport"]="Khost Heliport", ["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", ["Nimroz"]="Nimroz", ["Qala_i_Naw"]="Qala i Naw", @@ -30814,21 +32326,28 @@ AIRBASE.Afghanistan={ ["Urgoon_Heliport"]="Urgoon Heliport", } AIRBASE.Iraq={ -["Baghdad_International_Airport"]="Baghdad International Airport", -["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", -["Al_Sahra_Airport"]="Al-Sahra Airport", -["Erbil_International_Airport"]="Erbil International Airport", -["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", ["Al_Salam_Airbase"]="Al-Salam Airbase", -["Balad_Airbase"]="Balad Airbase", -["Kirkuk_International_Airport"]="Kirkuk International Airport", -["Bashur_Airport"]="Bashur Airport", +["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Taquddum_Airport"]="Al-Taquddum Airport", -["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", ["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", } AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", ["Airracing_Frankfurt"]="Airracing Frankfurt", ["Airracing_Koblenz"]="Airracing Koblenz", ["Airracing_Luebeck"]="Airracing Lubeck", @@ -30839,16 +32358,23 @@ AIRBASE.GermanyCW={ ["Bienenfarm"]="Bienenfarm", ["Bindersleben"]="Bindersleben", ["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", ["Braunschweig"]="Braunschweig", ["Bremen"]="Bremen", ["Briest"]="Briest", ["Buechel"]="Buchel", ["Bueckeburg"]="Buckeburg", ["Celle"]="Celle", +["Chojna"]="Chojna", ["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", ["Damgarten"]="Damgarten", ["Dedelow"]="Dedelow", ["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", ["Fassberg"]="Fassberg", ["Finow"]="Finow", ["Frankfurt"]="Frankfurt", @@ -30859,7 +32385,7 @@ AIRBASE.GermanyCW={ ["Gatow"]="Gatow", ["Gelnhausen"]="Gelnhausen", ["Giebelstadt"]="Giebelstadt", -["Glindbruchkippe"]="Glindbruchkippe ", +["Glindbruchkippe"]="Glindbruchkippe", ["Gross_Mohrdorf"]="Gross Mohrdorf", ["Grosse_Wiese"]="Grosse Wiese", ["Guetersloh"]="Gutersloh", @@ -30977,43 +32503,60 @@ AIRBASE.GermanyCW={ ["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", ["Hannover"]="Hannover", ["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", ["Herrenteich"]="Herrenteich", ["Hildesheim"]="Hildesheim", ["Hockenheim"]="Hockenheim", ["Holzdorf"]="Holzdorf", ["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", ["Koethen"]="Kothen", ["Laage"]="Laage", +["Landstuhl"]="Landstuhl", ["Langenselbold"]="Langenselbold", ["Laerz"]="Larz", -["Leipzig_Halle"]="Leipzig Halle", ["Leipzig_Mockau"]="Leipzig Mockau", ["Luebeck"]="Lubeck", ["Lueneburg"]="Luneburg", ["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", ["Mendig"]="Mendig", ["Merseburg"]="Merseburg", ["Neubrandenburg"]="Neubrandenburg", ["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", ["Northeim"]="Northeim", +["Noervenich"]="Norvenich", ["Ober_Moerlen"]="Ober-Morlen", ["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", ["Parchim"]="Parchim", ["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", ["Pferdsfeld"]="Pferdsfeld", ["Pinnow"]="Pinnow", ["Pottschutthoehe"]="Pottschutthohe", ["Ramstein"]="Ramstein", +["Revinge"]="Revinge", ["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", ["Schoenefeld"]="Schonefeld", ["Schweinfurt"]="Schweinfurt", ["Sembach"]="Sembach", +["Sittensen"]="Sittensen", ["Spangdahlem"]="Spangdahlem", ["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", ["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", ["Tegel"]="Tegel", ["Tempelhof"]="Tempelhof", ["Templin"]="Templin", +["Thurland"]="Thurland", ["Tutow"]="Tutow", ["Uelzen"]="Uelzen", ["Uetersen"]="Uetersen", @@ -31029,6 +32572,7 @@ AIRBASE.GermanyCW={ ["Worms"]="Worms", ["Wunstorf"]="Wunstorf", ["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", ["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ @@ -31096,9 +32640,19 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -31345,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -32036,16 +33617,55 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end if self.SceneryObject and self.SceneryObject.getLife then -self.Life0=self.SceneryObject:getLife()or 0 +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) @@ -32064,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 +local life=1 if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -32104,14 +33746,16 @@ end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -32130,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -32154,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -32183,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -33036,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -33508,7 +35167,7 @@ self:SetLiquid(lqno,lqam) end end else -self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end if self:IsLimitedAircraft()then @@ -33526,7 +35185,7 @@ self:SetAmount(acname,acnumber) end end else -self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end if self:IsLimitedWeapons()then @@ -33555,7 +35214,7 @@ end end end else -self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end return self @@ -33626,6 +35285,9 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33634,8 +35296,26 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.1.0" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -33858,13 +35538,29 @@ end end self.LastPosition=pos else -if self.timer and self.timer:IsRunning()then self.timer:Stop()end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") self.CargoState=DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end return self end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end function DYNAMICCARGO._FilterHeloTypes(client) if not client then return false end local typename=client:GetTypeName() @@ -35200,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -35213,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -35235,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -35247,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -35274,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -35306,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -35313,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -35452,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -35562,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -35588,6 +37412,7 @@ local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -35601,7 +37426,6 @@ 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 @@ -35613,14 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName -if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then TargetCategory=Unit.Category.STRUCTURE TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") end 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) @@ -35629,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -35640,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -35712,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -35730,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -35819,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -35918,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -35935,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -36173,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -36196,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -36204,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -36272,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -36293,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -43849,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -43860,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -43886,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -43916,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -43939,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -43954,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -43963,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -44575,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -44607,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -44626,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -46820,7 +48690,7 @@ maxrange=7000, reloadtime=nil, }, } -ARTY.version="1.3.3" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -47500,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -51029,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -51326,7 +53220,6 @@ 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) @@ -51430,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -51632,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -52441,7 +54334,6 @@ 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 @@ -54931,7 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", -version="0.9.34", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -54982,6 +56874,9 @@ SmokeDecoyColor=SMOKECOLOR.White, checkcounter=1, DLinkCacheTime=120, logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -55027,10 +56922,14 @@ MANTIS.SamData={ ["SA-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, ["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"}, -["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, ["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, ["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, ["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, @@ -55109,13 +57008,14 @@ MANTIS.SamDataCH={ ["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} @@ -55227,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -55268,6 +57273,69 @@ self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -55557,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -55566,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -55579,7 +57661,7 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end if self.checkforfriendlies==true and self.friendlyset==nil then self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() @@ -55635,9 +57717,27 @@ self:T(self.lid.."Starting Intel Detection") local groupset=self.EWR_Group local samset=self.SAM_Group self.intelset={} -local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne") +local IntelOne=INTEL:New(groupset,self.coalition,self.name.." IntelOne") +IntelOne.DetectAccoustic=self.DetectAccoustic +IntelOne.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelOne.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelOne:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelOne:Start() -local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo") +local IntelTwo=INTEL:New(samset,self.coalition,self.name.." IntelTwo") +IntelTwo.DetectAccoustic=self.DetectAccoustic +IntelTwo.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelTwo.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelTwo:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelTwo:Start() local CacheTime=self.DLinkCacheTime or 120 local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) @@ -55893,6 +57993,22 @@ self:T(self.lid.."RemoveShorad") self.ShoradLink=false return self end +function MANTIS:_SmokeUnits(Group) +self:T("Smoking") +local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME")or 0 +local TNow=timer.getTime() +if TNow-LastSmoketime>290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 @@ -55906,7 +58022,7 @@ local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 -local shortsam=(_data[6]==MANTIS.SamType.SHORT)and true or false +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false if not shortsam then shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false end @@ -55931,17 +58047,10 @@ switch=true end if self.SamStateTracker[name]~="RED"and switch then self:__RedState(1,samgroup) -self.SamStateTracker[name]="RED" end -if shortsam==true and self.SmokeDecoy==true then +if shortsam==true and self.SmokeDecoy==true and 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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -60798,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -62764,7 +65158,7 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then @@ -62784,13 +65178,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -62820,7 +65214,7 @@ return aoa end function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -62830,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -63463,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -63826,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -64092,7 +66487,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -64114,6 +66514,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -64856,77 +67278,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -65214,19 +67568,19 @@ end if 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" +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" +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)" +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)" +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 @@ -65841,7 +68195,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true) text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) local unitClient=Unit.getByName(unit:GetName()) local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early if hornet then local nozzlePosL=0 local burnerPosL=unitClient:getDrawArgumentValue(28) @@ -65957,7 +68311,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89) else nozzlePosL=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosL=unitClient:getDrawArgumentValue(434) end return nozzlePosL @@ -65974,7 +68328,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90) else nozzlePosR=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosR=unitClient:getDrawArgumentValue(433) end return nozzlePosR @@ -66025,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -66126,7 +68478,7 @@ 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=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -66317,7 +68669,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -66332,6 +68684,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66439,14 +68792,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66494,6 +68841,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66510,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -67557,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -70463,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -70509,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -70707,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -70726,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -70736,14 +73093,12 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) return self @@ -72802,6 +75157,9 @@ self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false self.ResourceMap=nil self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end self.StaticShape=nil self.TypeNames=nil self.StaticCategory="Cargos" @@ -72809,6 +75167,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -72865,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -72890,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -73181,7 +75544,9 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, @@ -73196,8 +75561,9 @@ CTLD.FixedWingTypes={ ["Hercules"]="Hercules", ["Bronco"]="Bronco", ["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", } -CTLD.version="1.3.37" +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -73240,12 +75606,15 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") self:AddTransition("*","CratesPacked","*") self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") @@ -73286,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -73299,7 +75670,10 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false self.enableFixedWing=false @@ -73326,6 +75700,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -73338,6 +75713,8 @@ self.filename=string.format("CTLD_%s_Persist.csv",AliaS) self.allowcratepickupagain=true self.enableslingload=false self.basetype="container_cargo" +self.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do @@ -73418,6 +75795,15 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end if _unit:IsHelicopter()or _group:IsHelicopter()then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil @@ -73453,6 +75839,7 @@ end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end elseif event.id==EVENTS.NewDynamicCargo then self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo @@ -73478,6 +75865,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73514,6 +75902,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73521,6 +75910,13 @@ self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -73548,6 +75944,12 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end function CTLD:AddAllowedFixedWingType(typename) @@ -73697,6 +76099,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -73792,7 +76195,7 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) local hassecondaries=false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!",10,false,Group) @@ -73921,18 +76324,340 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -73961,7 +76686,7 @@ 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 +if not self.debug then return false end end end end @@ -73971,13 +76696,16 @@ local loaddist=self.CrateDistance or 35 local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false +end +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -73992,6 +76720,7 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 @@ -74034,8 +76763,12 @@ self.CrateCounter=self.CrateCounter+1 local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() local basetype=CType or self.basetype or"container_cargo" CCat=CCat or"Cargos" -if isstatic then -basetype=cratetemplate +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -74081,8 +76814,8 @@ 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) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -74094,23 +76827,35 @@ realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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) else +if not quiet then self:_SendMessage(text,10,false,Group) end +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -74209,6 +76954,52 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis end return self end +function CTLD:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +end +return self +end function CTLD:_RemoveCratesNearby(_group,_unit) self:T(self.lid.." _RemoveCratesNearby") local finddist=self.CrateDistance or 35 @@ -74221,8 +77012,10 @@ for _,_entry in pairs(crates)do local entry=_entry local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -if entry:GetPositionable()then -entry:GetPositionable():Destroy(false) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end table.insert(removedIDs,entry:GetID()) end @@ -74231,8 +77024,17 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -74258,7 +77060,7 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() @@ -74294,8 +77096,16 @@ local cando=cargo:UnitCanCarry(_unit) if ignoretype==true then cando=true end self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distance0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -74527,6 +77347,18 @@ end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -74670,7 +77502,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -74766,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74776,6 +77611,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74864,6 +77700,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74873,6 +77710,9 @@ end end return self end +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then @@ -74889,11 +77729,19 @@ self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end -local finddist=self.CrateDistance or 35 +local baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -74903,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -74925,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -74953,19 +77812,55 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end self:__CratesBuildStarted(1,Group,Unit,build.Name) -self:_RefreshDropTroopsMenu(Group,Unit) else self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end +end end end end @@ -74978,24 +77873,32 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) self:_RefreshLoadCratesMenu(Group,Unit) self:__CratesPacked(1,Group,Unit,_entry) -return true +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end +if not packedAny then self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end +return true +end function CTLD:_RepairCrates(Group,Unit,Engineering) self:T(self.lid.." _RepairCrates") local finddist=self.CrateDistance or 35 @@ -75163,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -75210,13 +78114,57 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -75225,6 +78173,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -75349,85 +78530,112 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu,subcatmenus) +if cargoObj.DontShowInMenu then +return +end +local isStaticCargo=false +if(cargoObj.GetType and cargoObj:GetType()==CTLD_CARGO.Enum.STATIC)or cargoObj.CargoType==CTLD_CARGO.Enum.STATIC then +isStaticCargo=true +end +if isStaticCargo and cargoObj.UnitCanCarry and not cargoObj:UnitCanCarry(_unit)then +return +end +local parent=parentMenu +if subcatmenus and cargoObj.Subcategory then +parent=subcatmenus[cargoObj.Subcategory] +if not parent then +parent=MENU_GROUP:New(_group,cargoObj.Subcategory,cratesmenu) +subcatmenus[cargoObj.Subcategory]=parent +end +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for catName,_ in pairs(self.subcats)do -subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) -end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} -for catName,_ in pairs(self.subcats)do +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end +return subcatmenus[catName] +end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75435,8 +78643,14 @@ MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,c end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75485,6 +78699,43 @@ MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,sel end end end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end +else +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) @@ -75507,6 +78758,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75518,6 +78771,10 @@ end function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then @@ -75551,7 +78808,7 @@ lineIndex=lineIndex+1 end end end -function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) local grounded=not self:IsUnitInAir(Unit) local hover=self:CanHoverLoad(Unit) @@ -75582,18 +78839,23 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) local capacity=capabilities.cratelimit or 0 if loadedData.Cratesloaded>=capacity then self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress return self end local spaceLeft=capacity-loadedData.Cratesloaded local toLoad=math.min(found,needed,spaceLeft) if toLoad<1 then self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress return self end local crateIDsLoaded={} @@ -75626,6 +78888,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if details or(not batch)then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75643,8 +78906,29 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75738,6 +79022,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -75781,29 +79066,62 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75820,33 +79138,98 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) -MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then -MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) -end i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75860,7 +79243,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end 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) @@ -75968,6 +79351,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -75998,6 +79382,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -76070,7 +79455,45 @@ table.insert(self.Cargo_Troops,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnits") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Units for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock or nil +entry.Stock0=Stock or nil +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=true +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddUnitsNoMove(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnitsNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."UnitsNoMove for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock +entry.Stock0=Stock +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=false +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) self:T(self.lid.." AddCratesCargo") if not self:_CheckTemplates(Templates)then self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") @@ -76085,11 +79508,41 @@ cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end +cargo.C130TypeName=C130TypeName table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) +self:T(self.lid.." AddCratesCargoNoMove") +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) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +cargo.C130TypeName=C130TypeName +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 local type=CTLD_CARGO.Enum.STATIC @@ -76100,6 +79553,9 @@ if unittemplate and unittemplate.resourcePayload then ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) end local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end @@ -76241,7 +79697,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -76264,9 +79725,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -76600,6 +80097,7 @@ return self end function CTLD:IsCorrectHover(Unit) self:T(self.lid.." IsCorrectHover") +if self:IsFixedWing(Unit)then return false end local outcome=false if self:IsUnitInAir(Unit)then local uspeed=Unit:GetVelocityMPS() @@ -76777,6 +80275,23 @@ Troopstable[genname].GenericCargo=generic end end end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end for _id,_cargo in pairs(self.Cargo_Troops)do local generic=_cargo local genname=generic:GetName() @@ -76807,10 +80322,31 @@ Troopstable[genname].Infield=Troopstable[genname].Infield+1 Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo end else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) end end end +end for _unitname,_loaded in pairs(self.Loaded_Cargo)do local _unit=UNIT:FindByName(_unitname) if _unit and _unit:IsAlive()then @@ -76819,12 +80355,12 @@ local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} for _,_cgo in pairs(loadedcargo)do local cargo=_cgo local type=cargo.CargoType -local gname=cargo.Name +local gname=cargo:GetName() local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) -self:T("Looking at "..gname.." in the helo - type = "..type) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then if gcargo and gcargo:GetStock0()>0 then -self:T("Adding "..gname.." in the helo - type = "..type) +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 end @@ -76892,6 +80428,47 @@ end end return Troopstable end +function CTLD:AddStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock+number +end +break +end +end +return self +end +function CTLD:SetStockUnits(Name,Number) +local name=Name or"none" +local number=Number +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +if number==nil or number==-1 then +_unit.Stock=-1 +else +_unit.Stock=number +end +break +end +end +return self +end +function CTLD:GetStockUnits() +local Stock={} +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +Stock[_unit.Name]=_unit.Stock or-1 +end +return Stock +end function CTLD:AddStockTroops(Name,Number) local name=Name or"none" local number=Number or 1 @@ -76899,6 +80476,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76911,6 +80489,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76923,6 +80502,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76935,6 +80515,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76947,6 +80528,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76959,6 +80541,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -77009,6 +80592,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -77020,6 +80604,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -77031,6 +80616,27 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() +end +end +return self +end +function CTLD:RemoveStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock-number +if _unit.Stock<0 then +_unit.Stock=0 +end +end +break end end return self @@ -77480,11 +81086,44 @@ 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) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +for _,_cargo in pairs(Cargotable)do +local Vehicle=_cargo.Positionable +if Vehicle then +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 +end +end +return self +end +function CTLD:OnAfterGetCrates(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:OnAfterRemoveCratesNearby(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) return self end function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) @@ -77512,9 +81151,13 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -77761,11 +81404,22 @@ 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) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +if self.C130GetUnits then +for _,_unit in pairs(self.C130GetUnits)do +if _unit.Name==cargoname then +if type(_unit.Stock)=="number"and _unit.Stock~=-1 then +_unit.Stock0=_unit.Stock0 or _unit.Stock +_unit.Stock=math.max(0,(_unit.Stock or 0)-1) +end +break +end +end +end 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,timestamp) end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +elseif self.loadSavedCrates and(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic=nil if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then @@ -77777,8 +81431,12 @@ injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) 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) injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) -local map=cargotype:GetStaticResourceMap() -injectstatic:SetStaticResourceMap(map) +local unittemplate=_DATABASE:GetStaticUnitTemplate(cargoname) +local ResourceMap=nil +if unittemplate and unittemplate.resourcePayload then +ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) +end +injectstatic:SetStaticResourceMap(ResourceMap) end if injectstatic then self:InjectStatics(dropzone,injectstatic,false,true) @@ -78247,6 +81905,8 @@ CreateRadioBeacons=true, UserSetGroup=nil, AllowIRStrobe=false, IRStrobeRuntime=300, +FARPRescueDistance=500, +EnableMenuSmokeMASH=true, } CSAR.AircraftType={} CSAR.AircraftType["SA342Mistral"]=2 @@ -78260,13 +81920,14 @@ CSAR.AircraftType["Mi-24P"]=8 CSAR.AircraftType["Mi-24V"]=8 CSAR.AircraftType["Bell-47"]=2 CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 CSAR.AircraftType["AH-64D_BLK_II"]=2 CSAR.AircraftType["Bronco-OV-10A"]=2 CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.35" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -78346,12 +82007,13 @@ self.loadtimemax=135 self.radioSound="beacon.ogg" self.beaconRefresher=29 self.allowFARPRescue=true -self.FARPRescueDistance=1000 +self.FARPRescueDistance=500 self.max_units=6 self.useprefix=true self.csarPrefix={"helicargo","MEDEVAC"} self.template=Template or"generic" self.mashprefix={"MASH"} +self.EnableMenuSmokeMASH=true self.autosmoke=false self.autosmokedistance=2000 self.limitmaxdownedpilots=true @@ -78529,6 +82191,9 @@ BeaconName=_unitName..math.random(1,10000) else BeaconName="Ghost-1-1"..math.random(1,10000) end +if _playerName==nil or _playerName==""then +_playerName="AI MIA" +end if(_freq and _freq~=0)then self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) end @@ -78712,11 +82377,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78771,7 +82436,7 @@ end self.takenOff[_event.IniUnitName]=nil local _place=_event.Place if _place==nil then -self:T(self.lid.." Landing Place Nil") +self:T(self.lid.." Landing Place nil") return self end if self.inTransitGroups[_event.IniUnitName]==nil then @@ -78779,7 +82444,10 @@ 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) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -78796,7 +82464,7 @@ 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:T({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") Unit.destroy(_event.initiator) end @@ -79134,7 +82802,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -79152,7 +82820,13 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_distself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end end for _,_mashes in pairs(MashSets)do for _,_mashUnit in pairs(_mashes or{})do @@ -79472,11 +83186,12 @@ _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance,MashName +return _shortestDistance,MashName,Coordinate else return-1 end @@ -79531,6 +83246,9 @@ local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -79545,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -79616,7 +83335,7 @@ local name=_radioUnit:GetName() local Frequency=_freq local Sound="l10n/DEFAULT/"..self.radioSound local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end @@ -79926,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -79935,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -80005,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -80081,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -81010,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -81351,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -81465,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -81743,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -81766,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -81813,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -82322,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -82434,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -82442,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -82476,6 +86216,20 @@ mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.mi mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -82611,33 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) elseif MissionType==AUFTRAG.Type.STRAFING then -mission=self:NewSTRAFING(Target,Altitude) +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -82698,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -83261,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -83465,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -83711,12 +87476,18 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 -if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) return false end return true @@ -83981,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -84523,6 +88294,15 @@ param.action="Wedge" param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 +DCStask.params=param +table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then local DCStask={} DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY @@ -84783,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.72", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -85400,6 +89180,11 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end function AWACS:SetMaxMissionRange(NM) self.MaxMissionRange=NM or 125 return self @@ -85659,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -85672,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -87150,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -87183,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -87208,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -87300,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -87843,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -88486,24 +92324,36 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) @@ -89518,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -89547,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.1" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -90577,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -91014,8 +94925,11 @@ end end return self end -function COHORT:RemoveAssets(N) +function COHORT:RemoveAssets(N,Delay) self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else N=N or 1 local n=0 for i=#self.assets,1,-1 do @@ -91023,6 +94937,7 @@ local asset=self.assets[i] self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) if not(asset.requested or asset.spawned or asset.isReserved)then self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) table.remove(self.assets,i) n=n+1 else @@ -91033,6 +94948,7 @@ break end end self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end return self end function COHORT:GetName() @@ -96967,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -97191,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -97198,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -97251,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -97258,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -97290,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -97456,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -97497,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -97711,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -98560,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98615,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -99106,7 +103124,7 @@ text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsR for _,mtype in pairs(AUFTRAG.Type)do local n=self:CountMissionsInQueue(mtype) if n>0 then -local N=self:CountMissionsInQueue(mtype) +local N=self:CountMissionsInQueue(mtype,true) text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) end end @@ -99201,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -99963,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.3" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -100330,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -100554,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -100562,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -100578,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -100596,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -102626,6 +106648,12 @@ end function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +function OPSGROUP:IsOutOfA2GAmmo() +if(self.outofMissilesAG and self.outofBombs and self.outofGuns)or self.outofAmmo then +return true +end +return false +end function OPSGROUP:IsLasing() return self.spot.On end @@ -103405,14 +107433,32 @@ end function OPSGROUP:_UpdateTask(Task,Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +if Mission.type==AUFTRAG.Type.RESCUEHELO then +self:T("**********") +self:T("** RESCUEHELO USED") +self:T("**********") +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +local helogroupname=self:GetGroup():GetName() +Task.formation=RESCUEHELO:New(followUnit,helogroupname) +Task.formation:SetRespawnOnOff(false) +Task.formation.respawninair=false +Task.formation:SetTakeoffCold() +Task.formation:SetHomeBase(followUnit) +Task.formation.helo=self:GetGroup() +Task.formation:Start() +if self:IsFlightgroup()then +self:SetDespawnAfterLanding() +end +else 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=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION) 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() +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then local zone=Task.dcstask.params.zone local surfacetypes=nil @@ -103496,6 +107542,15 @@ end if target then self:EngageTarget(target,speed,Task.dcstask.params.formation) end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT 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=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.altitude) +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then if self.isFlightgroup then self:T("We are Special Auftrag Patrol Race Track, starting now ...") @@ -103555,7 +107610,12 @@ 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())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -103603,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -103679,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -104059,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -104339,10 +108399,12 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then @@ -104922,7 +108984,7 @@ 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:T(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() @@ -105069,7 +109131,6 @@ function OPSGROUP:_Spawn(Delay,Template) if Delay and Delay>0 then self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) else -self:T2({Template=Template}) if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then UTILS.ValidateAndRepositionGroundUnits(Template.units) end @@ -105137,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -105195,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -106710,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -107478,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -107843,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -107858,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -107878,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -107908,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -107953,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -110002,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -110129,6 +114211,13 @@ end end) return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -110165,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110562,10 +114661,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -110608,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -110668,6 +114770,12 @@ Scoring=nil, MenuNoTask=nil, InformationMenu=false, TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -110696,6 +114804,12 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.CAP]=100, [AUFTRAG.Type.CAPTUREZONE]=100, } +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, +} PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, AAA=GROUP.Attribute.GROUND_AAA, @@ -110724,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -110808,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -110871,7 +114991,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -110942,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -111297,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -111849,17 +115982,24 @@ end PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end +self:__TaskAdded(10,PlayerTask,Silent) else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end return self end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) self:T({Force,Group,Client}) self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end local force=false if type(Force)=="boolean"then force=Force @@ -111944,7 +116084,7 @@ 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 +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) @@ -111987,8 +116127,9 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -112004,7 +116145,12 @@ 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) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -112106,8 +116252,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -112160,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -112169,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -112577,6 +116738,49 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -112708,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -116174,6 +120524,33 @@ self.ConflictZoneSet:AddZone(Zone) self.GoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) self:T("_TryAssignIntercept for size "..WingSize or 1) local assigned=false @@ -116298,6 +120675,8 @@ end if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then success=false end +else +success=true end return success end, @@ -116306,6 +120685,13 @@ nogozoneset, conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -116331,6 +120717,12 @@ BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -116403,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -116413,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -116446,6 +120839,477 @@ _wing:Stop() end return self end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) +return self +end AI_BALANCER={ ClassName="AI_BALANCER", PatrolZones={}, @@ -132120,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist0 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" +text=text..tostring(UTILS.PrintTableToLog(v,indent+1),noprint).."\n" if not noprint then env.info(string.rep(" ",indent).."},") end @@ -1782,7 +2520,7 @@ function UTILS.SecondsToClock(seconds,short) if seconds==nil then return nil end -local seconds=tonumber(seconds) +local seconds=tonumber(seconds)or 0 local _seconds=seconds%(60*60*24) if seconds<0 then return nil @@ -1963,7 +2701,7 @@ local delta=UTILS.VecAngle(v1,v2) return math.abs(delta) end function UTILS.HdgTo(a,b) -local dz=b.z-a.z +local dz=(b.z or b.y)-(a.z or a.y) local dx=b.x-a.x local heading=math.deg(math.atan2(dz,dx)) if heading<0 then @@ -2073,6 +2811,12 @@ elseif map==DCSMAP.Sinai then declination=4.8 elseif map==DCSMAP.Kola then declination=15 +elseif map==DCSMAP.Afghanistan then +declination=3 +elseif map==DCSMAP.Iraq then +declination=4.4 +elseif map==DCSMAP.GermanyCW then +declination=0.1 else declination=0 end @@ -2146,6 +2890,11 @@ end end function UTILS.GetReportingName(Typename) local typename=string.lower(Typename) +if string.find(typename,"ka-50",1,true)then +return"Shark" +elseif string.find(typename,"a-50",1,true)then +return"Mainstay" +end for name,value in pairs(ENUMS.ReportingName.NATO)do local svalue=string.lower(value) if string.find(typename,svalue,1,true)then @@ -2210,6 +2959,21 @@ if value==Callsign then return name end end +for name,value in pairs(CALLSIGN.AH64)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.Kiowa)do +if value==Callsign then +return name +end +end +for name,value in pairs(CALLSIGN.Intruder)do +if value==Callsign then +return name +end +end return"Ghostrider" end function UTILS.GMTToLocalTimeDifference() @@ -2232,6 +2996,14 @@ elseif theatre==DCSMAP.Falklands then return-3 elseif theatre==DCSMAP.Sinai then return 2 +elseif theatre==DCSMAP.Kola then +return 3 +elseif theatre==DCSMAP.Afghanistan then +return 4.5 +elseif theatre==DCSMAP.Iraq then +return 3.0 +elseif theatre==DCSMAP.GermanyCW then +return 1.0 else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0",tostring(theatre))) return 0 @@ -2380,6 +3152,18 @@ 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 type_name=="C-130J-30"and(unit:getDrawArgumentValue(86)==1)then +BASE:T(unit_name.." rear doors are open") +return true +end +if type_name=="C-130J-30"and(unit:getDrawArgumentValue(87)==1)then +BASE:T(unit_name.." Side door(s) are open") +return true +end +if type_name=="C-130J-30"and(unit:getDrawArgumentValue(88)==1)then +BASE:T(unit_name.." Paratroop door(s) are open") +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 @@ -2404,6 +3188,14 @@ if type_name=="UH-60L"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -2416,7 +3208,17 @@ if type_name=="MH-60R"and(unit:getDrawArgumentValue(403)>0 or unit:getDrawArgume BASE:T(unit_name.." cargo door is open") return true end -return false +if type_name=="OH58D"then +BASE:T(unit_name.." front door(s) are open") +return true +end +if type_name=="CH-47Fbl1"and(unit:getDrawArgumentValue(86)>0.5)then +BASE:T(unit_name.." rear cargo door is open") +return true +end +local UnitDescriptor=unit:getDesc() +local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT) +return IsGroundResult end return nil end @@ -2434,14 +3236,16 @@ 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, +214,243,264,273,274,288,291.5,295,297.5, +300.5,304,305,307,309.5,310,311,312,312.5,316,317, +320,323,324,325,326,328,329,330,332,335,336,337, +340,342,343,346,348,351,352,353,358, +360,363,364,365,368,372.5,373,374, +380,381,384,385,387,389,391,395,396,399, +403,404,410,412,414,418,420,423, +430,432,435,440,445, +450,455,462,470,485,490, +507,515,520,525,528,540,550,560,563,570,577,580,595, 602,625,641,662,670,680,682,690, 705,720,722,730,735,740,745,750,770,795, 822,830,862,866, @@ -3036,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3542,6 +4356,874 @@ end Text="MGRS;"..Text return Text end +function UTILS.ReadCSV(filename) +if not UTILS.FileExists(filename)then +env.error("File does not exist") +return nil +end +local function _loadfile(filename) +local f=io.open(filename,"rb") +if f then +local data=f:read("*all") +f:close() +return data +else +BASE:E(string.format("WARNING: Could read data from file %s!",tostring(filename))) +return nil +end +end +local data=_loadfile(filename) +local lines=UTILS.Split(data,"\n") +for _,line in pairs(lines)do +line=string.gsub(line,"[\n\r]","") +end +local sep=";" +local columns=UTILS.Split(lines[1],sep) +table.remove(lines,1) +local csvdata={} +for i,line in pairs(lines)do +line=string.gsub(line,"[\n\r]","") +local row={} +for j,value in pairs(UTILS.Split(line,sep))do +local key=string.gsub(columns[j],"[\n\r]","") +row[key]=value +end +table.insert(csvdata,row) +end +return csvdata +end +function UTILS.LCGRandomSeed(seed) +UTILS.lcg={ +seed=seed or math.random(1,2^32-1), +a=1664525, +c=1013904223, +m=2^32 +} +end +function UTILS.LCGRandom() +if UTILS.lcg==nil then +UTILS.LCGRandomSeed() +end +UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m +return UTILS.lcg.seed/UTILS.lcg.m +end +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end +local farplocation=Coordinate +local farptype=FARPType or ENUMS.FARPType.FARP +local Coalition=Coalition or coalition.side.BLUE +local callsign=CallSign or CALLSIGN.FARP.Berlin +local freq=Frequency or 127.5 +local mod=Modulation or radio.modulation.AM +local radius=SpawnRadius or 100 +if radius<0 or radius>150 then radius=100 end +local liquids=Liquids or 10 +liquids=liquids*1000 +local equip=Equipment or 10 +local airframes=Airframes or 10 +local statictypes=ENUMS.FARPObjectTypeNamesAndShape[farptype]or{TypeName="FARP",ShapeName="FARPS"} +local STypeName=statictypes.TypeName +local SShapeName=statictypes.ShapeName +local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) +local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else +local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) +newfarp:InitShape(SShapeName) +newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) +local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) +table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) +local FARPStaticObjectsNato={ +["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, +["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, +["TENT"]={TypeName="FARP Tent",ShapeName="PalatkaB",Category="Fortifications"}, +["WINDSOCK"]={TypeName="Windsock",ShapeName="H-Windsock_RW",Category="Fortifications"}, +} +local farpobcount=0 +for _name,_object in pairs(FARPStaticObjectsNato)do +local objloc=farplocation:Translate(radius,farpobcount*30) +local heading=objloc:HeadingTo(farplocation) +local newobject=SPAWNSTATIC:NewFromType(_object.TypeName,_object.Category,Country) +newobject:InitShape(_object.ShapeName) +newobject:InitHeading(heading) +newobject:SpawnFromCoordinate(objloc,farpobcount*30,_name.." - "..Name) +table.insert(ReturnObjects,newobject) +farpobcount=farpobcount+1 +end +if VehicleTemplate and type(VehicleTemplate)=="string"then +local vcoordinate=farplocation:Translate(radius,farpobcount*30) +local heading=vcoordinate:HeadingTo(farplocation) +local vehicles=SPAWN:NewWithAlias(VehicleTemplate,"FARP Vehicles - "..Name) +vehicles:InitGroupHeading(heading) +vehicles:InitCountry(Country) +vehicles:InitCoalition(Coalition) +vehicles:InitDelayOff() +local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) +table.insert(ReturnObjects,spawnedvehicle) +end +local ADFName +if ADF and type(ADF)=="number"then +local ADFFreq=ADF*1000 +local Sound="l10n/DEFAULT/beacon.ogg" +local vec3=farplocation:GetVec3() +ADFName=Name.." ADF "..tostring(ADF).."KHz" +trigger.action.radioTransmission(Sound,vec3,0,true,ADFFreq,250,ADFName) +end +local MarkerID=nil +if F10Text then +local Color={0,0,1} +if Coalition==coalition.side.RED then +Color={1,0,0} +elseif Coalition==coalition.side.NEUTRAL then +Color={0,1,0} +end +local Alpha=0.75 +local coordinate=Coordinate:Translate(600,0) +MarkerID=coordinate:TextToAll(F10Text,Coalition,Color,1,{1,1,1},Alpha,14,true) +end +return ReturnObjects,ADFName,MarkerID +end +function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,DeployHelo,MASHRadio,MASHRadioModulation,MASHCallsign,Templates) +local MASHTemplates={ +[1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,}, +[2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,}, +[3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,}, +[4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id="us army dark green",}, +[5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,}, +[6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,}, +[7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,}, +[8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,}, +[9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,}, +[10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,}, +[11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,}, +[12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,}, +[13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,}, +[14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,}, +[15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,}, +[16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,}, +[17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,}, +[18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,}, +} +if Templates then MASHTemplates=Templates end +local name=Name or"Florence Nightingale" +local positionVec2 +local positionVec3 +local ReturnStatics={} +local CountryID=Country or country.id.USA +local livery="us army dark green" +local MASHRadio=MASHRadio or 127.5 +local MASHRadioModulation=MASHRadioModulation or radio.modulation.AM +local MASHCallsign=MASHCallsign or CALLSIGN.FARP.Berlin +if type(Coordinate)=="table"then +if Coordinate:IsInstanceOf("COORDINATE")or Coordinate:IsInstanceOf("ZONE_BASE")then +positionVec2=Coordinate:GetVec2() +positionVec3=Coordinate:GetVec3() +end +else +BASE:E("Spawn MASH - no ZONE or COORDINATE handed!") +return +end +local BaseX=positionVec2.x +local BaseY=positionVec2.y +for id,object in pairs(MASHTemplates)do +local NewName=string.format("%s#%3d",name,id) +local vec2={x=BaseX+object.x,y=BaseY+object.y} +local Coordinate=COORDINATE:NewFromVec2(vec2) +local static=SPAWNSTATIC:NewFromType(object.type,object.category,CountryID) +if object.shape_name and object.shape_name~="none"then +static:InitShape(object.shape_name) +end +if object.category=="Helicopters"and DeployHelo==true then +if object.livery_id~=nil then +livery=object.livery_id +end +static:InitLivery(livery) +local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName) +table.insert(ReturnStatics,newstatic) +elseif object.category=="Heliports"then +static:InitFARP(MASHCallsign,MASHRadio,MASHRadioModulation,false,false) +local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName) +table.insert(ReturnStatics,newstatic) +elseif object.category~="Helicopters"and object.category~="Heliports"then +local newstatic=static:SpawnFromCoordinate(Coordinate,object.heading,NewName) +table.insert(ReturnStatics,newstatic) +end +end +local ADFName +if ADF and type(ADF)=="number"then +local ADFFreq=ADF*1000 +local Sound="l10n/DEFAULT/beacon.ogg" +ADFName=Name.." ADF "..tostring(ADF).."KHz" +trigger.action.radioTransmission(Sound,positionVec3,0,true,ADFFreq,250,ADFName) +end +return ReturnStatics,ADFName +end +function UTILS.Vec2toVec3(vec,y) +if not vec.z then +if vec.alt and not y then +y=vec.alt +elseif not y then +y=0 +end +return{x=vec.x,y=y,z=vec.y} +else +return{x=vec.x,y=vec.y,z=vec.z} +end +end +function UTILS.GetNorthCorrection(gPoint) +local point=UTILS.DeepCopy(gPoint) +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 UTILS.GetDHMS(timeInSec) +if timeInSec and type(timeInSec)=='number'then +local tbl={d=0,h=0,m=0,s=0} +if timeInSec>86400 then +while timeInSec>86400 do +tbl.d=tbl.d+1 +timeInSec=timeInSec-86400 +end +end +if timeInSec>3600 then +while timeInSec>3600 do +tbl.h=tbl.h+1 +timeInSec=timeInSec-3600 +end +end +if timeInSec>60 then +while timeInSec>60 do +tbl.m=tbl.m+1 +timeInSec=timeInSec-60 +end +end +tbl.s=timeInSec +return tbl +else +BASE:E("No number handed!") +return +end +end +function UTILS.GetDirectionRadians(vec,point) +local dir=math.atan2(vec.z,vec.x) +if point then +dir=dir+UTILS.GetNorthCorrection(point) +end +if dir<0 then +dir=dir+2*math.pi +end +return dir +end +function UTILS.IsPointInPolygon(point,poly,maxalt) +point=UTILS.Vec2toVec3(point) +local px=point.x +local pz=point.z +local cn=0 +local newpoly=UTILS.DeepCopy(poly) +if not maxalt or(point.y<=maxalt)then +local polysize=#newpoly +newpoly[#newpoly+1]=newpoly[1] +newpoly[1]=UTILS.Vec2toVec3(newpoly[1]) +for k=1,polysize do +newpoly[k+1]=UTILS.Vec2toVec3(newpoly[k+1]) +if((newpoly[k].z<=pz)and(newpoly[k+1].z>pz))or((newpoly[k].z>pz)and(newpoly[k+1].z<=pz))then +local vt=(pz-newpoly[k].z)/(newpoly[k+1].z-newpoly[k].z) +if(px5000 then value=5000 end +return world.weather.setFogThickness(value) +end +function UTILS.Weather.RemoveFog() +return world.weather.setFogThickness(0) +end +function UTILS.Weather.GetFogVisibilityDistanceMax() +return world.weather.getFogVisibilityDistance() +end +function UTILS.Weather.SetFogVisibilityDistance(Thickness) +local value=Thickness +if value<100 then value=100 +elseif value>100000 then value=100000 end +return world.weather.setFogVisibilityDistance(value) +end +function UTILS.Weather.SetFogAnimation(AnimationKeys) +return world.weather.setFogAnimation(AnimationKeys) +end +function UTILS.Weather.StopFogAnimation() +return world.weather.setFogAnimation({}) +end +function UTILS.GetEnvZone(name) +for _,v in ipairs(env.mission.triggers.zones)do +if v.name==name then +return v +end +end +end +function UTILS.DoStringIn(State,DoString) +return net.dostring_in(State,DoString) +end +function UTILS.ShowPictureToAll(FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToCoalition(Coalition,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +local coalName=string.lower(UTILS.GetCoalitionName(Coalition)) +net.dostring_in("mission",string.format("a_out_picture_s(\"%s\", \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",coalName,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToCountry(Country,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_c(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Country,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToGroup(Group,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_g(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Group:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToUnit(Unit,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_u(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Unit:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.LoadMission(FileName) +net.dostring_in("mission",string.format("a_load_mission(\"%s\")",FileName)) +end +function UTILS.SetMissionBriefing(Coalition,Text,Picture) +Text=Text or"" +Text=Text:gsub("\n","\\n") +Picture=Picture or"" +local coalName=string.lower(UTILS.GetCoalitionName(Coalition)) +net.dostring_in("mission",string.format("a_set_briefing(\"%s\", \"%s\", \"%s\")",coalName,Picture,Text)) +end +function UTILS.ShowHelperGate(pos,heading) +net.dostring_in("mission",string.format("a_show_helper_gate(%s, %s, %s, %f)",pos.x,pos.y,pos.z,math.rad(heading))) +end +function UTILS.ShowHelperGateForUnit(Unit,Flag) +net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")",Unit:GetID(),Flag)) +end +function UTILS.SetCarrierIlluminationMode(UnitID,Mode) +net.dostring_in("mission",string.format("a_set_carrier_illumination_mode(%d, %d)",UnitID,Mode)) +end +function UTILS.ShellZone(name,power,count) +local z=UTILS.GetEnvZone(name) +if z then +net.dostring_in("mission",string.format("a_shelling_zone(%d, %d, %d)",z.zoneId,power,count)) +end +end +function UTILS.RemoveObjects(name,type) +local z=UTILS.GetEnvZone(name) +if z then +net.dostring_in("mission",string.format("a_remove_scene_objects(%d, %d)",z.zoneId,type)) +end +end +function UTILS.DestroyScenery(name,level) +local z=UTILS.GetEnvZone(name) +if z then +net.dostring_in("mission",string.format("a_scenery_destruction_zone(%d, %d)",z.zoneId,level)) +end +end +function UTILS.GetSimpleZones(Vec3,SearchRadius,PosRadius,NumPositions) +return Disposition.getSimpleZones(Vec3,SearchRadius,PosRadius,NumPositions) +end +function UTILS.GetClearZonePositions(Zone,PosRadius,NumPositions) +local radius=PosRadius or math.min(Zone:GetRadius()/10,200) +local clearPositions=UTILS.GetSimpleZones(Zone:GetVec3(),Zone:GetRadius(),radius,NumPositions or 50) +if clearPositions and#clearPositions>0 then +local validZones={} +for _,vec2 in pairs(clearPositions)do +if Zone:IsVec2InZone(vec2)then +table.insert(validZones,vec2) +end +end +if#validZones>0 then +return validZones,radius +end +end +return nil +end +function UTILS.GetRandomClearZoneCoordinate(Zone,PosRadius,NumPositions) +local clearPositions=UTILS.GetClearZonePositions(Zone,PosRadius,NumPositions) +if clearPositions and#clearPositions>0 then +local randomPosition,radius=clearPositions[math.random(1,#clearPositions)] +return COORDINATE:NewFromVec2(randomPosition),radius +end +return nil +end +function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) +local r=Radius +local cx=Vec1.x or 1 +local cy=Vec1.y or 1 +local px=Vec2.x or 1 +local py=Vec2.y or 1 +local dx=px-cx +local dy=py-cy +local dist=math.sqrt(dx*dx+dy*dy) +if dist==0 then +return{x=cx+r,y=cy} +end +local norm_dx=dx/dist +local norm_dy=dy/dist +local qx=cx+r*norm_dx +local qy=cy+r*norm_dy +local shift_factor=1 +qx=qx+shift_factor*norm_dx +qy=qy+shift_factor*norm_dy +return{x=qx,y=qy} +end +function UTILS.ValidateAndRepositionGroundUnits(Positions,Anchor,MaxRadius,Spacing) +local units=Positions +Anchor=Anchor or UTILS.GetCenterPoint(units) +local gPos={x=Anchor.x,y=Anchor.z or Anchor.y} +local maxRadius=0 +local unitCount=0 +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local dist=UTILS.VecDist2D(pos,gPos) +if dist>maxRadius then +maxRadius=dist +end +unitCount=unitCount+1 +end +maxRadius=MaxRadius or math.max(maxRadius*2,10) +local spacing=Spacing or math.max(maxRadius*0.05,5) +if unitCount>0 and maxRadius>5 then +local spots=UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos),maxRadius,spacing,1000) +if spots and#spots>0 then +local validSpots={} +for _,spot in pairs(spots)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +table.insert(validSpots,spot) +end +end +spots=validSpots +end +local step=spacing +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local isOnLand=land.getSurfaceType(pos)==land.SurfaceType.LAND +local isValid=false +if spots and#spots>0 then +local si=1 +local sid=0 +local closestDist=100000000 +local closestSpot +for _,spot in pairs(spots)do +local dist=UTILS.VecDist2D(pos,spot) +if dist=spacing then +pos=closestSpot +end +isValid=true +table.remove(spots,sid) +end +end +if not isValid and not isOnLand then +local h=UTILS.HdgTo(pos,gPos) +local retries=0 +while not isValid and retries<500 do +local dist=UTILS.VecDist2D(pos,gPos) +pos=UTILS.Vec2Translate(pos,step,h) +local skip=false +for _,unit2 in pairs(units)do +if unit~=unit2 then +local pos2={x=unit2.x,y=unit2.z or unit2.y} +local dist2=UTILS.VecDist2D(pos,pos2) +if dist2<12 then +isValid=false +skip=true +break +end +end +end +if not skip and dist>step and land.getSurfaceType(pos)==land.SurfaceType.LAND then +isValid=true +break +elseif dist<=step then +break +end +retries=retries+1 +end +end +if isValid then +unit.x=pos.x +if unit.z then +unit.z=pos.y +else +unit.y=pos.y +end +end +end +end +end +function UTILS.ValidateAndRepositionStatic(Country,Category,Type,Position,ShapeName,MaxRadius) +local coord=COORDINATE:NewFromVec2(Position) +local st=SPAWNSTATIC:NewFromType(Type,Category,Country) +if ShapeName then +st:InitShape(ShapeName) +end +local sName="s-"..timer.getTime().."-"..math.random(1,10000) +local tempStatic=st:SpawnFromCoordinate(coord,0,sName) +if tempStatic then +local sRadius=tempStatic:GetBoundingRadius(2)or 3 +tempStatic:Destroy() +sRadius=sRadius*0.5 +MaxRadius=MaxRadius or math.max(sRadius*10,100) +local positions=UTILS.GetSimpleZones(coord:GetVec3(),MaxRadius,sRadius,20) +if positions and#positions>0 then +local closestSpot +local closestDist=math.huge +for _,spot in pairs(positions)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +local dist=UTILS.VecDist2D(Position,spot) +if dist=sRadius then +return closestSpot +else +return Position +end +end +end +end +return nil +end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -3815,500 +5497,6 @@ 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", @@ -4833,7 +6021,7 @@ message.volume=Volume self:SendTable(message) return self end -local _TraceOnOff=true +local _TraceOnOff=false local _TraceLevel=1 local _TraceAll=false local _TraceClass={} @@ -4846,15 +6034,12 @@ Events={}, States={}, Debug=debug, Scheduler=nil, +Properties={}, } BASE.__={} BASE._={ Schedules={}, } -FORMATION={ -Cone="Cone", -Vee="Vee", -} function BASE:New() local self=UTILS.DeepCopy(self) _ClassID=_ClassID+1 @@ -5027,6 +6212,46 @@ initiator=PlayerUnit:GetDCSObject() } world.onEvent(Event) end +function BASE:CreateEventNewDynamicCargo(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.NewDynamicCargo, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function BASE:CreateEventDynamicCargoLoaded(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoLoaded, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function BASE:CreateEventDynamicCargoUnloaded(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoUnloaded, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function BASE:CreateEventDynamicCargoRemoved(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoRemoved, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end function BASE:onEvent(event) if self then for EventID,EventObject in pairs(self.Events)do @@ -5106,6 +6331,17 @@ if self.States[ClassNameAndID]then self.States[ClassNameAndID][StateName]=nil end end +function BASE:SetProperty(Key,Value) +self.Properties=self.Properties or{} +self.Properties[Key]=Value +end +function BASE:GetProperty(Key) +self.Properties=self.Properties or{} +return self.Properties[Key] +end +function BASE:GetProperties() +return self.Properties +end function BASE:TraceOn() self:TraceOnOff(true) end @@ -5187,7 +6423,7 @@ end end end function BASE:F(Arguments) -if BASE.Debug and _TraceOnOff then +if BASE.Debug and _TraceOnOff==true then local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") local DebugInfoFrom=BASE.Debug.getinfo(3,"l") if _TraceLevel>=1 then @@ -5196,7 +6432,7 @@ end end end function BASE:F2(Arguments) -if BASE.Debug and _TraceOnOff then +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=2 then local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") local DebugInfoFrom=BASE.Debug.getinfo(3,"l") if _TraceLevel>=2 then @@ -5205,7 +6441,7 @@ end end end function BASE:F3(Arguments) -if BASE.Debug and _TraceOnOff then +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=3 then local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") local DebugInfoFrom=BASE.Debug.getinfo(3,"l") if _TraceLevel>=3 then @@ -5235,7 +6471,7 @@ end end end function BASE:T(Arguments) -if BASE.Debug and _TraceOnOff then +if BASE.Debug and _TraceOnOff==true then local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") local DebugInfoFrom=BASE.Debug.getinfo(3,"l") if _TraceLevel>=1 then @@ -5244,7 +6480,7 @@ end end end function BASE:T2(Arguments) -if BASE.Debug and _TraceOnOff then +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=2 then local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") local DebugInfoFrom=BASE.Debug.getinfo(3,"l") if _TraceLevel>=2 then @@ -5253,7 +6489,7 @@ end end end function BASE:T3(Arguments) -if BASE.Debug and _TraceOnOff then +if BASE.Debug and _TraceOnOff==true and _TraceLevel>=3 then local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") local DebugInfoFrom=BASE.Debug.getinfo(3,"l") if _TraceLevel>=3 then @@ -5276,7 +6512,7 @@ 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))) +env.info(string.format("%1s:%30s%05d(%s)","E",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments))) end end function BASE:I(Arguments) @@ -5294,7 +6530,7 @@ 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))) +env.info(string.format("%1s:%30s%05d(%s)","I",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments))) end end ASTAR={ @@ -6267,7 +7503,7 @@ 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) +env.info("Error in timer function: "..errmsg or"") if BASE.Debug~=nil then env.info(BASE.Debug.traceback()) end @@ -6356,7 +7592,7 @@ function SCHEDULEDISPATCHER:Stop(Scheduler,CallID) self:F2({Stop=CallID,Scheduler=Scheduler}) if CallID then local Schedule=self.Schedule[Scheduler][CallID] -if Schedule.ScheduleID then +if Schedule and 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 @@ -6396,6 +7632,10 @@ 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 +world.event.S_EVENT_NEW_DYNAMIC_CARGO=world.event.S_EVENT_MAX+1008 +world.event.S_EVENT_DYNAMIC_CARGO_LOADED=world.event.S_EVENT_MAX+1009 +world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED=world.event.S_EVENT_MAX+1010 +world.event.S_EVENT_DYNAMIC_CARGO_REMOVED=world.event.S_EVENT_MAX+1011 EVENTS={ Shot=world.event.S_EVENT_SHOT, Hit=world.event.S_EVENT_HIT, @@ -6453,14 +7693,22 @@ 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, +UnitTaskComplete=world.event.S_EVENT_UNIT_TASK_COMPLETE 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, +RunwayTakeoff=world.event.S_EVENT_RUNWAY_TAKEOFF or-1, +RunwayTouch=world.event.S_EVENT_RUNWAY_TOUCH or-1, +MacLMSRestart=world.event.S_EVENT_MAC_LMS_RESTART or-1, +SimulationFreeze=world.event.S_EVENT_SIMULATION_FREEZE or-1, +SimulationUnfreeze=world.event.S_EVENT_SIMULATION_UNFREEZE or-1, +HumanAircraftRepairStart=world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or-1, +HumanAircraftRepairFinish=world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or-1, +NewDynamicCargo=world.event.S_EVENT_NEW_DYNAMIC_CARGO or-1, +DynamicCargoLoaded=world.event.S_EVENT_DYNAMIC_CARGO_LOADED or-1, +DynamicCargoUnloaded=world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED or-1, +DynamicCargoRemoved=world.event.S_EVENT_DYNAMIC_CARGO_REMOVED or-1, } local _EVENTMETA={ [world.event.S_EVENT_SHOT]={ @@ -6774,24 +8022,12 @@ 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", @@ -6810,17 +8046,71 @@ Side="I", Event="OnEventMissionWinner", Text="S_EVENT_MISSION_WINNER" }, -[EVENTS.PostponedTakeoff]={ +[EVENTS.RunwayTakeoff]={ Order=1, Side="I", -Event="OnEventPostponedTakeoff", -Text="S_EVENT_POSTPONED_TAKEOFF" +Event="OnEventRunwayTakeoff", +Text="S_EVENT_RUNWAY_TAKEOFF" }, -[EVENTS.PostponedLand]={ +[EVENTS.RunwayTouch]={ Order=1, Side="I", -Event="OnEventPostponedLand", -Text="S_EVENT_POSTPONED_LAND" +Event="OnEventRunwayTouch", +Text="S_EVENT_RUNWAY_TOUCH" +}, +[EVENTS.MacLMSRestart]={ +Order=1, +Side="I", +Event="OnEventMacLMSRestart", +Text="S_EVENT_MAC_LMS_RESTART" +}, +[EVENTS.SimulationFreeze]={ +Order=1, +Side="I", +Event="OnEventSimulationFreeze", +Text="S_EVENT_SIMULATION_FREEZE" +}, +[EVENTS.SimulationUnfreeze]={ +Order=1, +Side="I", +Event="OnEventSimulationUnfreeze", +Text="S_EVENT_SIMULATION_UNFREEZE" +}, +[EVENTS.HumanAircraftRepairStart]={ +Order=1, +Side="I", +Event="OnEventHumanAircraftRepairStart", +Text="S_EVENT_HUMAN_AIRCRAFT_REPAIR_START" +}, +[EVENTS.HumanAircraftRepairFinish]={ +Order=1, +Side="I", +Event="OnEventHumanAircraftRepairFinish", +Text="S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH" +}, +[EVENTS.NewDynamicCargo]={ +Order=1, +Side="I", +Event="OnEventNewDynamicCargo", +Text="S_EVENT_NEW_DYNAMIC_CARGO" +}, +[EVENTS.DynamicCargoLoaded]={ +Order=1, +Side="I", +Event="OnEventDynamicCargoLoaded", +Text="S_EVENT_DYNAMIC_CARGO_LOADED" +}, +[EVENTS.DynamicCargoUnloaded]={ +Order=1, +Side="I", +Event="OnEventDynamicCargoUnloaded", +Text="S_EVENT_DYNAMIC_CARGO_UNLOADED" +}, +[EVENTS.DynamicCargoRemoved]={ +Order=1, +Side="I", +Event="OnEventDynamicCargoRemoved", +Text="S_EVENT_DYNAMIC_CARGO_REMOVED" }, } function EVENT:New() @@ -7016,6 +8306,46 @@ initiator=PlayerUnit:GetDCSObject() } world.onEvent(Event) end +function EVENT:CreateEventNewDynamicCargo(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.NewDynamicCargo, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function EVENT:CreateEventDynamicCargoLoaded(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoLoaded, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function EVENT:CreateEventDynamicCargoUnloaded(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoUnloaded, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end +function EVENT:CreateEventDynamicCargoRemoved(DynamicCargo) +self:F({DynamicCargo}) +local Event={ +id=EVENTS.DynamicCargoRemoved, +time=timer.getTime(), +dynamiccargo=DynamicCargo, +initiator=DynamicCargo:GetDCSObject(), +} +world.onEvent(Event) +end end function EVENT:onEvent(Event) local ErrorHandler=function(errmsg) @@ -7073,6 +8403,7 @@ if not Event.IniUnit then Event.IniUnit=CLIENT:FindByName(Event.IniDCSUnitName,'',true) end Event.IniDCSGroupName=Event.IniUnit and Event.IniUnit.GroupName or"" +Event.IniGroupName=Event.IniDCSGroupName if Event.IniDCSGroup and Event.IniDCSGroup:isExist()then Event.IniDCSGroupName=Event.IniDCSGroup:getName() Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName) @@ -7092,17 +8423,24 @@ elseif Event.IniObjectCategory==Object.Category.CARGO then Event.IniDCSUnit=Event.initiator Event.IniDCSUnitName=Event.IniDCSUnit:getName() Event.IniUnitName=Event.IniDCSUnitName +if string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+")then +Event.IniDynamicCargo=DYNAMICCARGO:FindByName(Event.IniUnitName) +Event.IniDynamicCargoName=Event.IniUnitName +Event.IniPlayerName=string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+") +else Event.IniUnit=CARGO:FindByName(Event.IniDCSUnitName) +end 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.IniDCSUnitName=(Event.IniDCSUnit and Event.IniDCSUnit.getName)and Event.IniDCSUnit:getName()or"Scenery no name "..math.random(1,20000) 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" +Event.IniCategory=(Event.IniDCSUnit and Event.IniDCSUnit.getDesc)and Event.IniDCSUnit:getDesc().category +Event.IniTypeName=(Event.initiator and Event.initiator.isExist +and Event.initiator:isExist()and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName)and Event.IniDCSUnit:getTypeName()or"SCENERY" elseif Event.IniObjectCategory==Object.Category.BASE then Event.IniDCSUnit=Event.initiator Event.IniDCSUnitName=Event.IniDCSUnit:getName() @@ -7143,7 +8481,7 @@ 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 +if Event.target.isExist and Event.target:isExist()and Event.id~=33 then Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() if Event.TgtDCSUnitName and Event.TgtDCSUnitName~=""then Event.TgtUnitName=Event.TgtDCSUnitName @@ -7172,31 +8510,35 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil +if Event.TgtDCSUnitName~=nil then 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 +end +if Event.weapon and type(Event.weapon)=="table"and Event.weapon.isExist and Event.weapon:isExist()then Event.Weapon=Event.weapon -Event.WeaponName=Event.Weapon:getTypeName() +Event.WeaponName=Event.weapon:isExist()and Event.weapon:getTypeName()or"Unknown Weapon" 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() +Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon.getCoalition and Event.Weapon:getCoalition() +Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon.getDesc and Event.Weapon:getDesc().category +Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon.getTypeName 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -7210,6 +8552,13 @@ if Event.cargo then Event.Cargo=Event.cargo Event.CargoName=Event.cargo.Name end +if Event.dynamiccargo then +Event.IniDynamicCargo=Event.dynamiccargo +Event.IniDynamicCargoName=Event.IniDynamicCargo.StaticName +if Event.IniDynamicCargo.Owner or Event.IniUnitName then +Event.IniPlayerName=Event.IniDynamicCargo.Owner or string.match(Event.IniUnitName or"None|00:00|PKG00","^(.+)|%d%d:%d%d|PKG%d+") +end +end if Event.zone then Event.Zone=Event.zone Event.ZoneName=Event.zone.ZoneName @@ -7632,8 +8981,7 @@ end function SETTINGS:SetPlayerMenu(PlayerUnit) if _SETTINGS.ShowPlayerMenu==true then local PlayerGroup=PlayerUnit:GetGroup() -local PlayerName=PlayerUnit:GetPlayerName() -local PlayerNames=PlayerGroup:GetPlayerNames() +local PlayerName=PlayerUnit:GetPlayerName()or"None" local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"') self.PlayerMenu=PlayerMenu self:T(string.format("Setting menu for player %s",tostring(PlayerName))) @@ -7949,13 +9297,14 @@ end function MENU_INDEX:HasGroupMenu(Group,Path) if Group and Group:IsAlive()then local MenuGroupName=Group:GetName() +if self.Group[MenuGroupName]and self.Group[MenuGroupName].Menus and self.Group[MenuGroupName].Menus[Path]then return self.Group[MenuGroupName].Menus[Path] end +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) @@ -8439,6 +9788,7 @@ return self end end function MENU_GROUP_DELAYED:Set() +if not self.GroupID then return end do if not self.MenuSet then missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath) @@ -8564,20 +9914,16 @@ Checktime=5, } function ZONE_BASE:New(ZoneName) local self=BASE:Inherit(self,FSM:New()) -self:F(ZoneName) self.ZoneName=ZoneName return self end function ZONE_BASE:GetName() -self:F2() return self.ZoneName end function ZONE_BASE:SetName(ZoneName) -self:F2() self.ZoneName=ZoneName end function ZONE_BASE:IsVec2InZone(Vec2) -self:F2(Vec2) return false end function ZONE_BASE:IsVec3InZone(Vec3) @@ -8602,29 +9948,22 @@ function ZONE_BASE:GetVec2() return nil end function ZONE_BASE:GetPointVec2() -self:F2(self.ZoneName) local Vec2=self:GetVec2() -local PointVec2=POINT_VEC2:NewFromVec2(Vec2) -self:T2({PointVec2}) +local PointVec2=COORDINATE:NewFromVec2(Vec2) return PointVec2 end function ZONE_BASE:GetVec3(Height) -self:F2(self.ZoneName) Height=Height or 0 local Vec2=self:GetVec2() local Vec3={x=Vec2.x,y=Height and Height or land.getHeight(self:GetVec2()),z=Vec2.y} -self:T2({Vec3}) return Vec3 end function ZONE_BASE:GetPointVec3(Height) -self:F2(self.ZoneName) local Vec3=self:GetVec3(Height) -local PointVec3=POINT_VEC3:NewFromVec3(Vec3) -self:T2({PointVec3}) +local PointVec3=COORDINATE:NewFromVec3(Vec3) return PointVec3 end function ZONE_BASE:GetCoordinate(Height) -self:F2(self.ZoneName) local Vec3=self:GetVec3(Height) if self.Coordinate then self.Coordinate.x=Vec3.x @@ -8666,7 +10005,6 @@ local surface=coord:GetSurfaceType() return surface end function ZONE_BASE:BoundZone() -self:F2() end function ZONE_BASE:SetDrawCoalition(Coalition) self.drawCoalition=Coalition or-1 @@ -8747,19 +10085,23 @@ 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:FindNearestCoordinateOnRadius(Outsidecoordinate) +local Vec1=self:GetVec2() +local Radius=self:GetRadius() +local Vec2=Outsidecoordinate:GetVec2() +local Point=UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) +local rc=COORDINATE:NewFromVec2(Point) +return rc +end function ZONE_BASE:GetZoneMaybe() -self:F2() local Randomization=math.random() if Randomization<=self.ZoneProbability then return self @@ -8776,10 +10118,13 @@ self:SetStartState("TriggerStopped") self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning") self:AddTransition("*","EnteredZone","*") self:AddTransition("*","LeftZone","*") +self:AddTransition("*","ZoneEmpty","*") +self:AddTransition("*","ObjectDead","*") self:AddTransition("*","TriggerRunCheck","*") self:AddTransition("*","TriggerStop","TriggerStopped") self:TriggerStart() self.checkobjects=Objects +self.ObjectsInZone=false if UTILS.IsInstanceOf(Objects,"SET_BASE")then self.objectset=Objects.Set else @@ -8789,19 +10134,28 @@ self:_TriggerCheck(true) self:__TriggerRunCheck(self.Checktime) return self end +function ZONE_BASE:SetPartlyInside(state) +self.PartlyInside=state or not(state==false) +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 not obj.TriggerInZone then +obj.TriggerInZone={} +obj.TriggerZoneDeadNotification=false +end if obj and obj:IsAlive()and self:IsCoordinateInZone(obj:GetCoordinate())then obj.TriggerInZone[self.ZoneName]=true +self.ObjectsInZone=true else obj.TriggerInZone[self.ZoneName]=false end end else +local objcount=0 for _,_object in pairs(objectset)do local obj=_object if obj and obj:IsAlive()then @@ -8811,18 +10165,41 @@ end if not obj.TriggerInZone[self.ZoneName]then obj.TriggerInZone[self.ZoneName]=false end -local inzone=self:IsCoordinateInZone(obj:GetCoordinate()) +local inzone +if self.PartlyInside and obj.ClassName=="GROUP"then +inzone=obj:IsAnyInZone(self) +else +inzone=self:IsCoordinateInZone(obj:GetCoordinate()) +end +if inzone and obj.TriggerInZone[self.ZoneName]then +objcount=objcount+1 +self.ObjectsInZone=true +obj.TriggerZoneDeadNotification=false +end if inzone and not obj.TriggerInZone[self.ZoneName]then self:__EnteredZone(0.5,obj) obj.TriggerInZone[self.ZoneName]=true +objcount=objcount+1 +self.ObjectsInZone=true +obj.TriggerZoneDeadNotification=false elseif(not inzone)and obj.TriggerInZone[self.ZoneName]then self:__LeftZone(0.5,obj) obj.TriggerInZone[self.ZoneName]=false else end +else +if not obj.TriggerZoneDeadNotification==true then +obj.TriggerInZone=nil +self:__ObjectDead(0.5,obj) +obj.TriggerZoneDeadNotification=true end end end +if objcount==0 and self.ObjectsInZone==true then +self.ObjectsInZone=false +self:__ZoneEmpty(0.5) +end +end return self end function ZONE_BASE:onafterTriggerRunCheck(From,Event,To) @@ -8843,7 +10220,6 @@ 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 @@ -8920,7 +10296,6 @@ 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 @@ -8932,12 +10307,11 @@ 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) +COORDINATE:New(Point.x,AddHeight,Point.y):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 @@ -8948,38 +10322,28 @@ 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) +COORDINATE:New(Point.x,AddHeight,Point.y):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) @@ -8988,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -8998,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9018,7 +10381,6 @@ 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 @@ -9027,7 +10389,6 @@ 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 @@ -9047,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else -local FoundStatic=STATIC:FindByName(UnitObject:getName()) +local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9149,12 +10521,11 @@ 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, +radius=ZoneRadius, } } local function EvaluateZone(ZoneDCSUnit) @@ -9164,7 +10535,6 @@ 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 @@ -9175,15 +10545,23 @@ 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:GetClearZonePositions(PosRadius,NumPositions) +return UTILS.GetClearZonePositions(self,PosRadius,NumPositions) +end +function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius,NumPositions) +return UTILS.GetRandomClearZoneCoordinate(self,PosRadius,NumPositions) +end function ZONE_RADIUS:GetRandomVec2(inner,outer,surfacetypes) local Vec2=self:GetVec2() local _inner=inner or 0 local _outer=outer or self:GetRadius() +math.random() +math.random() +math.random() if surfacetypes and type(surfacetypes)~="table"then surfacetypes={surfacetypes} end @@ -9218,21 +10596,15 @@ 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}) +local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer)) 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}) +local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer)) return PointVec3 end function ZONE_RADIUS:GetRandomCoordinate(inner,outer,surfacetypes) @@ -9319,7 +10691,6 @@ end end end T1=timer.getTime() -self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) if found then return rcoord else return nil end end ZONE={ @@ -9336,7 +10707,6 @@ env.error("ERROR: Zone "..ZoneName.." does not exist!") return nil end local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,{x=Zone.point.x,y=Zone.point.z},Zone.radius,true)) -self:F(ZoneName) self.Color={1,0,0,0.15} self.Zone=Zone return self @@ -9362,14 +10732,22 @@ self.rho=Offset.rho or 0.0 self.theta=(Offset.theta or 0.0)*math.pi/180.0 self.relative_to_unit=Offset.relative_to_unit or false end -self:F({ZoneName,ZoneUNIT:GetVec2(),Radius}) self.ZoneUNIT=ZoneUNIT self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() -self:F2(self.ZoneName) local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then local heading @@ -9391,11 +10769,9 @@ return ZoneVec2 else return self.LastVec2 end -self:T2({ZoneVec2}) return nil end function ZONE_UNIT:GetRandomVec2() -self:F(self.ZoneName) local RandomVec2={} local Vec2=self:GetVec2() if not Vec2 then @@ -9404,15 +10780,12 @@ end local angle=math.random()*math.pi*2; RandomVec2.x=Vec2.x+math.cos(angle)*math.random()*self:GetRadius(); RandomVec2.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius(); -self:T({RandomVec2}) return RandomVec2 end function ZONE_UNIT:GetVec3(Height) -self:F2(self.ZoneName) 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 ZONE_GROUP={ @@ -9420,14 +10793,12 @@ ClassName="ZONE_GROUP", } function ZONE_GROUP:New(ZoneName,ZoneGROUP,Radius) local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneGROUP:GetVec2(),Radius,true)) -self:F({ZoneName,ZoneGROUP:GetVec2(),Radius}) self._.ZoneGROUP=ZoneGROUP self._.ZoneVec2Cache=self._.ZoneGROUP:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end function ZONE_GROUP:GetVec2() -self:F(self.ZoneName) local ZoneVec2=nil if self._.ZoneGROUP:IsAlive()then ZoneVec2=self._.ZoneGROUP:GetVec2() @@ -9435,23 +10806,29 @@ self._.ZoneVec2Cache=ZoneVec2 else ZoneVec2=self._.ZoneVec2Cache end -self:T({ZoneVec2}) return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() -self:F(self.ZoneName) local Point={} local Vec2=self._.ZoneGROUP:GetVec2() local angle=math.random()*math.pi*2; Point.x=Vec2.x+math.cos(angle)*math.random()*self:GetRadius(); Point.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius(); -self:T({Point}) return Point end function ZONE_GROUP:GetRandomPointVec2(inner,outer) -self:F(self.ZoneName,inner,outer) -local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2()) -self:T3({PointVec2}) +local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2()) return PointVec2 end _ZONE_TRIANGLE={ @@ -9532,7 +10909,6 @@ 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 @@ -9648,7 +11024,6 @@ 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 @@ -9691,10 +11066,14 @@ 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:GetClearZonePositions(PosRadius,NumPositions) +return UTILS.GetClearZonePositions(self,PosRadius,NumPositions) +end +function ZONE_POLYGON_BASE:GetRandomClearZoneCoordinate(PosRadius,NumPositions) +return UTILS.GetRandomClearZoneCoordinate(self,PosRadius,NumPositions) +end function ZONE_POLYGON_BASE:BoundZone(UnBound) local i local j @@ -9702,7 +11081,6 @@ 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 @@ -9845,18 +11223,16 @@ 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) +COORDINATE:New(PointX,0,PointY):Smoke(SmokeColor) end j=i i=i+1 @@ -9864,19 +11240,17 @@ 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) +COORDINATE:New(PointX,AddHeight,PointY):Flare(FlareColor,Azimuth) end j=i i=i+1 @@ -9884,7 +11258,6 @@ end return self end function ZONE_POLYGON_BASE:IsVec2InZone(Vec2) -self:F2(Vec2) if not Vec2 then return false end local Next local Prev @@ -9892,26 +11265,25 @@ 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() +math.random() +math.random() +math.random() local weights={} for _,triangle in pairs(self._Triangles)do weights[triangle]=triangle.SurfaceArea/self.SurfaceArea @@ -9926,21 +11298,15 @@ end end end function ZONE_POLYGON_BASE:GetRandomPointVec2() -self:F2() -local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2()) -self:T2(PointVec2) +local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2()) return PointVec2 end function ZONE_POLYGON_BASE:GetRandomPointVec3() -self:F2() -local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2()) -self:T2(PointVec3) +local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2()) 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() @@ -9949,7 +11315,6 @@ 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 @@ -9963,7 +11328,6 @@ 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 @@ -9989,7 +11353,6 @@ else Limit=#self._.Polygon end while i<=#self._.Polygon do -self:T({i,j,self._.Polygon[i],self._.Polygon[j]}) if j~=Limit then local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y @@ -10010,13 +11373,11 @@ ClassName="ZONE_POLYGON", function ZONE_POLYGON:New(ZoneName,ZoneGroup) local GroupPoints=ZoneGroup:GetTaskRoute() local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,GroupPoints)) -self:F({ZoneName,ZoneGroup,self._.Polygon}) _EVENTDISPATCHER:CreateEventNewZone(self) return self end function ZONE_POLYGON:NewFromPointsArray(ZoneName,PointsArray) local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,PointsArray)) -self:F({ZoneName,self._.Polygon}) _EVENTDISPATCHER:CreateEventNewZone(self) return self end @@ -10024,7 +11385,6 @@ function ZONE_POLYGON:NewFromGroupName(GroupName) local ZoneGroup=GROUP:FindByName(GroupName) local GroupPoints=ZoneGroup:GetTaskRoute() local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(GroupName,GroupPoints)) -self:F({GroupName,ZoneGroup,self._.Polygon}) _EVENTDISPATCHER:CreateEventNewZone(self) return self end @@ -10035,10 +11395,18 @@ for _,object in pairs(layer["objects"])do if object["name"]==DrawingName then if(object["primitiveType"]=="Line"and object["closed"]==true)or(object["polygonMode"]=="free")then for _,point in UTILS.spairs(object["points"])do +local skip=false local p={x=object["mapX"]+point["x"], y=object["mapY"]+point["y"]} +for _,pt in pairs(points)do +if pt.x==p.x and pt.y==p.y then +skip=true +end +end +if not skip then table.add(points,p) end +end elseif object["polygonMode"]=="rect"then local angle=object["angle"] local half_width=object["width"]/2 @@ -10069,11 +11437,7 @@ self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} local vectors=self:GetBoundingSquare() -local minVec3={x=vectors.x1,y=0,z=vectors.y1} -local maxVec3={x=vectors.x2,y=0,z=vectors.y2} -local minmarkcoord=COORDINATE:NewFromVec3(minVec3) -local maxmarkcoord=COORDINATE:NewFromVec3(maxVec3) -local ZoneRadius=minmarkcoord:Get2DDistance(maxmarkcoord)/2 +local ZoneRadius=UTILS.VecDist2D({x=vectors.x1,y=vectors.y1},{x=vectors.x2,y=vectors.y2})/2 local CenterVec3=self:GetCoordinate():GetVec3() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -10083,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10103,16 +11466,14 @@ 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 and self:IsVec3InZone(ZoneObject:getPoint())then +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(SceneryName,ZoneObject) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) -self:T({SCENERY=self.ScanData.Scenery[SceneryType][SceneryName]}) end end return true @@ -10263,6 +11624,24 @@ function ZONE_ELASTIC:AddVertex2D(Vec2) table.insert(self.points,Vec2) return self end +function ZONE_ELASTIC:RemoveVertex2D(Vec2) +local found=false +local findex=0 +for _id,_vec2 in pairs(self.points)do +if _vec2.x==Vec2.x and _vec2.y==Vec2.y then +found=true +findex=_id +break +end +end +if found==true and findex>0 then +table.remove(self.points,findex) +end +return self +end +function ZONE_ELASTIC:RemoveVertex3D(Vec3) +return self:RemoveVertex2D({x=Vec3.x,y=Vec3.z}) +end function ZONE_ELASTIC:AddVertex3D(Vec3) table.insert(self.points,{x=Vec3.x,y=Vec3.z}) return self @@ -10272,7 +11651,6 @@ 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 @@ -10286,6 +11664,8 @@ end end end self._.Polygon=self:_ConvexHull(points) +self._Triangles=self:_Triangulate() +self.SurfaceArea=self:_CalculateSurfaceArea() if Draw~=false then if self.DrawID or Draw==true then self:UndrawZone() @@ -10427,10 +11807,10 @@ 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()) +return COORDINATE:NewFromVec2(self:GetRandomVec2()) end function ZONE_OVAL:GetRandomPointVec3() -return POINT_VEC3:NewFromVec3(self:GetRandomVec2()) +return COORDINATE:NewFromVec3(self:GetRandomVec2()) end function ZONE_OVAL:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType) Coalition=Coalition or self:GetDrawCoalition() @@ -10486,7 +11866,6 @@ 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() @@ -10494,13 +11873,10 @@ 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}) +local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2()) return PointVec2 end end @@ -10556,7 +11932,7 @@ 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) +COORDINATE:New(Point.x,AddHeight,Point.y):Smoke(SmokeColor) end return self end @@ -10572,7 +11948,7 @@ 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) +COORDINATE:New(Point.x,AddHeight,Point.y):Flare(FlareColor,Azimuth) end return self end @@ -10641,6 +12017,7 @@ PATHLINES={}, STORAGES={}, STNS={}, SADL={}, +DYNAMICCARGO={}, } local _DATABASECoalition= { @@ -10664,12 +12041,14 @@ 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.UnitLost,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:HandleEvent(EVENTS.DynamicCargoRemoved,self._EventOnDynamicCargoRemoved) self:_RegisterTemplates() self:_RegisterGroupsAndUnits() self:_RegisterClients() @@ -10681,22 +12060,24 @@ 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) +function DATABASE:AddUnit(DCSUnitName,force) +local DCSunitName=DCSUnitName +if type(DCSunitName)=="number"then DCSunitName=string.format("%d",DCSUnitName)end +if not self.UNITS[DCSunitName]or force==true then +self:T({"Add UNIT:",DCSunitName}) +self.UNITS[DCSunitName]=UNIT:Register(DCSunitName) end -return self.UNITS[DCSUnitName] +return self.UNITS[DCSunitName] end function DATABASE:DeleteUnit(DCSUnitName) +self:T("DeleteUnit "..tostring(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 +return self.STATICS[DCSStaticName] end function DATABASE:DeleteStatic(DCSStaticName) self.STATICS[DCSStaticName]=nil @@ -10705,6 +12086,20 @@ function DATABASE:FindStatic(StaticName) local StaticFound=self.STATICS[StaticName] return StaticFound end +function DATABASE:AddDynamicCargo(Name) +if not self.DYNAMICCARGO[Name]then +self.DYNAMICCARGO[Name]=DYNAMICCARGO:Register(Name) +end +return self.DYNAMICCARGO[Name] +end +function DATABASE:FindDynamicCargo(DynamicCargoName) +local StaticFound=self.DYNAMICCARGO[DynamicCargoName] +return StaticFound +end +function DATABASE:DeleteDynamicCargo(DynamicCargoName) +self.DYNAMICCARGO[DynamicCargoName]=nil +return self +end function DATABASE:AddAirbase(AirbaseName) if not self.AIRBASES[AirbaseName]then self.AIRBASES[AirbaseName]=AIRBASE:Register(AirbaseName) @@ -10836,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -10972,31 +12373,49 @@ 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) +function DATABASE:AddClient(ClientName,Force) +local DCSUnitName=ClientName +if type(DCSUnitName)=="number"then DCSUnitName=string.format("%d",ClientName)end +if not self.CLIENTS[DCSUnitName]or Force==true then +self.CLIENTS[DCSUnitName]=CLIENT:Register(DCSUnitName) end -return self.CLIENTS[ClientName] +return self.CLIENTS[DCSUnitName] end function DATABASE:FindGroup(GroupName) +if type(GroupName)~="string"or GroupName==""then return end local GroupFound=self.GROUPS[GroupName] +if GroupFound==nil and GroupName~=nil and self.Templates.Groups[GroupName]==nil then +self:_RegisterDynamicGroup(GroupName) +return self.GROUPS[GroupName] +end return GroupFound end -function DATABASE:AddGroup(GroupName) -if not self.GROUPS[GroupName]then +function DATABASE:AddGroup(GroupName,force) +if not self.GROUPS[GroupName]or force==true then self:T({"Add GROUP:",GroupName}) self.GROUPS[GroupName]=GROUP:Register(GroupName) end return self.GROUPS[GroupName] end function DATABASE:AddPlayer(UnitName,PlayerName) +if type(UnitName)=="number"then UnitName=string.format("%d",UnitName)end if PlayerName then -self:T({"Add player for unit:",UnitName,PlayerName}) +self:I({"Add player for unit:",UnitName,PlayerName}) self.PLAYERS[PlayerName]=UnitName self.PLAYERUNITS[PlayerName]=self:FindUnit(UnitName) self.PLAYERSJOINED[PlayerName]=PlayerName end end +function DATABASE:_FindPlayerNameByUnitName(UnitName) +if UnitName then +for playername,unitname in pairs(self.PLAYERS)do +if unitname==UnitName and self.PLAYERUNITS[playername]and self.PLAYERUNITS[playername]:IsAlive()then +return playername,self.PLAYERUNITS[playername] +end +end +end +return nil +end function DATABASE:DeletePlayer(UnitName,PlayerName) if PlayerName then self:T({"Clean player:",PlayerName}) @@ -11092,7 +12511,7 @@ 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) +self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name) end end if UnitTemplate.AddPropAircraft.SADL_TN then @@ -11101,7 +12520,7 @@ 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) +self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name) end end end @@ -11176,10 +12595,13 @@ end return nextoctal end function DATABASE:GetGroupTemplate(GroupName) -local GroupTemplate=self.Templates.Groups[GroupName].Template +local GroupTemplate=nil +if self.Templates.Groups[GroupName]then +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 +end return GroupTemplate end function DATABASE:_RegisterStaticTemplate(StaticTemplate,CoalitionID,CategoryID,CountryID) @@ -11205,6 +12627,33 @@ Country=self.Templates.Statics[StaticTemplateName].CountryID self:AddStatic(StaticTemplateName) return self end +function DATABASE:_GetGenericStaticCargoGroupTemplate(Name,Typename,Mass,Coalition,Country) +local StaticTemplate={} +StaticTemplate.name=Name or"None" +StaticTemplate.units={[1]={ +name=Name, +resourcePayload={ +["weapons"]={}, +["aircrafts"]={}, +["gasoline"]=0, +["diesel"]=0, +["methanol_mixture"]=0, +["jet_fuel"]=0, +}, +["mass"]=Mass or 0, +["category"]="Cargos", +["canCargo"]=true, +["type"]=Typename or"container_cargo", +["rate"]=100, +["y"]=0, +["x"]=0, +["heading"]=0, +}} +StaticTemplate.CategoryID="static" +StaticTemplate.CoalitionID=Coalition or coalition.side.BLUE +StaticTemplate.CountryID=Country or country.id.GERMANY +return StaticTemplate +end function DATABASE:GetStaticGroupTemplate(StaticName) if self.Templates.Statics[StaticName]then local StaticTemplate=self.Templates.Statics[StaticName].GroupTemplate @@ -11248,14 +12697,26 @@ return nil end end function DATABASE:GetCoalitionFromClientTemplate(ClientName) +if self.Templates.ClientsByName[ClientName]then return self.Templates.ClientsByName[ClientName].CoalitionID end +self:T("WARNING: Template does not exist for client "..tostring(ClientName)) +return nil +end function DATABASE:GetCategoryFromClientTemplate(ClientName) +if self.Templates.ClientsByName[ClientName]then return self.Templates.ClientsByName[ClientName].CategoryID end +self:T("WARNING: Template does not exist for client "..tostring(ClientName)) +return nil +end function DATABASE:GetCountryFromClientTemplate(ClientName) +if self.Templates.ClientsByName[ClientName]then return self.Templates.ClientsByName[ClientName].CountryID end +self:T("WARNING: Template does not exist for client "..tostring(ClientName)) +return nil +end function DATABASE:GetCoalitionFromAirbase(AirbaseName) return self.AIRBASES[AirbaseName]:GetCoalition() end @@ -11279,6 +12740,22 @@ end end return self end +function DATABASE:_RegisterDynamicGroup(Groupname) +local DCSGroup=Group.getByName(Groupname) +if DCSGroup and DCSGroup:isExist()then +local DCSGroupName=DCSGroup:getName() +self:I(string.format("Register Group: %s",tostring(DCSGroupName))) +self:AddGroup(DCSGroupName,true) +for DCSUnitId,DCSUnit in pairs(DCSGroup:getUnits())do +local DCSUnitName=DCSUnit:getName() +self:I(string.format("Register Unit: %s",tostring(DCSUnitName))) +self:AddUnit(tostring(DCSUnitName),true) +end +else +self:E({"Group does not exist: ",DCSGroup}) +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 @@ -11329,8 +12806,15 @@ end return self end function DATABASE:_RegisterAirbase(airbase) +local IsSyria=UTILS.GetDCSMap()=="Syria"and true or false +local countHSyria=0 if airbase then local DCSAirbaseName=airbase:getName() +if IsSyria and DCSAirbaseName=="H"and countHSyria>0 then +return self +elseif IsSyria and DCSAirbaseName=="H"and countHSyria==0 then +countHSyria=countHSyria+1 +end local airbaseID=airbase:getID() local airbase=self:AddAirbase(DCSAirbaseName) local airbaseUID=airbase:GetID(true) @@ -11351,10 +12835,14 @@ end return self end function DATABASE:_EventOnBirth(Event) -self:F({Event}) +self:T({Event}) if Event.IniDCSUnit then if Event.IniObjectCategory==Object.Category.STATIC then self:AddStatic(Event.IniDCSUnitName) +elseif Event.IniObjectCategory==Object.Category.CARGO and string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+")then +local cargo=self:AddDynamicCargo(Event.IniDCSUnitName) +self:I(string.format("Adding dynamic cargo %s",tostring(Event.IniDCSUnitName))) +self:CreateEventNewDynamicCargo(cargo) else if Event.IniObjectCategory==Object.Category.UNIT then self:AddUnit(Event.IniDCSUnitName) @@ -11367,24 +12855,27 @@ end end end if Event.IniObjectCategory==Object.Category.UNIT then -Event.IniUnit=self:FindUnit(Event.IniDCSUnitName) Event.IniGroup=self:FindGroup(Event.IniDCSGroupName) +Event.IniUnit=self:FindUnit(Event.IniDCSUnitName) 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) +self:I(string.format("Player '%s' joined unit '%s' (%s) of group '%s'",tostring(PlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniTypeName),tostring(Event.IniDCSGroupName))) +if client==nil or(client and client:CountPlayers()==0)then +client=self:AddClient(Event.IniDCSUnitName,true) end client:AddPlayer(PlayerName) if not self.PLAYERS[PlayerName]then self:AddPlayer(Event.IniUnitName,PlayerName) end +local function SetPlayerSettings(self,PlayerName,IniUnit) local Settings=SETTINGS:Set(PlayerName) -Settings:SetPlayerMenu(Event.IniUnit) -self:CreateEventPlayerEnterAircraft(Event.IniUnit) +Settings:SetPlayerMenu(IniUnit) +self:CreateEventPlayerEnterAircraft(IniUnit) +end +self:ScheduleOnce(1,SetPlayerSettings,self,PlayerName,Event.IniUnit) end end end @@ -11407,7 +12898,7 @@ end else if Event.IniObjectCategory==1 then if self.UNITS[Event.IniDCSUnitName]then -self:DeleteUnit(Event.IniDCSUnitName) +self:ScheduleOnce(1,self.DeleteUnit,self,Event.IniDCSUnitName) end local client=self.CLIENTS[name] if client then @@ -11425,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -11443,6 +12934,12 @@ end end end end +function DATABASE:_EventOnDynamicCargoRemoved(Event) +self:T({Event}) +if Event.IniDynamicCargoName then +self:DeleteDynamicCargo(Event.IniDynamicCargoName) +end +end function DATABASE:_EventOnPlayerLeaveUnit(Event) self:F2({Event}) local function FindPlayerName(UnitName) @@ -11733,8 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, -TimeInterval=nil, -YieldInterval=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -11776,16 +13283,28 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +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) @@ -11793,7 +13312,6 @@ end return Names end function SET_BASE:GetSetObjects() -self:F2() local Objects={} for Name,Object in pairs(self.Set)do table.insert(Objects,Object) @@ -11801,7 +13319,6 @@ end return Objects end function SET_BASE:Remove(ObjectName,NoTriggerEvent) -self:F2({ObjectName=ObjectName}) local TriggerEvent=true if NoTriggerEvent then TriggerEvent=false @@ -11823,7 +13340,11 @@ end end end function SET_BASE:Add(ObjectName,Object) -self:T2({ObjectName=ObjectName,Object=Object}) +if not ObjectName or ObjectName==""then +self:E("SET_BASE:Add - Invalid ObjectName handed") +self:E({ObjectName=ObjectName,Object=Object}) +return self +end if self.Set[ObjectName]then self:Remove(ObjectName,true) end @@ -11833,9 +13354,6 @@ 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() @@ -11888,22 +13406,18 @@ end return SetA end function SET_BASE:Get(ObjectName) -self:F(ObjectName) local Object=self.Set[ObjectName] -self:T3({ObjectName,Object}) return Object end function SET_BASE:GetFirst() local ObjectName=self.Index[1] local FirstObject=self.Set[ObjectName] -self:T3({FirstObject}) return FirstObject end function SET_BASE:GetLast() local tablemax=table.maxn(self.Index) local ObjectName=self.Index[tablemax] local LastObject=self.Set[ObjectName] -self:T3({LastObject}) return LastObject end function SET_BASE:GetRandom() @@ -11912,7 +13426,6 @@ for _,_ind in pairs(self.Index)do tablemax=tablemax+1 end local RandomItem=self.Set[self.Index[math.random(1,tablemax)]] -self:T3({RandomItem}) return RandomItem end function SET_BASE:GetRandomSurely() @@ -11923,7 +13436,6 @@ tablemax=tablemax+1 sorted[tablemax]=_obj end local RandomItem=sorted[math.random(1,tablemax)] -self:T3({RandomItem}) return RandomItem end function SET_BASE:Count() @@ -11935,11 +13447,6 @@ self.Filter=OtherFilter self.Database=BaseSet:GetSet() return self end -function SET_BASE:SetIteratorIntervals(YieldInterval,TimeInterval) -self.YieldInterval=YieldInterval -self.TimeInterval=TimeInterval -return self -end function SET_BASE:SetSomeIteratorLimit(Limit) self.SomeIteratorLimit=Limit or 1 return self @@ -11947,6 +13454,17 @@ end function SET_BASE:GetSomeIteratorLimit() return self.SomeIteratorLimit or self:Count() end +function SET_BASE:GetThreatLevelMax() +local ThreatMax=0 +for _,_unit in pairs(self.Set or{})do +local unit=_unit +local threat=unit.GetThreatLevel and unit:GetThreatLevel()or 0 +if threat>ThreatMax then +ThreatMax=threat +end +end +return ThreatMax +end function SET_BASE:FilterOnce() for ObjectName,Object in pairs(self.Database)do if self:IsIncludeObject(Object)then @@ -11985,16 +13503,15 @@ self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.Crash) return self end -function SET_BASE:FindNearestObjectFromPointVec2(PointVec2) -self:F2(PointVec2) +function SET_BASE:FindNearestObjectFromPointVec2(Coordinate) local NearestObject=nil local ClosestDistance=nil for ObjectID,ObjectData in pairs(self.Set)do if NearestObject==nil then NearestObject=ObjectData -ClosestDistance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) +ClosestDistance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate()) else -local Distance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) +local Distance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate()) if Distance1 then @@ -16281,6 +17990,17 @@ function COORDINATE:GetLandHeight() local Vec2={x=self.x,y=self.z} return land.getHeight(Vec2) end +function COORDINATE:GetLandProfileVec3(Destination) +return land.profile(self:GetVec3(),Destination) +end +function COORDINATE:GetLandProfileCoordinates(Destination) +local points=self:GetLandProfileVec3(Destination:GetVec3()) +local coords={} +for _,point in ipairs(points)do +table.insert(coords,COORDINATE:NewFromVec3(point)) +end +return coords +end function COORDINATE:SetHeading(Heading) self.Heading=Heading end @@ -16342,8 +18062,12 @@ 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) +local a=self:GetVec2() +if not TargetCoordinate.ClassName then +TargetCoordinate=COORDINATE:NewFromVec3(TargetCoordinate) +end +local b=TargetCoordinate:GetVec2() +local norm=UTILS.VecDist2D(a,b) return norm end function COORDINATE:GetTemperature(height) @@ -16425,6 +18149,79 @@ 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:GetX() +return self.x +end +function COORDINATE:GetY() +if self:IsInstanceOf("POINT_VEC2")then +return self.z +end +return self.y +end +function COORDINATE:GetZ() +return self.z +end +function COORDINATE:SetX(x) +self.x=x +return self +end +function COORDINATE:SetY(y) +if self:IsInstanceOf("POINT_VEC2")then +self.z=y +else +self.y=y +end +return self +end +function COORDINATE:SetZ(z) +self.z=z +return self +end +function COORDINATE:AddX(x) +self.x=self.x+x +return self +end +function COORDINATE:GetLat() +return self.x +end +function COORDINATE:SetLat(x) +self.x=x +return self +end +function COORDINATE:GetLon() +return self.z +end +function COORDINATE:SetLon(z) +self.z=z +return self +end +function COORDINATE:GetAlt() +return self.y~=0 or land.getHeight({x=self.x,y=self.z}) +end +function COORDINATE:SetAlt(Altitude) +self.y=Altitude or land.getHeight({x=self.x,y=self.z}) +return self +end +function COORDINATE:AddAlt(Altitude) +self.y=land.getHeight({x=self.x,y=self.z})+Altitude or 0 +return self +end +function COORDINATE:GetRandomPointVec2InRadius(OuterRadius,InnerRadius) +self:F2({OuterRadius,InnerRadius}) +return COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius)) +end +function COORDINATE:AddY(y) +if self:IsInstanceOf("POINT_VEC2")then +return self:AddZ(y) +else +self.y=self.y+y +end +return self +end +function COORDINATE:AddZ(z) +self.z=self.z+z +return self +end function COORDINATE:GetWindText(height,Settings) local Direction,Strength=self:GetWind(height) local Settings=Settings or _SETTINGS @@ -16450,7 +18247,7 @@ 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 variation=self:GetMagneticDeclination()or 0 local AngleMagnetic=AngleDegrees-variation if AngleMagnetic<0 then AngleMagnetic=360-AngleMagnetic end s=string.format('%03d°M|%03d°',AngleMagnetic,AngleDegrees) @@ -16522,10 +18319,11 @@ else return" bearing unknown" end end -function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar) +function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar,Precision) local Settings=Settings or _SETTINGS +Precision=Precision or 0 local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar) -local DistanceText=self:GetDistanceText(Distance,Settings,Language,0) +local DistanceText=self:GetDistanceText(Distance,Settings,Language,Precision) local BRText=BearingText..DistanceText return BRText end @@ -16577,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -16785,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -16826,79 +18642,141 @@ trigger.action.illuminationBomb(self:GetVec3(),Power) end return self end -function COORDINATE:Smoke(SmokeColor) -self:F2({SmokeColor}) -trigger.action.smoke(self:GetVec3(),SmokeColor) +function COORDINATE:Smoke(SmokeColor,Duration,Delay,Name,Offset,Direction,Distance) +self:F2({SmokeColor,Name,Duration,Delay,Offset}) +SmokeColor=SmokeColor or SMOKECOLOR.Green +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COORDINATE.Smoke,self,SmokeColor,Duration,0,Name,Direction,Distance) +else +self.firename=Name or"Smoke-"..math.random(1,100000) +if Offset or self.SmokeOffset then +local Angle=Direction or self:GetSmokeOffsetDirection() +local Distance=Distance or self:GetSmokeOffsetDistance() +local newpos=self:Translate(Distance,Angle,true,false) +local newvec3=newpos:GetVec3() +trigger.action.smoke(newvec3,SmokeColor,self.firename) +else +trigger.action.smoke(self:GetVec3(),SmokeColor,self.firename) end -function COORDINATE:SmokeGreen() -self:F2() -self:Smoke(SMOKECOLOR.Green) +if Duration and Duration>0 then +self:ScheduleOnce(Duration,COORDINATE.StopSmoke,self,self.firename) end -function COORDINATE:SmokeRed() -self:F2() -self:Smoke(SMOKECOLOR.Red) end -function COORDINATE:SmokeWhite() -self:F2() -self:Smoke(SMOKECOLOR.White) +return self end -function COORDINATE:SmokeOrange() -self:F2() -self:Smoke(SMOKECOLOR.Orange) +function COORDINATE:GetSmokeOffsetDirection() +local direction=self.SmokeOffsetDirection or math.random(1,359) +return direction end -function COORDINATE:SmokeBlue() -self:F2() -self:Smoke(SMOKECOLOR.Blue) +function COORDINATE:SetSmokeOffsetDirection(Direction) +if self then +self.SmokeOffsetDirection=Direction or math.random(1,359) +return self +else +COORDINATE.SmokeOffsetDirection=Direction or math.random(1,359) 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:GetSmokeOffsetDistance() +local distance=self.SmokeOffsetDistance or math.random(10,20) +return distance +end +function COORDINATE:SetSmokeOffsetDistance(Distance) +if self then +self.SmokeOffsetDistance=Distance or math.random(10,20) +return self +else +COORDINATE.SmokeOffsetDistance=Distance or math.random(10,20) +end +end +function COORDINATE:SwitchSmokeOffsetOn() +if self then +self.SmokeOffset=true +return self +else +COORDINATE.SmokeOffset=true +end +end +function COORDINATE:SwitchSmokeOffsetOff() +if self then +self.SmokeOffset=false +return self +else +COORDINATE.SmokeOffset=false +end +end +function COORDINATE:StopSmoke(name) +self:StopBigSmokeAndFire(name) +end +function COORDINATE:SmokeGreen(Duration,Delay) +self:Smoke(SMOKECOLOR.Green,Duration,Delay) +return self +end +function COORDINATE:SmokeRed(Duration,Delay) +self:Smoke(SMOKECOLOR.Red,Duration,Delay) +return self +end +function COORDINATE:SmokeWhite(Duration,Delay) +self:Smoke(SMOKECOLOR.White,Duration,Delay) +return self +end +function COORDINATE:SmokeOrange(Duration,Delay) +self:Smoke(SMOKECOLOR.Orange,Duration,Delay) +return self +end +function COORDINATE:SmokeBlue(Duration,Delay) +self:Smoke(SMOKECOLOR.Blue,Duration,Delay) +return self +end +function COORDINATE:BigSmokeAndFire(Preset,Density,Duration,Delay,Name) +self:F2({preset=Preset,density=Density}) +Preset=Preset or BIGSMOKEPRESET.SmallSmokeAndFire +Density=Density or 0.5 +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COORDINATE.BigSmokeAndFire,self,Preset,Density,Duration,0,Name) +else +self.firename=Name or"Fire-"..math.random(1,10000) +trigger.action.effectSmokeBig(self:GetVec3(),Preset,Density,self.firename) +if Duration and Duration>0 then +self:ScheduleOnce(Duration,COORDINATE.StopBigSmokeAndFire,self,self.firename) +end +end +return self 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) +function COORDINATE:BigSmokeAndFireSmall(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,Density,Duration,Delay,Name) +return self end -function COORDINATE:BigSmokeAndFireMedium(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,density,name) +function COORDINATE:BigSmokeAndFireMedium(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,Density,Duration,Delay,Name) +return self end -function COORDINATE:BigSmokeAndFireLarge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,density,name) +function COORDINATE:BigSmokeAndFireLarge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,Density,Duration,Delay,Name) +return self end -function COORDINATE:BigSmokeAndFireHuge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,density,name) +function COORDINATE:BigSmokeAndFireHuge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,Density,Duration,Delay,Name) +return self end -function COORDINATE:BigSmokeSmall(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,density,name) +function COORDINATE:BigSmokeSmall(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,Density,Duration,Delay,Name) +return self end -function COORDINATE:BigSmokeMedium(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,density,name) +function COORDINATE:BigSmokeMedium(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,Density,Duration,Delay,Name) +return self end -function COORDINATE:BigSmokeLarge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,density,name) +function COORDINATE:BigSmokeLarge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,Density,Duration,Delay,Name) +return self end -function COORDINATE:BigSmokeHuge(density,name) -self:F2({density=density}) -density=density or 0.5 -self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,density,name) +function COORDINATE:BigSmokeHuge(Density,Duration,Delay,Name) +self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,Density,Duration,Delay,Name) +return self end function COORDINATE:Flare(FlareColor,Azimuth) self:F2({FlareColor}) @@ -17172,7 +19050,7 @@ 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 +if InSeconds or type(sunrise)=="string"then return sunrise else return UTILS.SecondsToClock(sunrise,true) @@ -17203,6 +19081,10 @@ 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) +if type(sunrise)=="string"or type(sunset)=="string"then +if sunrise=="N/R"then return false end +if sunset=="N/S"then return true end +end local time=UTILS.ClockToSeconds(clock) if time>sunrise and time<=sunset then return true @@ -17212,6 +19094,10 @@ end else local sunrise=self:GetSunrise(true) local sunset=self:GetSunset(true) +if type(sunrise)=="string"or type(sunset)=="string"then +if sunrise=="N/R"then return false end +if sunset=="N/S"then return true end +end local time=UTILS.SecondsOfToday() if time>sunrise and time<=sunset then return true @@ -17240,7 +19126,7 @@ 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 +if InSeconds or type(sunrise)=="string"then return sunrise else return UTILS.SecondsToClock(sunrise,true) @@ -17262,11 +19148,11 @@ delta=sunset+UTILS.SecondsToMidnight() end return delta/60 end -function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar) +function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar,Precision) local DirectionVec3=FromCoordinate:GetDirectionVec3(self) local AngleRadians=self:GetAngleRadians(DirectionVec3) local Distance=self:Get2DDistance(FromCoordinate) -return"BR, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar) +return"BR, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar,Precision) end function COORDINATE:ToStringBRA(FromCoordinate,Settings,MagVar) local DirectionVec3=FromCoordinate:GetDirectionVec3(self) @@ -17281,6 +19167,8 @@ local currentCoord=FromCoordinate local DirectionVec3=FromCoordinate:GetDirectionVec3(self) local AngleRadians=self:GetAngleRadians(DirectionVec3) local bearing=UTILS.Round(UTILS.ToDegree(AngleRadians),0) +local magnetic=self:GetMagneticDeclination()or 0 +bearing=bearing-magnetic local rangeMetres=self:Get2DDistance(currentCoord) local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0) local aspect=self:ToStringAspect(currentCoord) @@ -17512,25 +19400,9 @@ return self:ToStringMGRS(Settings) end return nil end -function COORDINATE:ToString(Controllable,Settings,Task) +function COORDINATE:ToString(Controllable,Settings) 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 @@ -17591,156 +19463,20 @@ local steep,elev=self:IsInSteepArea(Radius,Minelevation) local flat=not steep return flat,elev end +function COORDINATE:GetRandomPointVec3InRadius(OuterRadius,InnerRadius) +return COORDINATE:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius)) 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 +function COORDINATE:GetSimpleZones(SearchRadius,PosRadius,NumPositions) +local clearPositions=UTILS.GetSimpleZones(self:GetVec3(),SearchRadius,PosRadius,NumPositions) +if clearPositions and#clearPositions>0 then +local coords={} +for _,pos in pairs(clearPositions)do +local coord=COORDINATE:NewFromVec2(pos) +table.insert(coords,coord) end -function POINT_VEC3:NewFromVec2(Vec2,LandHeightAdd) -local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd)) -self:F2(self) -return self +return coords 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 +return nil end end do @@ -17830,15 +19566,15 @@ Briefing="Briefing Report", Overview="Overview Report", Detailed="Detailed Report", } -function MESSAGE:New(MessageText,MessageDuration,MessageCategory,ClearScreen) +function MESSAGE:New(Text,Duration,Category,ClearScreen) local self=BASE:Inherit(self,BASE:New()) -self:F({MessageText,MessageDuration,MessageCategory}) +self:F({Text,Duration,Category}) self.MessageType=nil -if MessageCategory and MessageCategory~=""then -if MessageCategory:sub(-1)~="\n"then -self.MessageCategory=MessageCategory..": " +if Category and Category~=""then +if Category:sub(-1)~="\n"then +self.MessageCategory=Category..": " else -self.MessageCategory=MessageCategory:sub(1,-2)..":\n" +self.MessageCategory=Category:sub(1,-2)..":\n" end else self.MessageCategory="" @@ -17847,9 +19583,9 @@ self.ClearScreen=false if ClearScreen~=nil then self.ClearScreen=ClearScreen end -self.MessageDuration=MessageDuration or 5 +self.MessageDuration=Duration or 5 self.MessageTime=timer.getTime() -self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1) +self.MessageText=Text:gsub("^\n","",1):gsub("\n$","",1) self.MessageSent=false self.MessageGroup=false self.MessageCoalition=false @@ -17874,24 +19610,12 @@ 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 +self:ToUnit(Client,Settings) return self end function MESSAGE:ToGroup(Group,Settings) self:F(Group.GroupName) -if Group then +if Group and Group:IsAlive()then if self.MessageType then local Settings=Settings or(Group and _DATABASE:GetPlayerSettings(Group:GetPlayerName()))or _SETTINGS self.MessageDuration=Settings:GetMessageTime(self.MessageType) @@ -17906,7 +19630,7 @@ return self end function MESSAGE:ToUnit(Unit,Settings) self:F(Unit.IdentifiableName) -if Unit then +if Unit and Unit:IsAlive()then if self.MessageType then local Settings=Settings or(Unit and _DATABASE:GetPlayerSettings(Unit:GetPlayerName()))or _SETTINGS self.MessageDuration=Settings:GetMessageTime(self.MessageType) @@ -17914,6 +19638,7 @@ self.MessageCategory="" end if self.MessageDuration~=0 then self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +local ID=Unit:GetID() trigger.action.outTextForUnit(Unit:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) end end @@ -18008,8 +19733,8 @@ 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" +function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) +_MESSAGESRS.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" _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) @@ -18019,6 +19744,9 @@ _MESSAGESRS.coordinate=Coordinate if Coordinate then _MESSAGESRS.MSRS:SetCoordinate(Coordinate) end +if Backend then +_MESSAGESRS.MSRS:SetBackend(Backend) +end _MESSAGESRS.Culture=Culture or MSRS.culture or"en-GB" _MESSAGESRS.MSRS:SetCulture(Culture) _MESSAGESRS.Gender=Gender or MSRS.gender or"female" @@ -18028,9 +19756,9 @@ _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.MSRS:SetLabel(_MESSAGESRS.label) _MESSAGESRS.port=Port or MSRS.port or 5002 -_MESSAGESRS.MSRS:SetPort(Port or 5002) +_MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port) _MESSAGESRS.volume=Volume or MSRS.volume or 1 _MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume) if Voice then _MESSAGESRS.MSRS:SetVoice(Voice)end @@ -18586,7 +20314,6 @@ 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 @@ -18628,7 +20355,6 @@ 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 @@ -18670,7 +20396,6 @@ 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 @@ -18719,7 +20444,6 @@ 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 @@ -18729,25 +20453,21 @@ 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 @@ -18765,22 +20485,18 @@ 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 @@ -18795,7 +20511,6 @@ 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 @@ -18808,7 +20523,6 @@ 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 @@ -18821,7 +20535,6 @@ end return self end function SPAWN:InitSpeedMps(MPS) -self:F({MPS=MPS}) if MPS==nil or tonumber(MPS)<0 then MPS=125 end @@ -18829,7 +20542,6 @@ 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 @@ -18837,7 +20549,6 @@ 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 @@ -18845,17 +20556,14 @@ 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 @@ -18872,7 +20580,6 @@ 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 @@ -18884,7 +20591,6 @@ 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 @@ -18894,7 +20600,6 @@ 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 @@ -18904,19 +20609,22 @@ 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:InitValidateAndRepositionGroundUnits(OnOff,MaxRadius,Spacing) +self.SpawnValidateAndRepositionGroundUnits=OnOff +self.SpawnValidateAndRepositionGroundUnitsRadius=MaxRadius +self.SpawnValidateAndRepositionGroundUnitsSpacing=Spacing +return self +end function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable) -self:F({self.SpawnTemplatePrefix,SpawnTemplatePrefixTable}) local temptable={} for _,_temp in pairs(SpawnTemplatePrefixTable)do temptable[#temptable+1]=_temp @@ -18924,29 +20632,25 @@ end self.SpawnTemplatePrefixTable=UTILS.ShuffleTable(temptable) self.SpawnRandomizeTemplate=true for SpawnGroupID=1,self.SpawnMaxGroups do -self:_RandomizeTemplate(SpawnGroupID) +self:_RandomizeTemplate(SpawnGroupID,RandomizePositionInZone) end return self end -function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet) -self:F({self.SpawnTemplatePrefix}) +function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone) local setnames=SpawnTemplateSet:GetSetNames() -self:InitRandomizeTemplate(setnames) +self:InitRandomizeTemplate(setnames,RandomizePositionInZone) return self end -function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes) -self:F({self.SpawnTemplatePrefix}) +function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes,RandomizePositionInZone) local SpawnTemplateSet=SET_GROUP:New():FilterPrefixes(SpawnTemplatePrefixes):FilterOnce() -self:InitRandomizeTemplateSet(SpawnTemplateSet) +self:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone) 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}) +function SPAWN:InitRandomizeZones(SpawnZoneTable,RandomizePositionInZone) local temptable={} for _,_temp in pairs(SpawnZoneTable)do temptable[#temptable+1]=_temp @@ -18954,7 +20658,7 @@ end self.SpawnZoneTable=UTILS.ShuffleTable(temptable) self.SpawnRandomizeZones=true for SpawnGroupID=1,self.SpawnMaxGroups do -self:_RandomizeZones(SpawnGroupID) +self:_RandomizeZones(SpawnGroupID,RandomizePositionInZone) end return self end @@ -18972,59 +20676,50 @@ 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}) +function SPAWN:InitRepeatOnLanding(WaitingTime) self:InitRepeat() self.RepeatOnEngineShutDown=false self.RepeatOnLanding=true +self.RepeatOnLandingTime=(WaitingTime and WaitingTime>3)and WaitingTime or 3 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 @@ -19044,6 +20739,7 @@ self:HandleEvent(EVENTS.Birth,self._OnBirth) self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.UnitLost,self._OnDeadOrCrash) if self.Repeat then self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) self:HandleEvent(EVENTS.Land,self._OnLand) @@ -19057,6 +20753,19 @@ SpawnY=SpawnYIndex*SpawnDeltaY end return self end +function SPAWN:StopRepeat() +if self.Repeat then +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Land) +end +if self.RepeatOnEngineShutDown then +self:UnHandleEvent(EVENTS.EngineShutdown) +end +self.Repeat=false +self.RepeatOnEngineShutDown=false +self.RepeatOnLanding=false +return self +end do function SPAWN:InitAIOnOff(AIOnOff) self.AIOnOff=AIOnOff @@ -19081,8 +20790,8 @@ function SPAWN:InitDelayOff() return self:InitDelayOnOff(false) end end -function SPAWN:InitHiddenOnMap() -self.SpawnHiddenOnMap=true +function SPAWN:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true return self end function SPAWN:InitHiddenOnMFD() @@ -19094,7 +20803,6 @@ 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 @@ -19102,7 +20810,6 @@ return self:SpawnWithIndex(self.SpawnIndex+1) end end function SPAWN:ReSpawn(SpawnIndex) -self:F({self.SpawnTemplatePrefix,SpawnIndex}) if not SpawnIndex then SpawnIndex=1 end @@ -19119,17 +20826,16 @@ if SpawnGroup and WayPoints then SpawnGroup:WayPointInitialize(WayPoints) SpawnGroup:WayPointExecute(1,5) end -if SpawnGroup.ReSpawnFunction then +if SpawnGroup and SpawnGroup.ReSpawnFunction then SpawnGroup:ReSpawnFunction() end -SpawnGroup:ResetEvents() +if SpawnGroup then SpawnGroup:ResetEvents()end 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) @@ -19139,10 +20845,8 @@ 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}) +local PointVec3=COORDINATE:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y) if self.SpawnRandomizePosition then local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnRandomizePositionOuterRadius,self.SpawnRandomizePositionInnerRadius) local CurrentX=SpawnTemplate.units[1].x @@ -19152,10 +20856,12 @@ 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 +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -19169,13 +20875,11 @@ 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) @@ -19290,7 +20994,12 @@ if self.SpawnHiddenOnMFD then SpawnTemplate.hiddenOnMFD=true end if self.SpawnHiddenOnMap then -SpawnTemplate.hidden=true +SpawnTemplate.hidden=self.SpawnHiddenOnMap +end +if self.SpawnValidateAndRepositionGroundUnits then +local units=SpawnTemplate.units +local gPos={x=SpawnTemplate.x,y=SpawnTemplate.y} +UTILS.ValidateAndRepositionGroundUnits(units,gPos,self.SpawnValidateAndRepositionGroundUnitsRadius,self.SpawnValidateAndRepositionGroundUnitsSpacing) end SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID @@ -19299,8 +21008,8 @@ 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.UnitLost,self._OnDeadOrCrash) self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) if self.Repeat then self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) @@ -19327,9 +21036,13 @@ 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<15 then +self:E("****SPAWN SCHEDULED****\nWARNING - Setting a very low SpawnTime heavily impacts your mission performance and CPU time, it is NOT useful to check the alive state of an object every "..tostring(SpawnTime).." seconds.\nSetting to 15 second intervals.\n*****") +SpawnTime=15 +end +if SpawnTimeVariation>1 or SpawnTimeVariation<0 then SpawnTimeVariation=0.5 end if SpawnTime~=nil and SpawnTimeVariation~=nil then local InitialDelay=0 if WithDelay or self.DelayOnOff==true then @@ -19340,17 +21053,14 @@ 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 @@ -19359,30 +21069,22 @@ 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 group=GROUP:FindByName(self.SpawnTemplatePrefix) +local unit=group:GetUnit(1) 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 ishelo=unit:HasAttribute("Helicopters") local nunits=#SpawnTemplate.units local SpawnPoint=SpawnTemplate.route.points[1] SpawnPoint.linkUnit=nil @@ -19390,34 +21092,24 @@ 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 +else 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 +local autoparking=false +if SpawnAirbase.isAirdrome then +autoparking=false +else +autoparking=true end local parkingspots={} local parkingindex={} @@ -19425,8 +21117,8 @@ local spots if spawnonground and not SpawnTemplate.parked then local nfree=0 local termtype=TerminalType -if spawnonrunway then -if spawnonship then +if Takeoff==SPAWN.Takeoff.Runway then +if SpawnAirbase.isShip then if ishelo then termtype=AIRBASE.TerminalType.HelicopterUsable else @@ -19441,51 +21133,46 @@ 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())) +if autoparking then nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true) spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true) +elseif Parkingdata~=nil then +nfree=#Parkingdata +spots=Parkingdata 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) +spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,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) @@ -19495,7 +21182,7 @@ PointVec3=spots[1].Coordinate else _notenough=true end -elseif spawnonairport then +else if nfree>=nunits then for i=1,nunits do table.insert(parkingspots,spots[i].Coordinate) @@ -19508,10 +21195,7 @@ 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 +autoparking=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) @@ -19541,7 +21225,6 @@ 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 @@ -19550,19 +21233,16 @@ 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())) +if autoparking then 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 @@ -19572,9 +21252,6 @@ 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 @@ -19586,19 +21263,15 @@ 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) +self:ScheduleOnce(5,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()}) 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 @@ -19615,9 +21288,7 @@ end local Parkingdata={} for _,TerminalID in pairs(Spots)do local spot=Airbase:GetParkingSpotData(TerminalID) -self:T2({spot=spot}) if spot and spot.Free then -self:T(string.format("Adding parking spot ID=%d TermType=%d",spot.TerminalID,spot.TerminalType)) table.insert(Parkingdata,spot) end end @@ -19632,14 +21303,11 @@ 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") @@ -19653,7 +21321,6 @@ 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 @@ -19667,7 +21334,6 @@ 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 @@ -19694,43 +21360,35 @@ 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 nfree0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -21952,11 +24252,11 @@ function IDENTIFIABLE:GetCategory() self:F2(self.ObjectName) local DCSObject=self:GetDCSObject() if DCSObject then -local ObjectCategory=DCSObject:getCategory() +local ObjectCategory,UnitCategory=DCSObject:getCategory() self:T3(ObjectCategory) -return ObjectCategory +return ObjectCategory,UnitCategory end -return nil +return nil,nil end function IDENTIFIABLE:GetCategoryName() local DCSIdentifiable=self:GetDCSObject() @@ -21978,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -22078,6 +24387,9 @@ end function POSITIONABLE:GetPosition() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() +if self:IsInstanceOf("GROUP")then +DCSPositionable=self:GetFirstUnitAlive():GetDCSObject() +end if DCSPositionable then local PositionablePosition=DCSPositionable:getPosition() self:T3(PositionablePosition) @@ -22122,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -22137,6 +24461,14 @@ function POSITIONABLE:GetVec3() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local vec3=DCSPositionable:getPoint() +if not vec3 then +local pos=DCSPositionable:getPosition() +if pos and pos.p then +vec3=pos.p +else +self:E({"Cannot get the position from DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()}) +end +end return vec3 end self:E({"Cannot get the Positionable DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()}) @@ -22151,15 +24483,25 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +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) +local PositionablePointVec2=COORDINATE:NewFromVec3(PositionableVec3) return PositionablePointVec2 end -self:E({"Cannot GetPointVec2",Positionable=self,Alive=self:IsAlive()}) +self:E({"Cannot Coordinate",Positionable=self,Alive=self:IsAlive()}) return nil end function POSITIONABLE:GetPointVec3() @@ -22171,7 +24513,7 @@ self.pointvec3.x=PositionableVec3.x self.pointvec3.y=PositionableVec3.y self.pointvec3.z=PositionableVec3.z else -self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3) +self.pointvec3=COORDINATE:NewFromVec3(PositionableVec3) end return self.pointvec3 end @@ -22182,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -22189,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -22196,11 +24540,13 @@ function POSITIONABLE:GetCoordinate() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then local coord=COORDINATE:NewFromVec3(PositionableVec3) local heading=self:GetHeading() coord.Heading=heading return coord end +end self:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -22344,15 +24690,21 @@ 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()}) +BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -22472,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -22966,6 +25328,8 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_KORD"]=6*POSITIONABLE.DefaultInfantryWeight, ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, +["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -23501,7 +25865,7 @@ power=Power or 10, }, } if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandSetFrequency,{self,Frequency,Modulation,Power}) +SCHEDULER:New(nil,self.CommandSetFrequency,{self,Frequency,Modulation,Power},Delay) else self:SetCommand(CommandSetFrequency) end @@ -23518,12 +25882,55 @@ power=Power or 10, }, } if Delay and Delay>0 then -SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID}) +SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID},Delay) else self:SetCommand(CommandSetFrequencyForUnit) end return self end +function CONTROLLABLE:CommandSmokeOnOff(OnOff,Delay) +local switch=(OnOff==nil)and true or OnOff +local command={ +id='SMOKE_ON_OFF', +params={ +value=switch +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSmokeOnOff,{self,switch},Delay) +else +self:SetCommand(command) +end +return self +end +function CONTROLLABLE:CommandSmokeON(Delay) +local command={ +id='SMOKE_ON_OFF', +params={ +value=true +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSmokeON,{self},Delay) +else +self:SetCommand(command) +end +return self +end +function CONTROLLABLE:CommandSmokeOFF(Delay) +local command={ +id='SMOKE_ON_OFF', +params={ +value=false +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSmokeOFF,{self},Delay) +else +self:SetCommand(command) +end +return self +end function CONTROLLABLE:TaskEPLRS(SwitchOnOff,idx) if SwitchOnOff==nil then SwitchOnOff=true @@ -23598,10 +26005,10 @@ local DCSTask={ id='Strafing', params={ point=Vec2, -weaponType=WeaponType or 1073741822, +weaponType=WeaponType or 805337088, expend=WeaponExpend or"Auto", attackQty=AttackQty or 1, -attackQtyLimit=AttackQty>1 and true or false, +attackQtyLimit=AttackQty~=nil and true or false, direction=Direction and math.rad(Direction)or 0, directionEnabled=Direction and true or false, groupAttack=GroupAttack or false, @@ -23723,7 +26130,6 @@ groupsForEmbarking=g4e, return Disembarking end function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed) -self:F2({self.ControllableName,Point,Altitude,Speed}) local DCSTask={ id='Orbit', params={ @@ -23803,20 +26209,25 @@ lastWptIndex=LastWptNumber } return DCSTask end -function CONTROLLABLE:TaskLandAtVec2(Vec2,Duration) +function CONTROLLABLE:TaskLandAtVec2(Vec2,Duration,CombatLanding,DirectionAfterLand) local DCSTask={ id='Land', params={ point=Vec2, durationFlag=Duration and true or false, duration=Duration, +combatLandingFlag=CombatLanding==true and true or false, }, } +if DirectionAfterLand~=nil and type(DirectionAfterLand)=="number"then +DCSTask.params.directionEnabled=true +DCSTask.params.direction=math.rad(DirectionAfterLand) +end return DCSTask end -function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint) +function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint,CombatLanding,DirectionAfterLand) local Point=RandomPoint and Zone:GetRandomVec2()or Zone:GetVec2() -local DCSTask=CONTROLLABLE.TaskLandAtVec2(self,Point,Duration) +local DCSTask=CONTROLLABLE.TaskLandAtVec2(self,Point,Duration,CombatLanding,DirectionAfterLand) return DCSTask end function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex) @@ -24563,7 +26974,7 @@ 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 DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTIC 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 @@ -24597,15 +27008,15 @@ 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 DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTIC 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 +local TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity =Controller:isTargetDetected(DCSObject,DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK) -return TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity +return TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity end return nil end @@ -24874,6 +27285,18 @@ return self end return nil end +function CONTROLLABLE:OptionPreferVerticalLanding() +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.PREFER_VERTICAL,true) +end +return self +end +return nil +end function CONTROLLABLE:OptionROTEvadeFirePossible() self:F2({self.ControllableName}) local DCSControllable=self:GetDCSObject() @@ -25023,6 +27446,30 @@ self:SetOption(AI.Option.Air.id.PROHIBIT_AB,Prohibit) end return self end +function CONTROLLABLE:OptionEvasionOfARM(Seconds) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +if Seconds==nil then Seconds=false end +Controller:setOption(AI.Option.Ground.id.EVASION_OF_ARM,Seconds) +end +end +return self +end +function CONTROLLABLE:OptionFormationInterval(meters) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +if meters==nil or meters>100 or meters<0 then meters=50 end +Controller:setOption(30,meters) +end +end +return self +end function CONTROLLABLE:OptionECM(ECMvalue) self:F2({self.ControllableName}) local DCSControllable=self:GetDCSObject() @@ -25145,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -25163,6 +27650,34 @@ return self end return nil end +function CONTROLLABLE:SetOptionLandingStraightIn() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","0") +end +return self +end +function CONTROLLABLE:SetOptionLandingForcePair() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","1") +end +return self +end +function CONTROLLABLE:SetOptionLandingRestrictPair() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","2") +end +return self +end +function CONTROLLABLE:SetOptionLandingOverheadBreak() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption("36","3") +end +return self +end function CONTROLLABLE:SetOptionRadarUsing(Option) self:F2({self.ControllableName}) if self:IsAir()then @@ -25244,6 +27759,9 @@ end function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation,onland) self:F2({self.ControllableName}) local _coord=self:GetCoordinate() +if not _coord then +return self +end local _radius=radius or 500 local _speed=speed or 20 local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100) @@ -26185,6 +28703,121 @@ PatrolGroup:Route(Route,Delay) end return self end +function CONTROLLABLE:NewIRMarker(EnableImmediately,Runtime) +self:T2("NewIRMarker") +if self:IsInstanceOf("GROUP")then +if self.IRMarkerGroup==true then return end +self.IRMarkerGroup=true +self.IRMarkerUnit=false +elseif self:IsInstanceOf("UNIT")then +if self.IRMarkerUnit==true then return end +self.IRMarkerGroup=false +self.IRMarkerUnit=true +end +self.Runtime=Runtime or 60 +if EnableImmediately and EnableImmediately==true then +self:EnableIRMarker(Runtime) +end +return self +end +function CONTROLLABLE:EnableIRMarker(Runtime) +self:T2("EnableIRMarker") +if self.IRMarkerGroup==nil then +self:NewIRMarker(true,Runtime) +return +end +if self:IsInstanceOf("GROUP")then +self:EnableIRMarkerForGroup(Runtime) +return +end +if self.timer and self.timer:IsRunning()then return self end +local Runtime=Runtime or self.Runtime +self.timer=TIMER:New(CONTROLLABLE._MarkerBlink,self) +self.timer:Start(nil,1-math.random(1,5)/10/2,Runtime) +self.IRMarkerUnit=true +return self +end +function CONTROLLABLE:DisableIRMarker() +self:T2("DisableIRMarker") +if self:IsInstanceOf("GROUP")then +self:DisableIRMarkerForGroup() +return +end +if self.spot then +self.spot=nil +end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end +if self:IsInstanceOf("GROUP")then +self.IRMarkerGroup=nil +elseif self:IsInstanceOf("UNIT")then +self.IRMarkerUnit=nil +end +return self +end +function CONTROLLABLE:EnableIRMarkerForGroup(Runtime) +self:T2("EnableIRMarkerForGroup") +if self:IsInstanceOf("GROUP") +then +local units=self:GetUnits()or{} +for _,_unit in pairs(units)do +_unit:EnableIRMarker(Runtime) +end +self.IRMarkerGroup=true +end +return self +end +function CONTROLLABLE:DisableIRMarkerForGroup() +self:T2("DisableIRMarkerForGroup") +if self:IsInstanceOf("GROUP")then +local units=self:GetUnits()or{} +for _,_unit in pairs(units)do +_unit:DisableIRMarker() +end +self.IRMarkerGroup=nil +end +return self +end +function CONTROLLABLE:HasIRMarker() +self:T2("HasIRMarker") +if self:IsInstanceOf("GROUP")then +local units=self:GetUnits()or{} +for _,_unit in pairs(units)do +if _unit.timer and _unit.timer:IsRunning()then return true end +end +elseif self.timer and self.timer:IsRunning()then return true end +return false +end +function CONTROLLABLE._StopSpot(spot) +if spot then +spot:destroy() +end +end +function CONTROLLABLE:_MarkerBlink() +self:T2("_MarkerBlink") +if self:IsAlive()~=true then +self:DisableIRMarker() +return +end +self.timer.dT=1-(math.random(1,2)/10/2) +local _,_,unitBBHeight,_=self:GetObjectSize() +local unitPos=self:GetPositionVec3() +if self.timer:IsRunning()then +self:T2("Create Spot") +local spot=Spot.createInfraRed( +self.DCSUnit, +{x=0,y=(unitBBHeight+1),z=0}, +{x=unitPos.x,y=(unitPos.y+unitBBHeight),z=unitPos.z} +) +self.spot=spot +local offTimer=nil +local offTimer=TIMER:New(CONTROLLABLE._StopSpot,spot) +offTimer:Start(0.5) +end +return self +end GROUP={ ClassName="GROUP", } @@ -26221,6 +28854,8 @@ GROUND_TRAIN="Ground_Train", GROUND_EWR="Ground_EWR", GROUND_AAA="Ground_AAA", GROUND_SAM="Ground_SAM", +GROUND_SHORAD="Ground_SHORAD", +GROUND_BALLISTICMISSILE="Ground_BallisticMissile", GROUND_OTHER="Ground_OtherGround", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_WARSHIP="Naval_WarShip", @@ -26277,33 +28912,30 @@ end function GROUP:GetDCSObject() local DCSGroup=Group.getByName(self.GroupName) if DCSGroup then +self.LastCallDCSObject=timer.getTime() +self.DCSObject=DCSGroup 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 @@ -26311,7 +28943,6 @@ 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) @@ -26323,11 +28954,10 @@ 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() +local DCSGroup=Group.getByName(self.GroupName) if DCSGroup then for Index,UnitData in pairs(DCSGroup:getUnits())do if GenerateEvent and GenerateEvent==true then @@ -26349,17 +28979,14 @@ 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={ @@ -26370,27 +28997,27 @@ local CategoryNames={ [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) +if self.GroupCoalition~=nil then +return self.GroupCoalition +else local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupCoalition=DCSGroup:getCoalition() -self:T3(GroupCoalition) +self.GroupCoalition=GroupCoalition return GroupCoalition end +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 @@ -26420,7 +29047,6 @@ end return nil end function GROUP:GetSpeedMax() -self:F2(self.GroupName) local DCSGroup=self:GetDCSObject() if DCSGroup then local Units=self:GetUnits() @@ -26437,7 +29063,6 @@ end return nil end function GROUP:GetRange() -self:F2(self.GroupName) local DCSGroup=self:GetDCSObject() if DCSGroup then local Units=self:GetUnits() @@ -26458,7 +29083,6 @@ end return nil end function GROUP:GetUnits() -self:F2({self.GroupName}) local DCSGroup=self:GetDCSObject() if DCSGroup then local DCSUnits=DCSGroup:getUnits()or{} @@ -26473,13 +29097,11 @@ unit=_DATABASE:AddUnit(UnitName) Units[#Units+1]=unit end end -self:T3(Units) return Units end return nil end function GROUP:GetPlayerUnits() -self:F2({self.GroupName}) local DCSGroup=self:GetDCSObject() if DCSGroup then local DCSUnits=DCSGroup:getUnits() @@ -26490,13 +29112,16 @@ if PlayerUnit:GetPlayerName()then Units[#Units+1]=PlayerUnit end end -self:T3(Units) return Units end return nil end function GROUP:IsPlayer() -return self:GetUnit(1):IsPlayer() +local unit=self:GetUnit(1) +if unit then +return unit:IsPlayer() +end +return false end function GROUP:GetUnit(UnitNumber) local DCSGroup=self:GetDCSObject() @@ -26548,7 +29173,6 @@ end return nil end function GROUP:CountAliveUnits() -self:F3({self.GroupName}) local DCSGroup=self:GetDCSObject() if DCSGroup then local units=self:GetUnits() @@ -26564,7 +29188,6 @@ end return 0 end function GROUP:GetFirstUnitAlive() -self:F3({self.GroupName}) local DCSGroup=self:GetDCSObject() if DCSGroup then local units=self:GetUnits() @@ -26578,7 +29201,6 @@ end return nil end function GROUP:GetFirstUnit() -self:F3({self.GroupName}) local DCSGroup=self:GetDCSObject() if DCSGroup then local units=self:GetUnits() @@ -26587,17 +29209,19 @@ end return nil end function GROUP:GetVelocityVec3() -self:F2(self.GroupName) local DCSGroup=self:GetDCSObject() if DCSGroup and DCSGroup:isExist()then local GroupUnits=DCSGroup:getUnits() -local GroupCount=#GroupUnits +local GroupCount=0 local VelocityVec3={x=0,y=0,z=0} for _,DCSUnit in pairs(GroupUnits)do +if DCSUnit:isExist()and DCSUnit:isActive()then local UnitVelocityVec3=DCSUnit:getVelocity() VelocityVec3.x=VelocityVec3.x+UnitVelocityVec3.x VelocityVec3.y=VelocityVec3.y+UnitVelocityVec3.y VelocityVec3.z=VelocityVec3.z+UnitVelocityVec3.z +GroupCount=GroupCount+1 +end end VelocityVec3.x=VelocityVec3.x/GroupCount VelocityVec3.y=VelocityVec3.y/GroupCount @@ -26608,11 +29232,9 @@ BASE:E({"Cannot GetVelocityVec3",Group=self,Alive=self:IsAlive()}) return nil end function GROUP:GetAltitude(FromGround) -self:F2(self.GroupName) return self:GetHeight(FromGround) end function GROUP:GetHeight(FromGround) -self:F2(self.GroupName) local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupUnits=DCSGroup:getUnits() @@ -26632,27 +29254,22 @@ end return nil end function GROUP:GetInitialSize() -self:F3({self.GroupName}) local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupInitialSize=DCSGroup:getInitialSize() -self:T3(GroupInitialSize) return GroupInitialSize end return nil end function GROUP:GetDCSUnits() -self:F2({self.GroupName}) local DCSGroup=self:GetDCSObject() if DCSGroup then local DCSUnits=DCSGroup:getUnits() -self:T3(DCSUnits) return DCSUnits end return nil end function GROUP:Activate(delay) -self:F2({self.GroupName}) if delay and delay>0 then self:ScheduleOnce(delay,GROUP.Activate,self) else @@ -26660,44 +29277,56 @@ trigger.action.activateGroup(self:GetDCSObject()) end return self end +function GROUP:Deactivate(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,GROUP.Deactivate,self) +else +trigger.action.deactivateGroup(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) +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +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) +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +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) +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +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) +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -26717,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -26740,14 +29378,12 @@ 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()}) +BASE:E({"Cannot get COORDINATE",Group=self,Alive=self:IsAlive()}) return nil end function GROUP:GetAverageCoordinate() @@ -26768,19 +29404,27 @@ end end end function GROUP:GetCoordinate() +local vec3=self:GetVec3() +local coord +if vec3 then +coord=COORDINATE:NewFromVec3(vec3) +coord.Heading=self:GetHeading()or 0 +return coord +end 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() +local Heading=self:GetHeading()or 0 FirstUnitCoordinate.Heading=Heading return FirstUnitCoordinate end end end local DCSGroup=Group.getByName(self.GroupName) +if DCSGroup then local DCSUnits=DCSGroup:getUnits()or{} for _,_unit in pairs(DCSUnits)do if Object.isExist(_unit)then @@ -26788,26 +29432,28 @@ local position=_unit:getPosition() local point=position.p~=nil and position.p or _unit:GetPoint() if point then local coord=COORDINATE:NewFromVec3(point) +coord.Heading=0 +local munit=UNIT:Find(_unit) +if munit then +coord.Heading=munit:GetHeading()or 0 +end return coord end 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 @@ -26825,7 +29471,6 @@ 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 @@ -26845,7 +29490,6 @@ end return min,unit end function GROUP:GetFuelAvg() -self:F(self.ControllableName) local DCSControllable=self:GetDCSObject() if DCSControllable then local GroupSize=self:GetSize() @@ -26853,7 +29497,6 @@ local TotalFuel=0 for UnitID,UnitData in pairs(self:GetUnits())do local Unit=UnitData local UnitFuel=Unit:GetFuel()or 0 -self:F({Fuel=UnitFuel}) TotalFuel=TotalFuel+UnitFuel end local GroupFuel=TotalFuel/GroupSize @@ -26866,7 +29509,6 @@ function GROUP:GetFuel() return self:GetFuelAvg() end function GROUP:GetAmmunition() -self:F(self.ControllableName) local DCSControllable=self:GetDCSObject() local Ntot=0 local Nshells=0 @@ -26906,7 +29548,6 @@ end return nil end function GROUP:IsCompletelyInZone(Zone) -self:F2({self.GroupName,Zone}) if not self:IsAlive()then return false end for UnitID,UnitData in pairs(self:GetUnits())do local Unit=UnitData @@ -26918,7 +29559,6 @@ end return true end function GROUP:IsPartlyInZone(Zone) -self:F2({self.GroupName,Zone}) local IsOneUnitInZone=false local IsOneUnitOutsideZone=false if not self:IsAlive()then return false end @@ -26940,7 +29580,6 @@ function GROUP:IsPartlyOrCompletelyInZone(Zone) return self:IsPartlyInZone(Zone)or self:IsCompletelyInZone(Zone) end function GROUP:IsNotInZone(Zone) -self:F2({self.GroupName,Zone}) if not self:IsAlive()then return true end for UnitID,UnitData in pairs(self:GetUnits())do local Unit=UnitData @@ -26961,7 +29600,6 @@ end return false end function GROUP:CountInZone(Zone) -self:F2({self.GroupName,Zone}) local Count=0 if not self:IsAlive()then return Count end for UnitID,UnitData in pairs(self:GetUnits())do @@ -26973,57 +29611,46 @@ end return Count end function GROUP:IsAir() -self:F2(self.GroupName) local DCSGroup=self:GetDCSObject() if DCSGroup then local IsAirResult=DCSGroup:getCategory()==Group.Category.AIRPLANE or DCSGroup:getCategory()==Group.Category.HELICOPTER -self:T3(IsAirResult) return IsAirResult end return nil end function GROUP:IsHelicopter() -self:F2(self.GroupName) local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupCategory=DCSGroup:getCategory() -self:T2(GroupCategory) return GroupCategory==Group.Category.HELICOPTER end return nil end function GROUP:IsAirPlane() -self:F2() local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupCategory=DCSGroup:getCategory() -self:T2(GroupCategory) return GroupCategory==Group.Category.AIRPLANE end return nil end function GROUP:IsGround() -self:F2() local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupCategory=DCSGroup:getCategory() -self:T2(GroupCategory) return GroupCategory==Group.Category.GROUND end return nil end function GROUP:IsShip() -self:F2() local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupCategory=DCSGroup:getCategory() -self:T2(GroupCategory) return GroupCategory==Group.Category.SHIP end return nil end function GROUP:AllOnGround() -self:F2() local DCSGroup=self:GetDCSObject() if DCSGroup then local AllOnGroundResult=true @@ -27032,7 +29659,6 @@ if UnitData:inAir()then AllOnGroundResult=false end end -self:T3(AllOnGroundResult) return AllOnGroundResult end return nil @@ -27058,23 +29684,23 @@ return self:SetAIOnOff(false) end end function GROUP:GetMaxVelocity() -self:F2() local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupVelocityMax=0 for Index,UnitData in pairs(DCSGroup:getUnits())do +if UnitData:isExist()and UnitData:isActive()then local UnitVelocityVec3=UnitData:getVelocity() local UnitVelocity=math.abs(UnitVelocityVec3.x)+math.abs(UnitVelocityVec3.y)+math.abs(UnitVelocityVec3.z) if UnitVelocity>GroupVelocityMax then GroupVelocityMax=UnitVelocity end end +end return GroupVelocityMax end return nil end function GROUP:GetMinHeight() -self:F2() local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupHeightMin=999999999 @@ -27090,7 +29716,6 @@ end return nil end function GROUP:GetMaxHeight() -self:F2() local DCSGroup=self:GetDCSObject() if DCSGroup then local GroupHeightMax=-999999999 @@ -27107,11 +29732,18 @@ return nil end function GROUP:GetTemplate() local GroupName=self:GetName() -return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName)) +local template=_DATABASE:GetGroupTemplate(GroupName) +if template then +return UTILS.DeepCopy(template) +end +return nil end function GROUP:GetTemplateRoutePoints() local GroupName=self:GetName() -return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName).route.points) +local template=_DATABASE:GetGroupTemplate(GroupName) +if template and template.route and template.route.points then +return UTILS.DeepCopy(template.route.points) +end end function GROUP:SetTemplateControlled(Template,Controlled) Template.uncontrolled=not Controlled @@ -27150,12 +29782,10 @@ 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 @@ -27164,12 +29794,10 @@ 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 @@ -27178,7 +29806,6 @@ end return self end function GROUP:InitModex(modex) -self:F({modex=modex}) if modex then self.InitRespawnModex=tonumber(modex) end @@ -27195,26 +29822,34 @@ h=-math.rad(360-course) end return h end +local function TransFormRoute(Template,OldPos,NewPos) +if Template.route and Template.route.points then +for _,_point in ipairs(Template.route.points)do +_point.x=_point.x-OldPos.x+NewPos.x +_point.y=_point.y-OldPos.y+NewPos.y +end +end +return Template +end if self:IsAlive()then +local OldPos=self:GetVec2() 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) +local NewPos={x=Vec3.x,y=Vec3.z} 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) +GroupUnitVec3=COORDINATE:NewFromVec3(From):GetRandomVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) else GroupUnitVec3=Zone:GetVec3() end @@ -27233,19 +29868,18 @@ 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 +Template=TransFormRoute(Template,OldPos,NewPos) 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) +GroupUnitVec3=COORDINATE:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) else GroupUnitVec3=Zone:GetVec3() end @@ -27258,8 +29892,8 @@ Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or 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 +Template=TransFormRoute(Template,OldPos,NewPos) else local units=self:GetUnits() for UnitID,Unit in pairs(Template.units)do @@ -27293,17 +29927,21 @@ if self.InitRespawnModu then Template.modulation=self.InitRespawnModu end self:Destroy(false) -self:T({Template=Template}) -_DATABASE:Spawn(Template) +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end +self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) self:ResetEvents() return self end +function GROUP:Teleport(Coordinate) +self:InitZone(Coordinate) +return self:Respawn(nil,false) +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 @@ -27332,7 +29970,6 @@ 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 @@ -27366,15 +30003,16 @@ 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) +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) +else +return{} +end end function GROUP:CopyRoute(Begin,End,Randomize,Radius) -self:F2({Begin,End}) local Points={} local GroupName=string.match(self:GetName(),".*#") if GroupName then @@ -27382,8 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -self:T3({GroupName}) -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -27405,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -27418,7 +30055,6 @@ if ThreatLevelA2G>MaxThreatLevelA2G then MaxThreatLevelA2G=ThreatLevelA2G end end -self:T3(MaxThreatLevelA2G) return MaxThreatLevelA2G end function GROUP:GetThreatLevel() @@ -27433,20 +30069,17 @@ 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 @@ -27501,6 +30134,8 @@ 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 ballisticMissile=artillery and self:HasAttribute("SS_missile") +local shorad=self:HasAttribute("SR SAM") local ewr=self:HasAttribute("EWR") local ifv=self:HasAttribute("IFV") local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker") @@ -27531,6 +30166,8 @@ elseif sam then attribute=GROUP.Attribute.GROUND_SAM elseif aaa then attribute=GROUP.Attribute.GROUND_AAA +elseif artillery and ballisticMissile then +attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE elseif artillery then attribute=GROUP.Attribute.GROUND_ARTILLERY elseif tank then @@ -27569,7 +30206,6 @@ return attribute end do function GROUP:RouteRTB(RTBAirbase,Speed) -self:F({RTBAirbase:GetName(),Speed}) local DCSGroup=self:GetDCSObject() if DCSGroup then if RTBAirbase then @@ -27578,7 +30214,6 @@ 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) @@ -27604,7 +30239,7 @@ return self end function GROUP:ResetEvents() self:EventDispatcher():Reset(self) -for UnitID,UnitData in pairs(self:GetUnits())do +for UnitID,UnitData in pairs(self:GetUnits()or{})do UnitData:ResetEvents() end return self @@ -27615,7 +30250,7 @@ function GROUP:GetPlayerNames() local HasPlayers=false local PlayerNames={} local Units=self:GetUnits() -for UnitID,UnitData in pairs(Units)do +for UnitID,UnitData in pairs(Units or{})do local Unit=UnitData local PlayerName=Unit:GetPlayerName() if PlayerName and PlayerName~=""then @@ -27625,7 +30260,6 @@ HasPlayers=true end end if HasPlayers==true then -self:F2(PlayerNames) return PlayerNames end return nil @@ -27644,7 +30278,6 @@ return PlayerCount end end function GROUP:EnableEmission(switch) -self:F2(self.GroupName) local switch=switch or false local DCSUnit=self:GetDCSObject() if DCSUnit then @@ -27652,11 +30285,14 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +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 @@ -27668,7 +30304,6 @@ function GROUP:SetCommandImmortal(switch) return self:CommandSetImmortal(switch) end function GROUP:CommandSetImmortal(switch) -self:F2(self.GroupName) if switch==nil then switch=false end @@ -27677,7 +30312,6 @@ 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" @@ -27701,7 +30335,8 @@ return threat,maxtl end return nil,nil end -function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) +function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...) +self:T("GetCustomCallSign") local callsign="Ghost 1" if self:IsAlive()then local IsPlayer=self:IsPlayer() @@ -27712,6 +30347,14 @@ 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 +local playername=shortcallsign +if IsPlayer then playername=self:GetPlayerName()end +self:T2("GetCustomCallSign outcome = "..playername) +if CustomFunction and IsPlayer then +local arguments=arg or{} +local callsign=CustomFunction(groupname,playername,unpack(arguments)) +return callsign +end if CallsignTranslations and CallsignTranslations[callsignroot]then callsignroot=CallsignTranslations[callsignroot] elseif IsPlayer and string.find(groupname,"#")then @@ -27721,8 +30364,8 @@ 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" +elseif IsPlayer and string.find(playername,"|")then +shortcallsign=string.match(playername,"|%s*([%a]+)")or string.match(self:GetPlayerName(),"|%s*([%d]+)")or"Ghost" personalized=true end if personalized then @@ -27789,7 +30432,7 @@ 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 +if unit:IsSAM()then issam=true break end @@ -27797,18 +30440,54 @@ end return issam end function GROUP:IsAAA() -local issam=false +local isAAA=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 +if unit:IsAAA()then +isAAA=true +break end end -return issam +return isAAA +end +function GROUP:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function GROUP:GetBoundingBox() +local bbox={min={x=math.huge,y=math.huge,z=math.huge}, +max={x=-math.huge,y=-math.huge,z=-math.huge} +} +local Units=self:GetUnits()or{} +if#Units==0 then +return nil +end +for _,unit in pairs(Units)do +if unit and unit:IsAlive()then +local ubox=unit:GetBoundingBox() +if ubox then +if ubox.min.xbbox.max.x then +bbox.max.x=ubox.max.x +end +if ubox.max.y>bbox.max.y then +bbox.max.y=ubox.max.y +end +if ubox.max.z>bbox.max.z then +bbox.max.z=ubox.max.z +end +end +end +end +return bbox end UNIT={ ClassName="UNIT", @@ -27824,6 +30503,7 @@ if unit then local group=unit:getGroup() if group then self.GroupName=group:getName() +self.groupId=group:getID() end self.DCSUnit=unit end @@ -27865,14 +30545,23 @@ function UNIT:Name() return self.UnitName end function UNIT:GetDCSObject() +if(not self.LastCallDCSObject)or(self.LastCallDCSObject and timer.getTime()-self.LastCallDCSObject>1)or(self.DCSObject==nil)or(self.DCSObject:isExist()==false)then local DCSUnit=Unit.getByName(self.UnitName) if DCSUnit then +self.LastCallDCSObject=timer.getTime() +self.DCSObject=DCSUnit return DCSUnit +else +self.DCSObject=nil +self.LastCallDCSObject=nil +end +else +return self.DCSObject end return nil end function UNIT:GetAltitude(FromGround) -local DCSUnit=Unit.getByName(self.UnitName) +local DCSUnit=self:GetDCSObject() if DCSUnit then local altitude=0 local point=DCSUnit:getPoint() @@ -27886,19 +30575,14 @@ 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() @@ -27906,22 +30590,17 @@ 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() @@ -27932,7 +30611,6 @@ 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 @@ -27941,7 +30619,6 @@ 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 @@ -27949,11 +30626,12 @@ i=i+1 end end SpawnGroupTemplate.groupId=nil -self:T(SpawnGroupTemplate) +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units) +end _DATABASE:Spawn(SpawnGroupTemplate) end function UNIT:IsActive() -self:F2(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then local UnitIsActive=DCSUnit:isActive() @@ -27970,7 +30648,6 @@ 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() @@ -27982,7 +30659,6 @@ 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() @@ -27991,13 +30667,27 @@ 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 +if not group then +return false +end +local template=group:GetTemplate() +if(template==nil)or(template.units==nil)then +local DCSObject=self:GetDCSObject() +if DCSObject then +if DCSObject:getPlayerName()~=nil then +return true +else +return false +end +else +return false +end +end +local units=template.units for _,unit in pairs(units)do if unit.name==self:GetName()and(unit.skill=="Client"or unit.skill=="Player")then return true @@ -28006,7 +30696,6 @@ end return false end function UNIT:GetPlayerName() -self:F(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then local PlayerName=DCSUnit:getPlayerName() @@ -28032,7 +30721,6 @@ 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() @@ -28041,16 +30729,14 @@ end return nil end function UNIT:GetSpeedMax() -self:F2(self.UnitName) local Desc=self:GetDesc() if Desc then -local SpeedMax=Desc.speedMax +local SpeedMax=Desc.speedMax or 0 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 @@ -28064,7 +30750,6 @@ end return nil end function UNIT:IsRefuelable() -self:F2(self.UnitName) local refuelable=self:HasAttribute("Refuelable") local system=nil local Desc=self:GetDesc() @@ -28074,7 +30759,6 @@ end return refuelable,system end function UNIT:IsTanker() -self:F2(self.UnitName) local tanker=self:HasAttribute("Tankers") local system=nil if tanker then @@ -28126,7 +30810,6 @@ end return false end function UNIT:GetGroup() -self:F2(self.UnitName) local UnitGroup=GROUP:FindByName(self.GroupName) if UnitGroup then return UnitGroup @@ -28143,17 +30826,14 @@ 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() @@ -28175,6 +30855,8 @@ local nrockets=0 local nmissiles=0 local nbombs=0 local narti=0 +local nAPshells=0 +local nHEshells=0 local unit=self local ammotable=unit:GetAmmo() if ammotable then @@ -28192,6 +30874,12 @@ 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 +if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_AP",1,true)then +nAPshells=nAPshells+Nammo +end +if ammotable[w].desc.typeName and(string.find(ammotable[w].desc.typeName,"_HE",1,true)or string.find(ammotable[w].desc.typeName,"HESH",1,true))then +nHEshells=nHEshells+Nammo +end elseif Category==Weapon.Category.ROCKET then nrockets=nrockets+Nammo elseif Category==Weapon.Category.BOMB then @@ -28214,10 +30902,45 @@ end end end nammo=nshells+nrockets+nmissiles+nbombs -return nammo,nshells,nrockets,nbombs,nmissiles,narti +return nammo,nshells,nrockets,nbombs,nmissiles,narti,nAPshells,nHEshells +end +function UNIT:HasAPShells() +local _,_,_,_,_,_,shells=self:GetAmmunition() +if shells>0 then +return true +else +return false +end +end +function UNIT:GetAPShells() +local _,_,_,_,_,_,shells=self:GetAmmunition() +return shells or 0 +end +function UNIT:GetHEShells() +local _,_,_,_,_,_,_,shells=self:GetAmmunition() +return shells or 0 +end +function UNIT:HasHEShells() +local _,_,_,_,_,_,_,shells=self:GetAmmunition() +if shells>0 then +return true +else +return false +end +end +function UNIT:HasArtiShells() +local _,_,_,_,_,shells=self:GetAmmunition() +if shells>0 then +return true +else +return false +end +end +function UNIT:GetArtiShells() +local _,_,_,_,_,shells=self:GetAmmunition() +return shells or 0 end function UNIT:GetSensors() -self:F2(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then local UnitSensors=DCSUnit:getSensors() @@ -28226,7 +30949,6 @@ end return nil end function UNIT:HasSensors(...) -self:F2(arg) local DCSUnit=self:GetDCSObject() if DCSUnit then local HasSensors=DCSUnit:hasSensors(unpack(arg)) @@ -28235,7 +30957,6 @@ end return nil end function UNIT:HasSEAD() -self:F2() local DCSUnit=self:GetDCSObject() if DCSUnit then local UnitSEADAttributes=DCSUnit:getDesc().attributes @@ -28251,7 +30972,6 @@ end return nil end function UNIT:GetRadar() -self:F2(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then local UnitRadarOn,UnitRadarObject=DCSUnit:getRadar() @@ -28260,7 +30980,6 @@ end return nil,nil end function UNIT:GetFuel() -self:F3(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then local UnitFuel=DCSUnit:getFuel() @@ -28269,18 +30988,15 @@ 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() @@ -28289,7 +31005,6 @@ end return-1 end function UNIT:GetLife0() -self:F2(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then local UnitLife0=DCSUnit:getLife0() @@ -28298,7 +31013,6 @@ 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() @@ -28307,7 +31021,6 @@ end return-1 end function UNIT:GetDamageRelative() -self:F2(self.UnitName) if self and self:IsAlive()then return 1-self:GetLifeRelative() end @@ -28322,7 +31035,6 @@ end return 0 end function UNIT:GetUnitCategory() -self:F3(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then return DCSUnit:getDesc().category @@ -28330,7 +31042,6 @@ end return nil end function UNIT:GetCategoryName() -self:F3(self.UnitName) local DCSUnit=self:GetDCSObject() if DCSUnit then local CategoryNames={ @@ -28341,7 +31052,6 @@ local CategoryNames={ [Unit.Category.STRUCTURE]="Structure", } local UnitCategory=DCSUnit:getDesc().category -self:T3(UnitCategory) return CategoryNames[UnitCategory] end return nil @@ -28354,84 +31064,112 @@ 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" +[1]="Unarmed", +[2]="Infantry", +[3]="Old Tanks & APCs", +[4]="Tanks & IFVs without ATGM", +[5]="Tanks & IFV with ATGM", +[6]="Modern Tanks", +[7]="AAA", +[8]="IR Guided SAMs", +[9]="SR SAMs", +[10]="MR SAMs", +[11]="LR SAMs" } -if Attributes["LR SAM"]then ThreatLevel=10 -elseif Attributes["MR SAM"]then ThreatLevel=9 +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 +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 +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 +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 +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" +[1]="Unarmed", +[2]="Tanker", +[3]="AWACS", +[4]="Transport Helicopter", +[5]="UAV", +[6]="Bomber", +[7]="Strategic Bomber", +[8]="Attack Helicopter", +[9]="Battleplane", +[10]="Multirole Fighter", +[11]="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 +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" +[1]="Unarmed ship", +[2]="Light armed ships", +[3]="Corvettes", +[4]="", +[5]="Frigates", +[6]="", +[7]="Cruiser", +[8]="", +[9]="Destroyer", +[10]="", +[11]="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 +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 @@ -28452,47 +31190,37 @@ 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() @@ -28507,7 +31235,6 @@ if Velocity<1 and Height<=60 then UnitInAir=false end end -self:T3(UnitInAir) return UnitInAir end return nil @@ -28592,7 +31319,6 @@ end return nil end function UNIT:EnableEmission(switch) -self:F2(self.UnitName) local switch=switch or false local DCSUnit=self:GetDCSObject() if DCSUnit then @@ -28601,18 +31327,20 @@ 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" +local skill="Random" +if _DATABASE.Templates.Units[name]and _DATABASE.Templates.Units[name].Template and _DATABASE.Templates.Units[name].Template.skill then +skill=_DATABASE.Templates.Units[name].Template.skill or"Random" +end 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 then if template.AddPropAircraft then if template.AddPropAircraft.STN_L16 then STN=template.AddPropAircraft.STN_L16 @@ -28628,8 +31356,79 @@ end if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then FGL=template.datalinks.SADL.settings.flightLead end +end return STN,VCL,VCN,FGL end +do +function UNIT:SetAIOnOff(AIOnOff) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local DCSController=DCSUnit:getController() +if DCSController then +DCSController:setOnOff(AIOnOff) +return self +end +end +return nil +end +function UNIT:SetAIOn() +return self:SetAIOnOff(true) +end +function UNIT:SetAIOff() +return self:SetAIOnOff(false) +end +end +function UNIT:IsSAM() +if self:HasSEAD()and self:IsGround()and(not self:HasAttribute("Mobile AAA"))then +return true +end +return false +end +function UNIT:IsEWR() +if self:IsGround()then +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local attrs=DCSUnit:getDesc().attributes +return attrs["EWR"]==true +end +end +return false +end +function UNIT:IsAAA() +local unit=self +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 +return true +end +return false +end +function UNIT:SetLife(Percent) +net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)",self:GetID(),Percent)) +end +function UNIT:SetCarrierIlluminationMode(Mode) +UTILS.SetCarrierIlluminationMode(self:GetID(),Mode) +end +function UNIT:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -28695,6 +31494,9 @@ function CLIENT:AddPlayer(PlayerName) table.insert(self.Players,PlayerName) return self end +function CLIENT:CountPlayers() +return#self.Players or 0 +end function CLIENT:GetPlayers() return self.Players end @@ -28763,7 +31565,7 @@ self.AliveCheckScheduler:NoTrace() return self end function CLIENT:_AliveCheckScheduler(SchedulerName) -self:F3({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack}) +self:T2({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack}) if self:IsAlive()then if self.ClientAlive2==false then self:ShowBriefing() @@ -28931,7 +31733,10 @@ local DCSStatic=StaticObject.getByName(self.StaticName) if DCSStatic then local Life0=DCSStatic:getLife()or 1 self.Life0=Life0 +else +self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -28944,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -29009,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -29019,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -29028,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -29050,6 +31872,20 @@ end end return GroupsFound end +function STATIC:GetStaticStorage() +local name=self:GetName() +local storage=STORAGE:NewFromStaticCargo(name) +return storage +end +function STATIC:GetCargoWeight() +local DCSObject=StaticObject.getByName(self.StaticName) +local mass=-1 +if DCSObject then +mass=DCSObject:getCargoWeight()or 0 +local masstxt=DCSObject:getCargoDisplayName()or"none" +end +return mass +end AIRBASE={ ClassName="AIRBASE", CategoryName={ @@ -29103,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -29111,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -29133,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -29143,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -29161,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -29168,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -29186,10 +32033,10 @@ 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -29232,14 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", -["Amman"]="Amman", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -29247,6 +32094,7 @@ AIRBASE.Syria={ ["Gaziantep"]="Gaziantep", ["Gazipasa"]="Gazipasa", ["Gecitkale"]="Gecitkale", +["H"]="H", ["H3"]="H3", ["H3_Northwest"]="H3 Northwest", ["H3_Southwest"]="H3 Southwest", @@ -29254,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -29269,12 +32118,14 @@ AIRBASE.Syria={ ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -29291,13 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["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", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -29305,9 +32157,21 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } +AIRBASE.MarianaIslandsWWII={ +["Agana"]="Agana", +["Airfield_3"]="Airfield 3", +["Charon_Kanoa"]="Charon Kanoa", +["Gurguan_Point"]="Gurguan Point", +["Isley"]="Isley", +["Kagman"]="Kagman", +["Marpi"]="Marpi", +["Orote"]="Orote", +["Pagan"]="Pagan", +["Rota"]="Rota", +["Ushi"]="Ushi", +} AIRBASE.SouthAtlantic={ ["Almirante_Schroeders"]="Almirante Schroeders", -["Caleta_Tortel"]="Caleta Tortel", ["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena", ["Cullen"]="Cullen", ["El_Calafate"]="El Calafate", @@ -29338,49 +32202,378 @@ AIRBASE.SouthAtlantic={ AIRBASE.Sinai={ ["Abu_Rudeis"]="Abu Rudeis", ["Abu_Suwayr"]="Abu Suwayr", +["Al_Bahr_al_Ahmar"]="Al Bahr al Ahmar", ["Al_Ismailiyah"]="Al Ismailiyah", +["Al_Khatatbah"]="Al Khatatbah", ["Al_Mansurah"]="Al Mansurah", +["Al_Rahmaniyah_Air_Base"]="Al Rahmaniyah Air Base", ["As_Salihiyah"]="As Salihiyah", ["AzZaqaziq"]="AzZaqaziq", ["Baluza"]="Baluza", ["Ben_Gurion"]="Ben-Gurion", +["Beni_Suef"]="Beni Suef", ["Bilbeis_Air_Base"]="Bilbeis Air Base", ["Bir_Hasanah"]="Bir Hasanah", +["Birma_Air_Base"]="Birma Air Base", +["Borg_El_Arab_International_Airport"]="Borg El Arab International Airport", ["Cairo_International_Airport"]="Cairo International Airport", ["Cairo_West"]="Cairo West", +["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", +["El_Minya"]="El Minya", ["Fayed"]="Fayed", +["Gebel_El_Basur_Air_Base"]="Gebel El Basur Air Base", ["Hatzerim"]="Hatzerim", ["Hatzor"]="Hatzor", +["Hurghada_International_Airport"]="Hurghada International Airport", ["Inshas_Airbase"]="Inshas Airbase", +["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", +["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", +["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", ["Ovda"]="Ovda", -["Palmahim"]="Palmahim", +["Palmachim"]="Palmachim", +["Quwaysina"]="Quwaysina", +["Rafic_Hariri_Intl"]="Rafic Hariri Intl", +["Ramat_David"]="Ramat David", ["Ramon_Airbase"]="Ramon Airbase", +["Ramon_International_Airport"]="Ramon International Airport", ["Sde_Dov"]="Sde Dov", +["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", +["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", +["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ -["Bas_100"]="Bas 100", +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", +["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", +["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", +["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", +["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", -["Lakselv"]="Lakselv", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", +["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", -["Olenegorsk"]="Olenegorsk", +["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", -["Severomorsk1"]="Severomorsk1", -["Severomorsk3"]="Severomorsk3", +["Severomorsk_1"]="Severomorsk-1", +["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", +["Vidsel"]="Vidsel", +["Vuojarvi"]="Vuojarvi", +} +AIRBASE.Afghanistan={ +["Bagram"]="Bagram", +["Bamyan"]="Bamyan", +["Bost"]="Bost", +["Camp_Bastion"]="Camp Bastion", +["Camp_Bastion_Heliport"]="Camp Bastion Heliport", +["Chaghcharan"]="Chaghcharan", +["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", +["Farah"]="Farah", +["Gardez"]="Gardez", +["Ghazni_Heliport"]="Ghazni Heliport", +["Herat"]="Herat", +["Jalalabad"]="Jalalabad", +["Kabul"]="Kabul", +["Kandahar"]="Kandahar", +["Kandahar_Heliport"]="Kandahar Heliport", +["Khost"]="Khost", +["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", +["Nimroz"]="Nimroz", +["Qala_i_Naw"]="Qala i Naw", +["Sharana"]="Sharana", +["Shindand"]="Shindand", +["Shindand_Heliport"]="Shindand Heliport", +["Tarinkot"]="Tarinkot", +["Urgoon_Heliport"]="Urgoon Heliport", +} +AIRBASE.Iraq={ +["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", +["Al_Salam_Airbase"]="Al-Salam Airbase", +["Al_Taji_Airport"]="Al-Taji Airport", +["Al_Taquddum_Airport"]="Al-Taquddum Airport", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", +["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", +} +AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", +["Airracing_Frankfurt"]="Airracing Frankfurt", +["Airracing_Koblenz"]="Airracing Koblenz", +["Airracing_Luebeck"]="Airracing Lubeck", +["Allstedt"]="Allstedt", +["Altes_Lager"]="Altes Lager", +["Bad_Duerkheim"]="Bad Durkheim", +["Barth"]="Barth", +["Bienenfarm"]="Bienenfarm", +["Bindersleben"]="Bindersleben", +["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", +["Braunschweig"]="Braunschweig", +["Bremen"]="Bremen", +["Briest"]="Briest", +["Buechel"]="Buchel", +["Bueckeburg"]="Buckeburg", +["Celle"]="Celle", +["Chojna"]="Chojna", +["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", +["Damgarten"]="Damgarten", +["Dedelow"]="Dedelow", +["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", +["Fassberg"]="Fassberg", +["Finow"]="Finow", +["Frankfurt"]="Frankfurt", +["Fritzlar"]="Fritzlar", +["Fulda"]="Fulda", +["Gardelegen"]="Gardelegen", +["Garz"]="Garz", +["Gatow"]="Gatow", +["Gelnhausen"]="Gelnhausen", +["Giebelstadt"]="Giebelstadt", +["Glindbruchkippe"]="Glindbruchkippe", +["Gross_Mohrdorf"]="Gross Mohrdorf", +["Grosse_Wiese"]="Grosse Wiese", +["Guetersloh"]="Gutersloh", +["H_FRG_01"]="H FRG 01", +["H_FRG_02"]="H FRG 02", +["H_FRG_03"]="H FRG 03", +["H_FRG_04"]="H FRG 04", +["H_FRG_05"]="H FRG 05", +["H_FRG_06"]="H FRG 06", +["H_FRG_07"]="H FRG 07", +["H_FRG_08"]="H FRG 08", +["H_FRG_09"]="H FRG 09", +["H_FRG_10"]="H FRG 10", +["H_FRG_11"]="H FRG 11", +["H_FRG_12"]="H FRG 12", +["H_FRG_13"]="H FRG 13", +["H_FRG_14"]="H FRG 14", +["H_FRG_15"]="H FRG 15", +["H_FRG_16"]="H FRG 16", +["H_FRG_17"]="H FRG 17", +["H_FRG_18"]="H FRG 18", +["H_FRG_19"]="H FRG 19", +["H_FRG_20"]="H FRG 20", +["H_FRG_21"]="H FRG 21", +["H_FRG_23"]="H FRG 23", +["H_FRG_25"]="H FRG 25", +["H_FRG_27"]="H FRG 27", +["H_FRG_30"]="H FRG 30", +["H_FRG_31"]="H FRG 31", +["H_FRG_32"]="H FRG 32", +["H_FRG_34"]="H FRG 34", +["H_FRG_38"]="H FRG 38", +["H_FRG_39"]="H FRG 39", +["H_FRG_40"]="H FRG 40", +["H_FRG_41"]="H FRG 41", +["H_FRG_42"]="H FRG 42", +["H_FRG_43"]="H FRG 43", +["H_FRG_44"]="H FRG 44", +["H_FRG_45"]="H FRG 45", +["H_FRG_46"]="H FRG 46", +["H_FRG_47"]="H FRG 47", +["H_FRG_48"]="H FRG 48", +["H_FRG_49"]="H FRG 49", +["H_FRG_50"]="H FRG 50", +["H_FRG_51"]="H FRG 51", +["H_GDR_01"]="H GDR 01", +["H_GDR_02"]="H GDR 02", +["H_GDR_03"]="H GDR 03", +["H_GDR_04"]="H GDR 04", +["H_GDR_05"]="H GDR 05", +["H_GDR_06"]="H GDR 06", +["H_GDR_07"]="H GDR 07", +["H_GDR_08"]="H GDR 08", +["H_GDR_09"]="H GDR 09", +["H_GDR_10"]="H GDR 10", +["H_GDR_11"]="H GDR 11", +["H_GDR_12"]="H GDR 12", +["H_GDR_13"]="H GDR 13", +["H_GDR_14"]="H GDR 14", +["H_GDR_15"]="H GDR 15", +["H_GDR_16"]="H GDR 16", +["H_GDR_17"]="H GDR 17", +["H_GDR_18"]="H GDR 18", +["H_GDR_19"]="H GDR 19", +["H_GDR_21"]="H GDR 21", +["H_GDR_22"]="H GDR 22", +["H_GDR_24"]="H GDR 24", +["H_GDR_25"]="H GDR 25", +["H_GDR_26"]="H GDR 26", +["H_GDR_30"]="H GDR 30", +["H_GDR_31"]="H GDR 31", +["H_GDR_32"]="H GDR 32", +["H_GDR_33"]="H GDR 33", +["H_GDR_34"]="H GDR 34", +["H_Med_FRG_01"]="H Med FRG 01", +["H_Med_FRG_02"]="H Med FRG 02", +["H_Med_FRG_04"]="H Med FRG 04", +["H_Med_FRG_06"]="H Med FRG 06", +["H_Med_FRG_11"]="H Med FRG 11", +["H_Med_FRG_12"]="H Med FRG 12", +["H_Med_FRG_13"]="H Med FRG 13", +["H_Med_FRG_14"]="H Med FRG 14", +["H_Med_FRG_15"]="H Med FRG 15", +["H_Med_FRG_16"]="H Med FRG 16", +["H_Med_FRG_17"]="H Med FRG 17", +["H_Med_FRG_21"]="H Med FRG 21", +["H_Med_FRG_24"]="H Med FRG 24", +["H_Med_FRG_26"]="H Med FRG 26", +["H_Med_FRG_27"]="H Med FRG 27", +["H_Med_FRG_29"]="H Med FRG 29", +["H_Med_GDR_01"]="H Med GDR 01", +["H_Med_GDR_02"]="H Med GDR 02", +["H_Med_GDR_03"]="H Med GDR 03", +["H_Med_GDR_08"]="H Med GDR 08", +["H_Med_GDR_09"]="H Med GDR 09", +["H_Med_GDR_10"]="H Med GDR 10", +["H_Med_GDR_11"]="H Med GDR 11", +["H_Med_GDR_12"]="H Med GDR 12", +["H_Med_GDR_13"]="H Med GDR 13", +["H_Med_GDR_14"]="H Med GDR 14", +["H_Med_GDR_16"]="H Med GDR 16", +["H_Radar_FRG_02"]="H Radar FRG 02", +["H_Radar_GDR_01"]="H Radar GDR 01", +["H_Radar_GDR_02"]="H Radar GDR 02", +["H_Radar_GDR_03"]="H Radar GDR 03", +["H_Radar_GDR_04"]="H Radar GDR 04", +["H_Radar_GDR_05"]="H Radar GDR 05", +["H_Radar_GDR_06"]="H Radar GDR 06", +["H_Radar_GDR_07"]="H Radar GDR 07", +["H_Radar_GDR_08"]="H Radar GDR 08", +["H_Radar_GDR_09"]="H Radar GDR 09", +["Hahn"]="Hahn", +["Haina"]="Haina", +["Hamburg"]="Hamburg", +["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", +["Hannover"]="Hannover", +["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", +["Herrenteich"]="Herrenteich", +["Hildesheim"]="Hildesheim", +["Hockenheim"]="Hockenheim", +["Holzdorf"]="Holzdorf", +["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", +["Koethen"]="Kothen", +["Laage"]="Laage", +["Landstuhl"]="Landstuhl", +["Langenselbold"]="Langenselbold", +["Laerz"]="Larz", +["Leipzig_Mockau"]="Leipzig Mockau", +["Luebeck"]="Lubeck", +["Lueneburg"]="Luneburg", +["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", +["Mendig"]="Mendig", +["Merseburg"]="Merseburg", +["Neubrandenburg"]="Neubrandenburg", +["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", +["Northeim"]="Northeim", +["Noervenich"]="Norvenich", +["Ober_Moerlen"]="Ober-Morlen", +["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", +["Parchim"]="Parchim", +["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", +["Pferdsfeld"]="Pferdsfeld", +["Pinnow"]="Pinnow", +["Pottschutthoehe"]="Pottschutthohe", +["Ramstein"]="Ramstein", +["Revinge"]="Revinge", +["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", +["Schoenefeld"]="Schonefeld", +["Schweinfurt"]="Schweinfurt", +["Sembach"]="Sembach", +["Sittensen"]="Sittensen", +["Spangdahlem"]="Spangdahlem", +["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", +["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", +["Tegel"]="Tegel", +["Tempelhof"]="Tempelhof", +["Templin"]="Templin", +["Thurland"]="Thurland", +["Tutow"]="Tutow", +["Uelzen"]="Uelzen", +["Uetersen"]="Uetersen", +["Ummern"]="Ummern", +["Verden_Scharnhorst"]="Verden-Scharnhorst", +["Walldorf"]="Walldorf", +["Waren_Vielist"]="Waren Vielist", +["Werneuchen"]="Werneuchen", +["Weser_Wuemme"]="Weser Wumme", +["Wiesbaden"]="Wiesbaden", +["Wismar"]="Wismar", +["Wittstock"]="Wittstock", +["Worms"]="Worms", +["Wunstorf"]="Wunstorf", +["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", +["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ Runway=16, @@ -29392,6 +32585,7 @@ OpenBig=104, OpenMedOrBig=176, HelicopterUsable=216, FighterAircraft=244, +FighterAircraftSmall=344, } AIRBASE.SpotStatus={ FREE="Free", @@ -29406,8 +32600,9 @@ 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 +elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01"then self.isHelipad=true +self.category=Airbase.Category.HELIPAD elseif self.category==Airbase.Category.SHIP then self.isShip=true if self.descriptors.typeName=="Oil rig"or self.descriptors.typeName=="Ga"then @@ -29420,10 +32615,16 @@ else self:E("ERROR: Unknown airbase category!") end self:_InitRunways() -if self.isAirdrome then +local Nrunways=#self.runways +if Nrunways>0 then self:SetActiveRunway() end self:_InitParkingSpots() +if self.category==Airbase.Category.AIRDROME and(Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly])then +self.category=Airbase.Category.HELIPAD +self.isAirdrome=true +self.isHelipad=true +end local vec2=self:GetVec2() self:GetCoordinate() self.storage=_DATABASE:AddStorage(AirbaseName) @@ -29439,9 +32640,48 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end +function AIRBASE:_GetCategory() +local name=self.AirbaseName +local static=StaticObject.getByName(name) +local airbase=Airbase.getByName(name) +local unit=Unit.getByName(name) +local text=string.format("\n=====================================================") +text=text..string.format("\nAirbase %s:",name) +if static then +local oc,uc=static:getCategory() +local ex=static:getCategoryEx() +text=text..string.format("\nSTATIC: oc=%d, uc=%d, ex=%d",oc,uc,ex) +text=text..string.format("\n--------------------------------------------------") +end +if unit then +local oc,uc=unit:getCategory() +local ex=unit:getCategoryEx() +text=text..string.format("\nUNIT: oc=%d, uc=%d, ex=%d",oc,uc,ex) +text=text..string.format("\n--------------------------------------------------") +end +if airbase then +local oc,uc=airbase:getCategory() +local ex=airbase:getCategoryEx() +text=text..string.format("\nAIRBASE: oc=%d, uc=%d, ex=%d",oc,uc,ex) +text=text..string.format("\n--------------------------------------------------") +text=text..UTILS.PrintTableToLog(airbase:getDesc(),nil,true) +end +text=text..string.format("\n=====================================================") +env.info(text) +end function AIRBASE:Find(DCSAirbase) local AirbaseName=DCSAirbase:getName() local AirbaseFound=_DATABASE:FindAirbase(AirbaseName) @@ -29659,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -29695,13 +32962,14 @@ 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 +if self._CheckTerminalType(park.TerminalType,terminalType)then self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 end end self.parkingByID[park.TerminalID]=park table.insert(self.parking,park) end +self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway] return self end function AIRBASE:_GetParkingSpotByID(TerminalID) @@ -29946,6 +33214,10 @@ if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.Op match=true end elseif termtype==AIRBASE.TerminalType.FighterAircraft then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then +match=true +end +elseif termtype==AIRBASE.TerminalType.FighterAircraftSmall 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 @@ -29963,6 +33235,7 @@ if Name then for _,_runway in pairs(self.runways)do local runway=_runway local name=self:GetRunwayName(runway) +self:T("Check Runway Name: "..name) if name==Name:upper()then return runway end @@ -29976,11 +33249,8 @@ 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) +self:T("Create Runway: name = "..name) local bearing=-1*course local heading=math.deg(bearing) local runway={} @@ -29991,6 +33261,7 @@ else runway.name=string.format("%02d",tonumber(name)) end runway.magheading=tonumber(runway.name)*10 +runway.idx=runway.magheading runway.heading=heading runway.width=width or 0 runway.length=length or 0 @@ -30028,7 +33299,7 @@ local airbase=self:GetDCSObject() if airbase then local runways=airbase:getRunways() self:T2(runways) -if runways then +if runways and#runways>0 then for _,rwy in pairs(runways)do self:T(rwy) local runway=_createRunway(rwy.Name,rwy.course,rwy.width,rwy.length,rwy.position) @@ -30043,6 +33314,9 @@ local runway=_createRunway(name2,rwy.course-math.pi,rwy.width,rwy.length,rwy.pos table.insert(Runways,runway) end end +else +self.runways={} +return{} end end local rpairs={} @@ -30170,14 +33444,47 @@ local idx=string.format("%02d",UTILS.Round((hdg-magvar)/10,0)) local runway={} runway.heading=hdg runway.idx=idx +runway.magheading=idx runway.length=c1:Get2DDistance(c2) runway.position=c1 runway.endpoint=c2 +self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d",self:GetName(),runway.idx,runway.heading,runway.length,i,j)) 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 +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.position +local a=UTILS.VecTranslate(c0,1000,ri.heading) +local b=UTILS.VecSubstract(rj.position,ri.position) +b=UTILS.VecAdd(ri.position,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 return runways end function AIRBASE:SetActiveRunway(Name,PreferLeft) @@ -30310,21 +33617,63 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject -if self.SceneryObject then -self.Life0=self.SceneryObject:getLife() +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if self.SceneryObject and self.SceneryObject.getLife then +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) return self.Properties[PropertyName] end +function SCENERY:HasProperty(PropertyName) +return self.Properties[PropertyName]~=nil and true or false +end function SCENERY:GetAllProperties() return self.Properties end @@ -30335,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 -if self.SceneryObject then +local life=1 +if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -30368,20 +33739,23 @@ end function SCENERY:GetRelativeLife() local life=self:GetLife() local life0=self:GetLife0() +if life==0 or life0==0 then return 0 end local rlife=math.floor((life/life0)*100) return rlife end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -30400,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -30424,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -30453,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -30810,7 +34199,8 @@ 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 name then +self:T(self.lid..string.format("Got Target Object %s, category=%d",name,category)) if category==Object.Category.UNIT then target=UNIT:FindByName(name) elseif category==Object.Category.STATIC then @@ -30822,6 +34212,7 @@ self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet end end end +end return target end function WEAPON:GetTargetDistance(ConversionFunction) @@ -31058,7 +34449,7 @@ end do NET={ ClassName="NET", -Version="0.1.3", +Version="0.1.4", BlockTime=600, BlockedPilots={}, BlockedUCIDs={}, @@ -31076,6 +34467,9 @@ self.BlockedPilots={} self.KnownPilots={} self:SetBlockMessage() self:SetUnblockMessage() +self.BlockedSides={} +self.BlockedSides[1]=false +self.BlockedSides[2]=false self:SetStartState("Stopped") self:AddTransition("Stopped","Run","Running") self:AddTransition("*","PlayerJoined","*") @@ -31091,23 +34485,26 @@ self:Run() return self end function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) +self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot}) local blocked=false local TNow=timer.getTime() if UCID and self.BlockedUCIDs[UCID]and TNow3)then @@ -31222,7 +34627,6 @@ 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 @@ -31230,10 +34634,9 @@ 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 +self.BlockedSides[Side]=false end return self end @@ -31292,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -31300,8 +34703,11 @@ end return nil end function NET:GetPlayerIDFromClient(Client) +self:T("GetPlayerIDFromClient") +self:T({Client=Client}) if Client then local name=Client:GetPlayerName() +self:T({name=name}) local id=self:GetPlayerIDByName(name) return id else @@ -31328,18 +34734,6 @@ 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() @@ -31398,9 +34792,12 @@ return nil end end function NET:GetSlot(Client) +self:T("NET.GetSlot") local PlayerID=self:GetPlayerIDFromClient(Client) +self:T("NET.GetSlot PlayerID = "..tostring(PlayerID)) if PlayerID then local side,slot=net.get_slot(tonumber(PlayerID)) +self:T("NET.GetSlot side, slot = "..tostring(side)..","..tostring(slot)) return side,slot else return nil,nil @@ -31408,14 +34805,16 @@ 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'') +local SlotID=SlotID or Client:GetID() +if PlayerID then +return net.force_player_slot(tonumber(PlayerID),SideID,SlotID) else return false end end function NET:ReturnToSpectators(Client) local outcome=self:ForceSlot(Client,0) +local sched=TIMER:New(Client.Destroy,Client,1):Start(1) return outcome end function NET.Lua2Json(Lua) @@ -31447,7 +34846,7 @@ 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 +if type(entry)=="number"and entry>=TNow then entry=false end end end HouseHold(self.BlockedPilots) @@ -31492,14 +34891,43 @@ GASOLINE=1, MW50=2, DIESEL=3, } -STORAGE.version="0.0.1" +STORAGE.LiquidName={ +GASOLINE="gasoline", +DIESEL="diesel", +MW50="methanol_mixture", +JETFUEL="jet_fuel", +} +STORAGE.Type={ +WEAPONS="weapons", +LIQUIDS="liquids", +AIRCRAFT="aircrafts", +} +STORAGE.version="0.1.5" function STORAGE:New(AirbaseName) local self=BASE:Inherit(self,BASE:New()) self.airbase=Airbase.getByName(AirbaseName) -if Airbase.getWarehouse then +if Airbase.getWarehouse and self.airbase then self.warehouse=self.airbase:getWarehouse() end -self.lid=string.format("STORAGE %s",AirbaseName) +self.lid=string.format("STORAGE %s | ",AirbaseName) +return self +end +function STORAGE:NewFromStaticCargo(StaticCargoName) +local self=BASE:Inherit(self,BASE:New()) +self.airbase=StaticObject.getByName(StaticCargoName) +if Airbase.getWarehouse then +self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) +end +self.lid=string.format("STORAGE %s | ",StaticCargoName) +return self +end +function STORAGE:NewFromDynamicCargo(DynamicCargoName) +local self=BASE:Inherit(self,BASE:New()) +self.airbase=Unit.getByName(DynamicCargoName)or StaticObject.getByName(DynamicCargoName) +if Airbase.getWarehouse then +self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) +end +self.lid=string.format("STORAGE %s | ",DynamicCargoName) return self end function STORAGE:FindByName(AirbaseName) @@ -31508,6 +34936,10 @@ return storage end function STORAGE:SetVerbosity(VerbosityLevel) self.verbose=VerbosityLevel or 0 +if self.verbose>1 then +BASE:TraceOn() +BASE:TraceClass("STORAGE") +end return self end function STORAGE:AddItem(Name,Amount) @@ -31602,11 +35034,11 @@ local unlimited=false if N>0 then self:RemoveAmount(Type,1) local n=self:GetAmount(Type) -unlimited=n==N +unlimited=unlimited or n>2^29 or 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)) +self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)",tostring(Type),tostring(unlimited),N,n)) end return unlimited end @@ -31642,6 +35074,499 @@ function STORAGE:GetInventory(Item) local inventory=self.warehouse:getInventory(Item) return inventory.aircraft,inventory.liquids,inventory.weapon end +function STORAGE:SaveToFile(Path,Filename) +if not io then +BASE:E("ERROR: io not desanitized. Can't save the files.") +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 given path.") +end +local ac,lq,wp=self:GetInventory() +local DataAircraft="" +local DataLiquids="" +local DataWeapons="" +if#lq>0 then +DataLiquids=DataLiquids.."Liquids in Storage:\n" +for key,amount in pairs(lq)do +DataLiquids=DataLiquids..tostring(key).."="..tostring(amount).."\n" +end +UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids) +if self.verbose and self.verbose>0 then +self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") +end +end +if UTILS.TableLength(ac)>0 then +DataAircraft=DataAircraft.."Aircraft in Storage:\n" +for key,amount in pairs(ac)do +DataAircraft=DataAircraft..tostring(key).."="..tostring(amount).."\n" +end +UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft) +if self.verbose and self.verbose>0 then +self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") +end +end +if UTILS.TableLength(wp)>0 then +DataWeapons=DataWeapons.."Weapons and Materiel in Storage:\n" +for _,_category in pairs(ENUMS.Storage.weapons)do +for _,_key in pairs(_category)do +local amount=self:GetAmount(_key) +if type(_key)=="table"then +_key="{"..table.concat(_key,",").."}" +end +DataWeapons=DataWeapons..tostring(_key).."="..tostring(amount).."\n" +end +end +for key,amount in pairs(ENUMS.Storage.weapons.Gazelle)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.Gazelle."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.CH47)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.CH47[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.CH47."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.UH1H)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.UH1H[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.UH1H."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.OH58)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.OH58[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.OH58."..tostring(key).."="..tostring(amount).."\n" +end +for key,amount in pairs(ENUMS.Storage.weapons.AH64D)do +amount=self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key]) +DataWeapons=DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n" +end +UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons) +if self.verbose and self.verbose>0 then +self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") +end +end +return self +end +function STORAGE:LoadFromFile(Path,Filename) +if not io then +BASE:E("ERROR: io not desanitized. Can't read the files.") +return false +end +if Path==nil and not lfs then +BASE:E("WARNING: lfs not desanitized. File will be read from DCS installation root directory rather than your give path.") +end +if self:IsLimitedLiquids()then +local Ok,Liquids=UTILS.LoadFromFile(Path,Filename.."_Liquids.csv") +if Ok then +if self.verbose and self.verbose>0 then +self:I(self.lid.."Loading Liquids from "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") +end +for _id,_line in pairs(Liquids)do +if string.find(_line,"Storage")==nil then +local tbl=UTILS.Split(_line,"=") +local lqno=tonumber(tbl[1]) +local lqam=tonumber(tbl[2]) +self:SetLiquid(lqno,lqam) +end +end +else +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") +end +end +if self:IsLimitedAircraft()then +local Ok,Aircraft=UTILS.LoadFromFile(Path,Filename.."_Aircraft.csv") +if Ok then +if self.verbose and self.verbose>0 then +self:I(self.lid.."Loading Aircraft from "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") +end +for _id,_line in pairs(Aircraft)do +if string.find(_line,"Storage")==nil then +local tbl=UTILS.Split(_line,"=") +local acname=tbl[1] +local acnumber=tonumber(tbl[2]) +self:SetAmount(acname,acnumber) +end +end +else +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") +end +end +if self:IsLimitedWeapons()then +local Ok,Weapons=UTILS.LoadFromFile(Path,Filename.."_Weapons.csv") +if Ok then +if self.verbose and self.verbose>0 then +self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") +end +for _id,_line in pairs(Weapons)do +if string.find(_line,"Storage")==nil then +local tbl=UTILS.Split(_line,"=") +local wpname=tbl[1] +local wpnumber=tonumber(tbl[2]) +if string.find(wpname,"{")==1 then +wpname=string.gsub(wpname,"{","") +wpname=string.gsub(wpname,"}","") +local tbl=UTILS.Split(wpname,",") +local wptbl={} +for _id,_key in ipairs(tbl)do +table.insert(wptbl,_id,_key) +end +self:SetAmount(wptbl,wpnumber) +else +self:SetAmount(wpname,wpnumber) +end +end +end +else +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") +end +end +return self +end +function STORAGE:StartAutoSave(Path,Filename,Interval,LoadOnce) +if LoadOnce~=false then +self:LoadFromFile(Path,Filename) +end +local interval=Interval or 300 +self.SaverTimer=TIMER:New(STORAGE.SaveToFile,self,Path,Filename) +self.SaverTimer:Start(interval,interval) +return self +end +function STORAGE:StopAutoSave() +if self.SaverTimer and self.SaverTimer:IsRunning()then +self.SaverTimer:Stop() +self.SaverTimer=nil +end +return self +end +function STORAGE:FindSyriaHHelipadWarehouse(ZoneName) +local findzone=ZONE:New(ZoneName) +local base=world.getAirbases() +for i=1,#base do +local info={} +info.callsign=Airbase.getCallsign(base[i]) +info.id=Airbase.getID(base[i]) +info.point=Airbase.getPoint(base[i]) +info.coordinate=COORDINATE:NewFromVec3(info.point) +info.DCSObject=base[i] +if info.callsign=="H"and findzone:IsCoordinateInZone(info.coordinate)then +info.warehouse=info.DCSObject:getWarehouse() +info.Storage=STORAGE:New(info.callsign..info.id) +info.Storage.airbase=info.DCSObject +info.Storage.warehouse=info.warehouse +return info.Storage +end +end +end +DYNAMICCARGO={ +ClassName="DYNAMICCARGO", +verbose=0, +testing=false, +Interval=10, +} +DYNAMICCARGO.Liquid={ +JETFUEL=0, +GASOLINE=1, +MW50=2, +DIESEL=3, +} +DYNAMICCARGO.LiquidName={ +GASOLINE="gasoline", +DIESEL="diesel", +MW50="methanol_mixture", +JETFUEL="jet_fuel", +} +DYNAMICCARGO.Type={ +WEAPONS="weapons", +LIQUIDS="liquids", +AIRCRAFT="aircrafts", +} +DYNAMICCARGO.State={ +NEW="NEW", +LOADED="LOADED", +UNLOADED="UNLOADED", +REMOVED="REMOVED", +} +DYNAMICCARGO.AircraftTypes={ +["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", +} +DYNAMICCARGO.AircraftDimensions={ +["CH-47Fbl1"]={ +["width"]=4, +["height"]=6, +["length"]=11, +["ropelength"]=30, +}, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, +} +DYNAMICCARGO.version="0.1.0" +function DYNAMICCARGO:Register(CargoName) +local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) +self.StaticName=CargoName +self.LastPosition=self:GetCoordinate() +self.CargoState=DYNAMICCARGO.State.NEW +self.Interval=DYNAMICCARGO.Interval or 10 +local DCSObject=self:GetDCSObject() +if DCSObject then +local warehouse=STORAGE:NewFromDynamicCargo(CargoName) +self.warehouse=warehouse +end +self.lid=string.format("DYNAMICCARGO %s",CargoName) +self.Owner=string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+")or"None" +self.timer=TIMER:New(DYNAMICCARGO._UpdatePosition,self) +self.timer:Start(self.Interval,self.Interval) +if not _DYNAMICCARGO_HELOS then +_DYNAMICCARGO_HELOS=SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart() +end +if self.testing then +BASE:TraceOn() +BASE:TraceClass("DYNAMICCARGO") +end +return self +end +function DYNAMICCARGO:GetDCSObject() +local DCSStatic=StaticObject.getByName(self.StaticName)or Unit.getByName(self.StaticName) +if DCSStatic then +return DCSStatic +end +return nil +end +function DYNAMICCARGO:GetLastOwner() +return self.Owner +end +function DYNAMICCARGO:IsNew() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.NEW then +return true +else +return false +end +end +function DYNAMICCARGO:IsLoaded() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.LOADED then +return true +else +return false +end +end +function DYNAMICCARGO:IsUnloaded() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.UNLOADED then +return true +else +return false +end +end +function DYNAMICCARGO:IsRemoved() +if self.CargoState and self.CargoState==DYNAMICCARGO.State.REMOVED then +return true +else +return false +end +end +function DYNAMICCARGO:GetCratesNeeded() +return 1 +end +function DYNAMICCARGO:WasDropped() +return self.CargoState==DYNAMICCARGO.State.UNLOADED and true or false +end +function DYNAMICCARGO:GetType() +return CTLD_CARGO.Enum.GCLOADABLE +end +function DYNAMICCARGO:GetLastPosition() +return self.LastPosition +end +function DYNAMICCARGO:GetState() +return self.CargoState +end +function DYNAMICCARGO:FindByName(Name) +local storage=_DATABASE:FindDynamicCargo(Name) +return storage +end +function DYNAMICCARGO:FindByMatching(Pattern) +local GroupFound=nil +for name,static in pairs(_DATABASE.DYNAMICCARGO)do +if string.match(name,Pattern)then +GroupFound=static +break +end +end +return GroupFound +end +function DYNAMICCARGO:FindAllByMatching(Pattern) +local GroupsFound={} +for name,static in pairs(_DATABASE.DYNAMICCARGO)do +if string.match(name,Pattern)then +GroupsFound[#GroupsFound+1]=static +end +end +return GroupsFound +end +function DYNAMICCARGO:GetStorageObject() +return self.warehouse +end +function DYNAMICCARGO:GetCargoWeight() +local DCSObject=self:GetDCSObject() +if DCSObject then +local weight=DCSObject:getCargoWeight() +return weight +else +return 0 +end +end +function DYNAMICCARGO:GetCargoDisplayName() +local DCSObject=self:GetDCSObject() +if DCSObject then +local weight=DCSObject:getCargoDisplayName() +return weight +else +return self.StaticName +end +end +function DYNAMICCARGO:_HeloHovering(Unit,ropelength) +local DCSUnit=Unit:GetDCSObject() +local hovering=false +local Height=0 +if DCSUnit then +local UnitInAir=DCSUnit:inAir() +local UnitCategory=DCSUnit:getDesc().category +if UnitInAir==true and UnitCategory==1 then +local VelocityVec3=DCSUnit:getVelocity() +local Velocity=UTILS.VecNorm(VelocityVec3) +local Coordinate=DCSUnit:getPoint() +local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z}) +Height=Coordinate.y-LandHeight +if Velocity<1 and Height<=ropelength and Height>6 then +hovering=true +end +end +return hovering,Height +end +return false +end +function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) +local set=_DYNAMICCARGO_HELOS:GetAliveSet() +local success=false +local Helo=nil +local Playername=nil +for _,_helo in pairs(set or{})do +local helo=_helo +local name=helo:GetPlayerName()or _DATABASE:_FindPlayerNameByUnitName(helo:GetName())or"None" +self:T(self.lid.." Checking: "..name) +local hpos=helo:GetCoordinate() +local typename=helo:GetTypeName() +local dimensions=DYNAMICCARGO.AircraftDimensions[typename] +local hovering,height=self:_HeloHovering(helo,dimensions.ropelength) +local helolanded=not helo:InAir() +self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering)) +if hpos and typename and dimensions then +local delta2D=hpos:Get2DDistance(pos) +local delta3D=hpos:Get3DDistance(pos) +if self.testing then +self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) +self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) +self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height)) +end +if loading~=true and(delta2D>dimensions.length or delta2D>dimensions.width)and helolanded then +success=true +Helo=helo +Playername=name +end +if loading~=true and delta3D>dimensions.ropelength then +success=true +Helo=helo +Playername=name +end +if loading==true and((delta2D0.5 then +if self.CargoState==DYNAMICCARGO.State.NEW or self.CargoState==DYNAMICCARGO.State.UNLOADED then +local isloaded,client,playername=self:_GetPossibleHeloNearby(pos,true) +self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername)) +self.CargoState=DYNAMICCARGO.State.LOADED +self.Owner=playername +_DATABASE:CreateEventDynamicCargoLoaded(self) +end +elseif self.CargoState==DYNAMICCARGO.State.LOADED then +local count=_DYNAMICCARGO_HELOS:CountAlive() +local landheight=pos:GetLandHeight() +local agl=pos.y-landheight +agl=UTILS.Round(agl,2) +self:T(self.lid.." AGL: "..agl or-1) +local isunloaded=true +local client +local playername=self.Owner +if count>0 then +self:T(self.lid.." Possible alive helos: "..count or-1) +isunloaded,client,playername=self:_GetPossibleHeloNearby(pos,false) +if isunloaded then +self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) +self.CargoState=DYNAMICCARGO.State.UNLOADED +self.Owner=playername +_DATABASE:CreateEventDynamicCargoUnloaded(self) +end +end +end +self.LastPosition=pos +else +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end +self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") +self.CargoState=DYNAMICCARGO.State.REMOVED +_DATABASE:CreateEventDynamicCargoRemoved(self) +end +return self +end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end +function DYNAMICCARGO._FilterHeloTypes(client) +if not client then return false end +local typename=client:GetTypeName() +local isinclude=DYNAMICCARGO.AircraftTypes[typename]~=nil and true or false +return isinclude +end CARGOS={} do CARGO={ @@ -32971,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -32984,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -33006,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -33018,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -33045,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -33077,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -33084,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -33223,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -33333,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -33358,6 +37411,8 @@ local TargetType=nil local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil +local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -33371,7 +37426,6 @@ 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 @@ -33383,10 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then +TargetCategory=Unit.Category.STRUCTURE +TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") +end 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) @@ -33395,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -33406,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -33452,17 +37512,22 @@ MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit en MESSAGE.Type.Update) :ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) :ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -else +elseif TargetIsScenery~=true then 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()) +elseif TargetIsScenery==true then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.".." 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:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit nothing special.", MESSAGE.Type.Update) :ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) :ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) @@ -33473,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -33491,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -33580,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -33679,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -33696,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -33934,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -33957,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -33965,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -34033,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -34054,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -34223,14 +38315,14 @@ self:T({"CleanUp: Add to CleanUpList: ",CleanUpGroup:GetName(),CleanUpUnitName}) end function CLEANUP_AIRBASE.__:EventAddForCleanUp(Event) self:F({Event}) -if Event.IniDCSUnit and Event.IniCategory==Object.Category.UNIT then +if Event.IniDCSUnit and Event.IniUnit and Event.IniCategory==Object.Category.UNIT then if self.CleanUpList[Event.IniDCSUnitName]==nil then if self:IsInAirbase(Event.IniUnit:GetVec2())then self:AddForCleanUp(Event.IniUnit,Event.IniDCSUnitName) end end end -if Event.TgtDCSUnit and Event.TgtCategory==Object.Category.UNIT then +if Event.TgtDCSUnit and Event.TgtUnit and Event.TgtCategory==Object.Category.UNIT then if self.CleanUpList[Event.TgtDCSUnitName]==nil then if self:IsInAirbase(Event.TgtUnit:GetVec2())then self:AddForCleanUp(Event.TgtUnit,Event.TgtDCSUnitName) @@ -34244,7 +38336,7 @@ for CleanUpUnitName,CleanUpListData in pairs(self.CleanUpList)do CleanUpCount=CleanUpCount+1 local CleanUpUnit=CleanUpListData.CleanUpUnit local CleanUpGroupName=CleanUpListData.CleanUpGroupName -if CleanUpUnit:IsAlive()~=nil then +if CleanUpUnit and CleanUpUnit:IsAlive()~=nil then if self:IsInAirbase(CleanUpUnit:GetVec2())then if _DATABASE:GetStatusGroup(CleanUpGroupName)~="ReSpawn"then local CleanUpCoordinate=CleanUpUnit:GetCoordinate() @@ -34264,7 +38356,7 @@ self:DestroyUnit(CleanUpUnit) end end end -if CleanUpUnit and not CleanUpUnit:GetPlayerName()then +if CleanUpUnit and(CleanUpUnit.GetPlayerName==nil or not CleanUpUnit:GetPlayerName())then local CleanUpUnitVelocity=CleanUpUnit:GetVelocityKMH() if CleanUpUnitVelocity<1 then if CleanUpListData.CleanUpMoved then @@ -34384,16 +38476,18 @@ Excellent={Evade=10,DelayOn={10,30}} SEADGroupPrefixes={}, SuppressedGroups={}, EngagementRange=75, -Padding=10, +Padding=15, CallBack=nil, UseCallBack=false, debug=false, +WeaponTrack=false, } SEAD.Harms={ ["AGM_88"]="AGM_88", ["AGM_122"]="AGM_122", ["AGM_84"]="AGM_84", ["AGM_45"]="AGM_45", +["AGM_65"]="AGM_65", ["ALARM"]="ALARM", ["LD-10"]="LD-10", ["X_58"]="X_58", @@ -34409,6 +38503,7 @@ SEAD.Harms={ SEAD.HarmData={ ["AGM_88"]={150,3}, ["AGM_45"]={12,2}, +["AGM_65"]={16,0.9}, ["AGM_122"]={16.5,2.3}, ["AGM_84"]={280,0.8}, ["ALARM"]={45,2}, @@ -34444,7 +38539,7 @@ self:HandleEvent(EVENTS.Shot,self.HandleEventShot) self:SetStartState("Running") self:AddTransition("*","ManageEvasion","*") self:AddTransition("*","CalculateHitZone","*") -self:I("*** SEAD - Started Version 0.4.6") +self:I("*** SEAD - Started Version 0.4.9") return self end function SEAD:UpdateSet(SEADGroupPrefixes) @@ -34592,7 +38687,7 @@ local wpndata=SEAD.HarmData[data] reach=wpndata[1]*1.1 local mach=wpndata[2] wpnspeed=math.floor(mach*340.29) -if Weapon then +if Weapon and Weapon:GetSpeed()>0 then wpnspeed=Weapon:GetSpeed() self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed)) end @@ -34659,24 +38754,30 @@ return self end function SEAD:HandleEventShot(EventData) self:T({EventData.id}) +local SEADWeapon=EventData.Weapon +local SEADWeaponName=EventData.WeaponName or"None" +if self:_CheckHarms(SEADWeaponName)then local SEADPlane=EventData.IniUnit +if not SEADPlane then return self end 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') +if self.WeaponTrack==true then +WeaponWrapper:SetFuncTrack(function(weapon)env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed()or-1))end) +WeaponWrapper:StartTrack(0.1) +WeaponWrapper:StopTrack(30) +end 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.") +self:T("**** Tracking AGM-88/154 with no target data.") local pos0=SEADPlane:GetCoordinate() local fheight=SEADPlane:GetHeight() self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName) @@ -34721,7 +38822,7 @@ 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) +self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,2,WeaponWrapper) else self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) end @@ -35357,7 +39458,6 @@ 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 @@ -35408,8 +39508,6 @@ else self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient) end return true -else -end end return false end @@ -36026,7 +40124,7 @@ return true end ATC_GROUND_UNIVERSAL={ ClassName="ATC_GROUND_UNIVERSAL", -Version="0.0.1", +Version="0.0.2", SetClient=nil, Airbases=nil, AirbaseList=nil, @@ -36036,14 +40134,21 @@ 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 +for _name,_base in pairs(_DATABASE.AIRBASES)do +if _base and _base.isAirdrome==true then self.AirbaseList[_name]=_name +self.Airbases[_name]={} +end +end +else +for _,_name in pairs(AirbaseList)do +local airbase=_DATABASE:FindAirbase(_name) +if airbase and(airbase.isAirdrome==true)then +self.Airbases[_name]={} +end end end self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart() @@ -36160,19 +40265,21 @@ NotInRunwayZone=(Client:IsNotInZone(_runwaydata.zone)==true)and NotInRunwayZone end end if NotInRunwayZone then -if IsOnGround then local Taxi=Client:GetState(self,"Taxi") +if IsOnGround then 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) +Client:SetState(self,"Speeding",false) +Client:SetState(self,"Warnings",0) end local Velocity=VELOCITY_POSITIONABLE:New(Client) local IsAboveRunway=Client:IsAboveRunway() self:T({IsAboveRunway,IsOnGround,Velocity:Get()}) -if IsOnGround then +if IsOnGround and not Taxi then local Speeding=false if AirbaseMeta.MaximumKickSpeed then if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then @@ -36184,11 +40291,11 @@ 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) +Client:SetState(self,"Speeding",true) +local SpeedingWarnings=Client:GetState(self,"Warnings") +Client:SetState(self,"Warnings",SpeedingWarnings+1) +Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is ".. +Velocity:ToString(),5,"ATC") end end if IsOnGround then @@ -36387,7 +40494,7 @@ function DETECTION_BASE:onafterStart(From,Event,To) self:__Detect(1) end function DETECTION_BASE:onafterDetect(From,Event,To) -local DetectDelay=0.1 +local DetectDelay=0.15 self.DetectionCount=0 self.DetectionRun=0 self:UnIdentifyAllDetectedObjects() @@ -36418,13 +40525,15 @@ self.DetectionSet:ForEachGroupAlive(IteratorFunction,arg) return self end function DETECTION_BASE:onafterDetection(From,Event,To,Detection,DetectionTimeStamp) +self:T({DetectedObjects=self.DetectedObjects}) self.DetectionRun=self.DetectionRun+1 local HasDetectedObjects=false if Detection and Detection:IsAlive()then +self:T({"DetectionGroup is Alive",Detection:GetName()}) local DetectionGroupName=Detection:GetName() -local DetectionUnit=Detection:GetUnit(1) +local DetectionUnit=Detection:GetFirstUnitAlive() local DetectedUnits={} -local DetectedTargets=Detection:GetDetectedTargets( +local DetectedTargets=DetectionUnit:GetDetectedTargets( self.DetectVisual, self.DetectOptical, self.DetectRadar, @@ -36432,8 +40541,7 @@ self.DetectIRST, self.DetectRWR, self.DetectDLINK ) -self:F({DetectedTargets=DetectedTargets}) -for DetectionObjectID,Detection in pairs(DetectedTargets)do +for DetectionObjectID,Detection in pairs(DetectedTargets or{})do local DetectedObject=Detection.object if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then local DetectedObjectName=DetectedObject:getName() @@ -36444,10 +40552,10 @@ self.DetectedObjects[DetectedObjectName].Object=DetectedObject end end end -for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do +for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects or{})do local DetectedObject=DetectedObjectData.Object if DetectedObject:isExist()then -local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected( +local TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected( DetectedObject, self.DetectVisual, self.DetectOptical, @@ -38361,7 +42469,6 @@ 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()}) @@ -38386,6 +42493,7 @@ break end end end +if TargetSetUnit==nil then return end if self.AutoLase or(not self.AutoLase and(self.LaseStart+Duration>=timer.getTime()))then TargetSetUnit:ForEachUnitPerThreatLevel(10,0, function(TargetUnit) @@ -38412,14 +42520,15 @@ self.LaserCodesUsed[LaserCode]=LaserCodeIndex local Spot=RecceUnit:LaseUnit(TargetUnit,LaserCode,Duration) local AttackSet=self.AttackSet local DesignateName=self.DesignateName +local typename=TargetUnit:GetTypeName() function Spot:OnAfterDestroyed(From,Event,To) -self.Recce:MessageToSetGroup("Target "..TargetUnit:GetTypeName().." destroyed. "..TargetSetUnit:Count().." targets left.", +self.Recce:MessageToSetGroup("Target "..typename.." destroyed. "..TargetSetUnit:CountAlive().." targets left.", 5,AttackSet,self.DesignateName) end self.Recces[TargetUnit]=RecceUnit MarkingCount=MarkingCount+1 local TargetUnitType=TargetUnit:GetTypeName() -RecceUnit:MessageToSetGroup("Marking "..TargetUnit:GetTypeName().." with laser "..RecceUnit:GetSpot().LaserCode.." for "..Duration.."s.", +RecceUnit:MessageToSetGroup("Marking "..TargetUnitType.." with laser "..RecceUnit:GetSpot().LaserCode.." for "..Duration.."s.", 10,self.AttackSet,DesignateName) if not MarkedTypes[TargetUnitType]then MarkedTypes[TargetUnitType]=true @@ -38530,9 +42639,11 @@ end end end function DESIGNATE:onafterDoneSmoking(From,Event,To,Index) +if self.Designating[Index]~=nil then self.Designating[Index]=string.gsub(self.Designating[Index],"S","") self:SetDesignateMenu() end +end function DESIGNATE:onafterDoneIlluminating(From,Event,To,Index) self.Designating[Index]=string.gsub(self.Designating[Index],"I","") self:SetDesignateMenu() @@ -39575,6 +43686,10 @@ elseif DCStype=="Mirage-F1CE"then self.aircraft.length=16 self.aircraft.height=5 self.aircraft.width=9 +elseif DCStype=="Saab340"then +self.aircraft.length=19.73 +self.aircraft.height=6.97 +self.aircraft.width=21.44 end self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width) local text=string.format("\n******************************************************\n") @@ -40576,11 +44691,16 @@ 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 +local Dtravel=0 +if coords and ratcraft.Pnow then +local Dtravel=coords:Get2DDistance(ratcraft.Pnow) +ratcraft.Pnow=coords +end ratcraft.Distance=ratcraft.Distance+Dtravel -local Ddestination=Pnow:Get2DDistance(ratcraft.destination:GetCoordinate()) +local Ddestination=-1 +if ratcraft.Pnow then +Ddestination=ratcraft.Pnow:Get2DDistance(ratcraft.destination:GetCoordinate()) +end if(forID and spawnindex==forID)or(not forID)then local text=string.format("ID %i of flight %s",spawnindex,prefix) if N0units>1 then @@ -41582,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -41593,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -41619,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -41649,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -41672,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -41687,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -41696,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -41966,6 +46088,8 @@ targetsheet=nil, targetpath=nil, targetprefix=nil, Coalition=nil, +ceilingaltitude=20000, +ceilingenabled=false, } RANGE.Defaults={ goodhitrange=25, @@ -42035,7 +46159,7 @@ IRExitRange={filename="IR-ExitRange.ogg",duration=3.10}, RANGE.Names={} RANGE.MenuF10={} RANGE.MenuF10Root=nil -RANGE.version="2.7.3" +RANGE.version="2.8.1" function RANGE:New(RangeName,Coalition) local self=BASE:Inherit(self,FSM:New()) self.rangename=RangeName or"Practice Range" @@ -42101,32 +46225,32 @@ 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:SetDigit(0,self.Sound.RC0.filename,self.Sound.RC0.duration,self.soundpath) +self.rangecontrol:SetDigit(1,self.Sound.RC1.filename,self.Sound.RC1.duration,self.soundpath) +self.rangecontrol:SetDigit(2,self.Sound.RC2.filename,self.Sound.RC2.duration,self.soundpath) +self.rangecontrol:SetDigit(3,self.Sound.RC3.filename,self.Sound.RC3.duration,self.soundpath) +self.rangecontrol:SetDigit(4,self.Sound.RC4.filename,self.Sound.RC4.duration,self.soundpath) +self.rangecontrol:SetDigit(5,self.Sound.RC5.filename,self.Sound.RC5.duration,self.soundpath) +self.rangecontrol:SetDigit(6,self.Sound.RC6.filename,self.Sound.RC6.duration,self.soundpath) +self.rangecontrol:SetDigit(7,self.Sound.RC7.filename,self.Sound.RC7.duration,self.soundpath) +self.rangecontrol:SetDigit(8,self.Sound.RC8.filename,self.Sound.RC8.duration,self.soundpath) +self.rangecontrol:SetDigit(9,self.Sound.RC9.filename,self.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:SetDigit(0,self.Sound.IR0.filename,self.Sound.IR0.duration,self.soundpath) +self.instructor:SetDigit(1,self.Sound.IR1.filename,self.Sound.IR1.duration,self.soundpath) +self.instructor:SetDigit(2,self.Sound.IR2.filename,self.Sound.IR2.duration,self.soundpath) +self.instructor:SetDigit(3,self.Sound.IR3.filename,self.Sound.IR3.duration,self.soundpath) +self.instructor:SetDigit(4,self.Sound.IR4.filename,self.Sound.IR4.duration,self.soundpath) +self.instructor:SetDigit(5,self.Sound.IR5.filename,self.Sound.IR5.duration,self.soundpath) +self.instructor:SetDigit(6,self.Sound.IR6.filename,self.Sound.IR6.duration,self.soundpath) +self.instructor:SetDigit(7,self.Sound.IR7.filename,self.Sound.IR7.duration,self.soundpath) +self.instructor:SetDigit(8,self.Sound.IR8.filename,self.Sound.IR8.duration,self.soundpath) +self.instructor:SetDigit(9,self.Sound.IR9.filename,self.Sound.IR9.duration,self.soundpath) self.instructor:SetSenderCoordinate(self.location) self.instructor:SetSenderUnitName(self.instructorrelayname) self.instructor:Start(1,0.1) @@ -42142,7 +46266,11 @@ self:_SmokeStrafeTargets() self:_SmokeStrafeTargetBoxes() self.rangezone:SmokeZone(SMOKECOLOR.White) end -self:__Status(-60) +self:__Status(-10) +end +function RANGE:SetMenuRoot(menu) +self.menuF10root=menu +return self end function RANGE:SetMaxStrafeAlt(maxalt) self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt @@ -42208,9 +46336,32 @@ self.location=coordinate return self end function RANGE:SetRangeZone(zone) +if zone and type(zone)=="string"then +zone=ZONE:FindByName(zone) +end self.rangezone=zone return self end +function RANGE:SetRangeCeiling(altitude) +self:T(self.lid.."SetRangeCeiling") +if altitude and type(altitude)=="number"then +self.ceilingaltitude=altitude +else +self:E(self.lid.."Altitude either not provided or is not a number, using default setting (20000).") +self.ceilingaltitude=20000 +end +return self +end +function RANGE:EnableRangeCeiling(enabled) +self:T(self.lid.."EnableRangeCeiling") +if enabled and type(enabled)=="boolean"then +self.ceilingenabled=enabled +else +self:E(self.lid.."Enabled either not provide or is not a boolean, using default setting (false).") +self.ceilingenabled=false +end +return self +end function RANGE:SetBombTargetSmokeColor(colorid) self.BombSmokeColor=colorid or SMOKECOLOR.Red return self @@ -42279,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -42311,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -42330,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -42348,7 +46514,32 @@ 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)) +self:T2(self.lid..string.format("Setting sound files path to %s",self.soundpath)) +return self +end +function RANGE:SetSoundfilesInfo(csvfile) +local function getSound(filename) +for key,_soundfile in pairs(self.Sound)do +local soundfile=_soundfile +if filename==soundfile.filename then +return soundfile +end +end +return nil +end +local data=UTILS.ReadCSV(csvfile) +if data then +for i,sound in pairs(data)do +local soundfile=getSound(sound.filename..".ogg") +if soundfile then +soundfile.duration=tonumber(sound.duration) +else +self:E(string.format("ERROR: Could not get info for sound file %s",sound.filename)) +end +end +else +self:E(string.format("ERROR: Could not read sound csv file!")) +end return self end function RANGE:AddStrafePit(targetnames,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) @@ -42474,9 +46665,9 @@ 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))) +self:T(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))) +self:T(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 @@ -42515,7 +46706,7 @@ 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)) +self:T(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 @@ -42532,6 +46723,9 @@ return self end function RANGE:AddBombingTargetGroup(group,goodhitrange,randommove) self:F({group=group,goodhitrange=goodhitrange,randommove=randommove}) +if group and type(group)=="string"then +group=GROUP:FindByName(group) +end if group then local _units=group:GetUnits() for _,_unit in pairs(_units)do @@ -42575,7 +46769,7 @@ 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) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName,EventData.IniPlayerName) 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)) @@ -42627,7 +46821,7 @@ local _unitID=_unit:GetID() local target=EventData.TgtUnit local targetname=EventData.TgtUnitName local _currentTarget=self.strafeStatus[_unitID] -if _currentTarget and target:IsAlive()then +if _currentTarget and target and target:IsAlive()then local playerPos=_unit:GetCoordinate() local targetPos=target:GetCoordinate() for _,_target in pairs(_currentTarget.zone.targets)do @@ -42667,6 +46861,7 @@ end end end function RANGE._OnImpact(weapon,self,playerData,attackHdg,attackAlt,attackVel) +if not playerData then return end local _closetTarget=nil local _distance=nil local _closeCoord=nil @@ -42676,11 +46871,11 @@ 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 and playerData.smokebombimpact and insidezone then if playerData.delaysmoke then -timer.scheduleFunction(self._DelayedSmoke,{coord=impactcoord,color=playerData.smokecolor},timer.getTime()+self.TdelaySmoke) +impactcoord:Smoke(playerData.smokecolor,30,self.TdelaySmoke) else -impactcoord:Smoke(playerData.smokecolor) +impactcoord:Smoke(playerData.smokecolor,30) end end for _,_bombtarget in pairs(self.bombingTargets)do @@ -42731,7 +46926,12 @@ result.rangename=self.rangename result.attackHdg=attackHdg result.attackVel=attackVel result.attackAlt=attackAlt -result.date=os and os.date()or"n/a" +if os and os.date then +result.date=os.date() +else +self:E(self.lid.."os or os.date() not available") +result.date="n/a" +end table.insert(_results,result) self:Impact(result,playerData) elseif insidezone then @@ -42745,7 +46945,7 @@ 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) +self.rangecontrol:NewTransmission(self.Sound.RCWeaponImpactedTooFar.filename,self.Sound.RCWeaponImpactedTooFar.duration,self.soundpath,nil,nil,_message,self.subduration) end end else @@ -42760,14 +46960,15 @@ 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 _unit,_playername=self:_GetPlayerUnitAndName(_unitName,EventData.IniPlayerName) 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 +if _track and dPR<=self.BombtrackThreshold and _unit and _playername and self.PlayerSettings[_playername]then local playerData=self.PlayerSettings[_playername] +if not playerData then return end local attackHdg=_unit:GetHeading() local attackAlt=_unit:GetHeight() attackAlt=UTILS.MetersToFeet(attackAlt) @@ -42802,7 +47003,7 @@ 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) +self:T(self.lid..text) end self:_CheckPlayers() self:__Status(-10) @@ -42816,13 +47017,13 @@ 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:NewTransmission(self.Sound.IREnterRange.filename,self.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:NewTransmission(self.Sound.IRDecimal.filename,self.Sound.IRDecimal.duration,self.soundpath) self.instructor:Number2Transmission(RF[2]) end -self.instructor:NewTransmission(RANGE.Sound.IRMegaHertz.filename,RANGE.Sound.IRMegaHertz.duration,self.soundpath) +self.instructor:NewTransmission(self.Sound.IRMegaHertz.filename,self.Sound.IRMegaHertz.duration,self.soundpath) end end end @@ -42844,7 +47045,7 @@ 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) +self.instructor:NewTransmission(self.Sound.IRExitRange.filename,self.Sound.IRExitRange.duration,self.soundpath) end end end @@ -42865,20 +47066,20 @@ 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:NewTransmission(self.Sound.RCImpact.filename,self.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:NewTransmission(self.Sound.RCDegrees.filename,self.Sound.RCDegrees.duration,self.soundpath) +self.rangecontrol:NewTransmission(self.Sound.RCFor.filename,self.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) +self.rangecontrol:NewTransmission(self.Sound.RCFeet.filename,self.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) +self.rangecontrol:NewTransmission(self.Sound.RCPoorHit.filename,self.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) +self.rangecontrol:NewTransmission(self.Sound.RCIneffectiveHit.filename,self.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) +self.rangecontrol:NewTransmission(self.Sound.RCGoodHit.filename,self.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) +self.rangecontrol:NewTransmission(self.Sound.RCExcellentHit.filename,self.Sound.RCExcellentHit.duration,self.soundpath,nil,0.5) end end end @@ -42913,12 +47114,12 @@ 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))) +self:T(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 path=self.targetpath or 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 @@ -42957,10 +47158,10 @@ self:E(self.lid..string.format("WARNING: Could not load player results from file return nil end end -local path=lfs.writedir()..[[Logs\]] +local path=self.targetpath or 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) +self:T(self.lid..text) local data=_loadfile(filename) if data then local results=UTILS.Split(data,"\n") @@ -43028,9 +47229,6 @@ 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) @@ -43157,7 +47355,6 @@ function RANGE:_DisplayRangeInfo(_unitname) self:F(_unitname) local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname) if unit and playername then -self:I(playername) local text="" local coord=unit:GetCoordinate() if self.location then @@ -43256,7 +47453,7 @@ local ca2g=coord:ToStringA2G(_unit,_settings) _text=_text..string.format("\n- %s:\n%s @ %s",bombtarget.name or"unknown",ca2g,eltxt) end end -self:_DisplayMessageToGroup(_unit,_text,120,true,true,_multiplayer) +self:_DisplayMessageToGroup(_unit,_text,150,true,true,_multiplayer) end end function RANGE:_DisplayStrafePits(_unitname) @@ -43324,7 +47521,9 @@ local playersettings=_playersettings local unitname=playersettings.unitname local unit=UNIT:FindByName(unitname) if unit and unit:IsAlive()then -if unit:IsInZone(self.rangezone)then +local unitalt=unit:GetAltitude(false) +local unitaltinfeet=UTILS.MetersToFeet(unitalt) +if unit:IsInZone(self.rangezone)and(not self.ceilingenabled or unitaltinfeet=90 then resulttext="DEADEYE PASS" -_sound=RANGE.Sound.RCExcellentPass +_sound=self.Sound.RCExcellentPass elseif accur>=75 then resulttext="EXCELLENT PASS" -_sound=RANGE.Sound.RCExcellentPass +_sound=self.Sound.RCExcellentPass elseif accur>=50 then resulttext="GOOD PASS" -_sound=RANGE.Sound.RCGoodPass +_sound=self.Sound.RCGoodPass elseif accur>=25 then resulttext="INEFFECTIVE PASS" -_sound=RANGE.Sound.RCIneffectivePass +_sound=self.Sound.RCIneffectivePass else resulttext="POOR PASS" -_sound=RANGE.Sound.RCPoorPass +_sound=self.Sound.RCPoorPass end end local _text=string.format("%s, hits on target %s: %d",self:_myname(_unitName),_result.zone.name,_result.hits) @@ -43450,14 +47649,14 @@ 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:NewTransmission(self.Sound.RCHitsOnTarget.filename,self.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:NewTransmission(self.Sound.RCTotalRoundsFired.filename,self.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:NewTransmission(self.Sound.RCAccuracy.filename,self.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) +self.rangecontrol:NewTransmission(self.Sound.RCPercent.filename,self.Sound.RCPercent.duration,self.soundpath) end self.rangecontrol:NewTransmission(_sound.filename,_sound.duration,self.soundpath,nil,0.5) end @@ -43481,7 +47680,7 @@ 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) +self.rangecontrol:NewTransmission(self.Sound.RCRollingInOnStrafeTarget.filename,self.Sound.RCRollingInOnStrafeTarget.duration,self.soundpath) end end self:_DisplayMessageToGroup(_unit,_msg,10,true) @@ -43501,19 +47700,25 @@ 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") +local _rootMenu=nil +if self.menuF10root then +_rootMenu=self.menuF10root +self:T2(self.lid..string.format("Creating F10 menu for group %s",group:GetName())) +elseif RANGE.MenuF10Root then +_rootMenu=RANGE.MenuF10Root else if RANGE.MenuF10[_gid]==nil then -RANGE.MenuF10[_gid]=MENU_GROUP:New(group,"On the Range") +self:T2(self.lid..string.format("Creating F10 menu 'On the Range' for group %s",group:GetName())) +else +self:T2(self.lid..string.format("F10 menu 'On the Range' already EXISTS for group %s",group:GetName())) end -_rangePath=MENU_GROUP:New(group,self.rangename,RANGE.MenuF10[_gid]) +_rootMenu=RANGE.MenuF10[_gid]or MENU_GROUP:New(group,"On the Range") 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 _rangePath=MENU_GROUP:New(group,self.rangename,_rootMenu) local _infoPath=MENU_GROUP:New(group,"Range Info",_rangePath) +local _markPath=MENU_GROUP:New(group,"Mark Targets",_rangePath) +local _statsPath=MENU_GROUP:New(group,"Statistics",_rangePath) +local _settingsPath=MENU_GROUP:New(group,"My Settings",_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) @@ -43764,12 +47969,12 @@ 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.") +text=string.format("Roger, your targetsheets are now SAVED.") else -text=string.format("affirm, your targetsheets are NOT SAVED.") +text=string.format("Affirm, your targetsheets are NOT SAVED.") end else -text="negative, target sheet data recorder is broken on this range." +text="Negative, target sheet data recorder is broken on this range." end self:_DisplayMessageToGroup(unit,text,5,false,false) end @@ -43913,13 +48118,12 @@ self:T({speed=speed}) end return speed end -function RANGE:_GetPlayerUnitAndName(_unitName) -self:F2(_unitName) +function RANGE:_GetPlayerUnitAndName(_unitName,PlayerName) if _unitName~=nil then local multiplayer=false local DCSunit=Unit.getByName(_unitName) -if DCSunit then -local playername=DCSunit:getPlayerName() +if DCSunit and DCSunit.getPlayerName then +local playername=DCSunit:getPlayerName()or PlayerName or"None" local unit=UNIT:Find(DCSunit) self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) if DCSunit and unit and playername then @@ -44353,63 +48557,140 @@ IlluminationShells=667, SmokeShells=668, } ARTY.db={ -["2B11 mortar"]={ +["LeFH_18-40-105"]={ +displayname="FH LeFH-18 105mm", minrange=500, -maxrange=7000, -reloadtime=30, -}, -["SPH 2S1 Gvozdika"]={ -minrange=300, -maxrange=15000, +maxrange=10500, reloadtime=nil, }, -["SPH 2S19 Msta"]={ -minrange=300, -maxrange=23500, -reloadtime=nil, -}, -["SPH 2S3 Akatsia"]={ -minrange=300, -maxrange=17000, -reloadtime=nil, -}, -["SPH 2S9 Nona"]={ +["M2A1-105"]={ +displayname="FH M2A1 105mm", minrange=500, -maxrange=7000, +maxrange=11500, reloadtime=nil, }, -["SPH M109 Paladin"]={ -minrange=300, -maxrange=22000, +["Pak40"]={ +displayname="FH Pak 40 75mm", +minrange=500, +maxrange=3000, reloadtime=nil, }, -["SpGH Dana"]={ -minrange=300, -maxrange=18700, +["L118_Unit"]={ +displayname="L118 Light Artillery Gun", +minrange=500, +maxrange=17500, 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"]={ +["Smerch"]={ +displayname="MLRS 9A52 Smerch CM 300mm", minrange=20000, maxrange=70000, reloadtime=2160, }, -["MLRS M270"]={ +["Smerch_HE"]={ +displayname="MLRS 9A52 Smerch HE 300mm", +minrange=20000, +maxrange=70000, +reloadtime=2160, +}, +["Uragan_BM-27"]={ +displayname="MLRS 9K57 Uragan BM-27 220mm", +minrange=11500, +maxrange=35800, +reloadtime=840, +}, +["Grad-URAL"]={ +displayname="MLRS BM-21 Grad 122mm", +minrange=5000, +maxrange=19000, +reloadtime=420, +}, +["HL_B8M1"]={ +displayname="MLRS HL with B8M1 80mm", +minrange=500, +maxrange=5000, +reloadtime=nil, +}, +["tt_B8M1"]={ +displayname="MLRS LC with B8M1 80mm", +minrange=500, +maxrange=5000, +reloadtime=nil, +}, +["MLRS"]={ +displayname="MLRS M270 227mm", minrange=10000, maxrange=32000, reloadtime=540, }, +["2B11 mortar"]={ +displayname="Mortar 2B11 120mm", +minrange=500, +maxrange=7000, +reloadtime=30, +}, +["PLZ05"]={ +displayname="PLZ-05", +minrange=500, +maxrange=23500, +reloadtime=nil, +}, +["SAU Gvozdika"]={ +displayname="SPH 2S1 Gvozdika 122mm", +minrange=300, +maxrange=15000, +reloadtime=nil, +}, +["SAU Msta"]={ +displayname="SPH 2S19 Msta 152mm", +minrange=300, +maxrange=23500, +reloadtime=nil, +}, +["SAU Akatsia"]={ +displayname="SPH 2S3 Akatsia 152mm", +minrange=300, +maxrange=17000, +reloadtime=nil, +}, +["SpGH_Dana"]={ +displayname="SPH Dana vz77 152mm", +minrange=300, +maxrange=18700, +reloadtime=nil, +}, +["M-109"]={ +displayname="SPH M109 Paladin 155mm", +minrange=300, +maxrange=22000, +reloadtime=nil, +}, +["M12_GMC"]={ +displayname="SPH M12 GMC 155mm", +minrange=300, +maxrange=18200, +reloadtime=nil, +}, +["Wespe124"]={ +displayname="SPH Sd.Kfz.124 Wespe 105mm", +minrange=300, +maxrange=7000, +reloadtime=nil, +}, +["T155_Firtina"]={ +displayname="SPH T155 Firtina 155mm", +minrange=300, +maxrange=41000, +reloadtime=nil, +}, +["SAU 2-C9"]={ +displayname="SPM 2S9 Nona 120mm M", +minrange=500, +maxrange=7000, +reloadtime=nil, +}, } -ARTY.version="1.3.0" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -44448,7 +48729,7 @@ for id,desc in pairs(self.DCSdesc)do self:T3({id=id,desc=desc}) end self.SpeedMax=group:GetSpeedMax() -if self.SpeedMax>1 then +if self.SpeedMax>3.6 then self.ismobile=true else self.ismobile=false @@ -44872,7 +49153,7 @@ 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) +self.Nammo0,self.Nshells0,self.Nrockets0,self.Nmissiles0,self.Narty0=self:GetAmmo(self.Debug) if self.nukerange==nil then self.nukerange=1500/75000*self.nukewarhead end @@ -44897,7 +49178,7 @@ else self.Nsmoke=0 self.Nsmoke0=0 end -local _dbproperties=self:_CheckDB(self.DisplayName) +local _dbproperties=self:_CheckDB(self.Type) self:T({dbproperties=_dbproperties}) if _dbproperties~=nil then for property,value in pairs(_dbproperties)do @@ -44929,8 +49210,8 @@ 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("Speed max = %.1f km/h\n",self.SpeedMax) +text=text..string.format("Speed default = %.1f 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) @@ -45033,7 +49314,7 @@ function ARTY:_StatusReport(display) if display==nil then display=false end -local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() +local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo() local Nnukes=self.Nukes local Nillu=self.Nillu local Nsmoke=self.Nsmoke @@ -45044,7 +49325,7 @@ 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 shells = %d\n",Narty) 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) @@ -45089,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist0 then for i=1,#self.targets do @@ -45558,14 +49843,14 @@ 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 Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo() +local nfire=Narty local _type="shots" if target.weapontype==ARTY.WeaponType.Auto then nfire=Nammo _type="shots" elseif target.weapontype==ARTY.WeaponType.Cannon then -nfire=Nshells +nfire=Narty _type="shells" elseif target.weapontype==ARTY.WeaponType.TacticalNukes then nfire=self.Nukes @@ -45706,7 +49991,7 @@ end end function ARTY:_CheckRearmed() self:F2() -local nammo,nshells,nrockets,nmissiles=self:GetAmmo() +local nammo,nshells,nrockets,nmissiles,narty=self:GetAmmo() local units=self.Controllable:GetUnits() local nunits=0 if units then @@ -45816,10 +50101,13 @@ 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 +if group:HasTask()then +group:ClearTasks() +end group:OptionROEOpenFire() local vec2=coord:GetVec2() local fire=group:TaskFireAtPoint(vec2,radius,nshells,weapontype) -group:SetTask(fire) +group:SetTask(fire,1) end function ARTY:_AttackGroup(target) local group=self.Controllable @@ -45827,10 +50115,13 @@ 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 +if group:HasTask()then +group:ClearTasks() +end group:OptionROEOpenFire() local targetgroup=GROUP:FindByName(target.name) local fire=group:TaskAttackGroup(targetgroup,weapontype,AI.Task.WeaponExpend.ONE,1) -group:SetTask(fire) +group:SetTask(fire,1) end function ARTY:_NuclearBlast(_coord) local S0=self.nukewarhead @@ -45949,6 +50240,7 @@ local nammo=0 local nshells=0 local nrockets=0 local nmissiles=0 +local nartyshells=0 local units=self.Controllable:GetUnits() if units==nil then return nammo,nshells,nrockets,nmissiles @@ -46016,6 +50308,8 @@ end end if _gotshell then nshells=nshells+Nammo +local _,_,_,_,_,shells=unit:GetAmmunition() +nartyshells=nartyshells+shells text=text..string.format("- %d shells of type %s\n",Nammo,_weaponName) elseif _gotrocket then nrockets=nrockets+Nammo @@ -46039,7 +50333,7 @@ MESSAGE:New(text,10):ToAllIf(display) end end nammo=nshells+nrockets+nmissiles -return nammo,nshells,nrockets,nmissiles +return nammo,nshells,nrockets,nmissiles,nartyshells end function ARTY:_MissileCategoryName(categorynumber) local cat="unknown" @@ -46488,7 +50782,8 @@ 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(string.format("dt = %d WaitTime = %d | shots = %d TargetShells = %d",dt,self.WaitForShotTime,self.Nshots,self.currentTarget.nshells)) +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) @@ -46522,13 +50817,13 @@ self:T2(self.lid..string.format("WARNING: Move with name %s could not be found. return nil end function ARTY:_CheckOutOfAmmo(targets) -local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() +local _nammo,_nshells,_nrockets,_nmissiles,_narty=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 +elseif Target.weapontype==ARTY.WeaponType.Cannon and _narty==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 @@ -46551,12 +50846,12 @@ end return _partlyoutofammo end function ARTY:_CheckWeaponTypeAvailable(target) -local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() +local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo() local nfire=Nammo if target.weapontype==ARTY.WeaponType.Auto then nfire=Nammo elseif target.weapontype==ARTY.WeaponType.Cannon then -nfire=Nshells +nfire=Narty elseif target.weapontype==ARTY.WeaponType.TacticalNukes then nfire=self.Nukes elseif target.weapontype==ARTY.WeaponType.IlluminationShells then @@ -47585,7 +51880,7 @@ eventsmoose=true, reportplayername=false, } PSEUDOATC.id="PseudoATC | " -PSEUDOATC.version="0.10.5" +PSEUDOATC.version="0.10.6" function PSEUDOATC:New() local self=BASE:Inherit(self,BASE:New()) self:E(PSEUDOATC.id..string.format("PseudoATC version %s",PSEUDOATC.version)) @@ -47832,6 +52127,9 @@ 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) +if self.radios then +missionCommands.addCommandForGroup(GID,"Radios",submenu,self.ReportRadios,self,GID,UID,pos,name) +end self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d",name,GID)) end end @@ -47892,6 +52190,20 @@ 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:ReportRadios(GID,UID,position,location) +self:F({GID=GID,UID=UID,position=position,location=location}) +if self.radios then +local Text="" +local radio=self.radios:GetClosestRadio(position,9) +if radio then +Text=self.radios:_GetMarkerText(radio) +else +Text=self.group[GID].player[UID].playername..", no radio information found!" +end +self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,Text,self.mdur,true) +end +return self +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 @@ -48024,6 +52336,14 @@ local pname=unit:GetPlayerName() local csign=unit:GetCallsign() return string.format("%s (%s)",csign,pname) end +function PSEUDOATC:SetUsingRadioInformationFromMap(path) +if RADIOS and lfs and io then +self.radios=RADIOS:NewFromFile(path) +else +self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -48513,7 +52833,7 @@ return _nstock end end function WAREHOUSE:GetCoordinate() -return self.warehouse:GetCoordinate() +return self.warehouse:GetCoord() end function WAREHOUSE:GetVec3() local vec3=self.warehouse:GetVec3() @@ -48604,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -48901,7 +53220,6 @@ 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) @@ -49005,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -49099,6 +53418,9 @@ self:I(self.lid..text) self:T({DCSdesc=asset.DCSdesc}) self:T3({Template=asset.template}) end +function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +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 @@ -49204,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -49914,6 +54235,9 @@ template.route.points[1].y=coord.z template.x=coord.x template.y=coord.z template.alt=coord.y +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(template.units) +end local group=_DATABASE:Spawn(template) return group end @@ -49953,7 +54277,7 @@ 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 +if parking and#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 @@ -49980,7 +54304,7 @@ else coord=parking[i].Coordinate terminal=parking[i].TerminalID end -if self.Debug then +if self.Debug and terminal then local text=string.format("Spawnplace unit %s terminal %d.",unit.name,terminal) coord:MarkToAll(text) env.info(text) @@ -50010,7 +54334,6 @@ 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 @@ -50279,7 +54602,6 @@ 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 @@ -50357,7 +54679,7 @@ local CountryNeutral=nil if gotunits then for _,_unit in pairs(units)do local unit=_unit -local distance=coord:Get2DDistance(unit:GetCoordinate()) +local distance=coord:Get2DDistance(unit:GetCoord()) if unit:IsGround()and unit:IsAlive()and distance<=radius then local _coalition=unit:GetCoalition() local _country=unit:GetCountry() @@ -50932,6 +55254,7 @@ if not self.allowSpawnOnClientSpots then local clients=_DATABASE.CLIENTS for clientname,client in pairs(clients)do local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) +if template then local units=template.units for i,unit in pairs(units)do local coord=COORDINATE:New(unit.x,unit.alt,unit.y) @@ -50939,6 +55262,7 @@ coords[unit.name]=coord end end end +end return coords end local parkingdata=airbase.parking @@ -51019,9 +55343,11 @@ break else if self.Debug then local coord=problem.coord +if coord then 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)) +coord:MarkToAll(text) +end else self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!",_termid)) end @@ -51184,7 +55510,8 @@ end function WAREHOUSE:_GetAttribute(group) local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN if group then -local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes") +local groupCat=group:GetCategory() +local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes")and groupCat==Group.Category.AIRPLANE 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") @@ -51298,7 +55625,6 @@ 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 @@ -51731,7 +56057,6 @@ explosionpower=0.1, explosiondist=200, explosiondist2=500, bigmissilemass=50, -destroy=nil, dt50=5, dt10=1, dt05=0.5, @@ -52099,7 +56424,7 @@ 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) +self:T(FOX.lid..text) MESSAGE:New(text,10):ToAllIf(self.Debug) for _,_player in pairs(self.players)do local player=_player @@ -52498,6 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -52509,6 +56835,7 @@ SAM_Table={}, SAM_Table_Long={}, SAM_Table_Medium={}, SAM_Table_Short={}, +SAM_Table_PointDef={}, lid="", Detection=nil, AWACS_Detection=nil, @@ -52542,6 +56869,14 @@ automode=true, autoshorad=true, ShoradGroupSet=nil, checkforfriendlies=false, +SmokeDecoy=false, +SmokeDecoyColor=SMOKECOLOR.White, +checkcounter=1, +DLinkCacheTime=120, +logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -52552,11 +56887,17 @@ MANTIS.SamType={ SHORT="Short", MEDIUM="Medium", LONG="Long", +POINT="Point", } +MANTIS.radiusscale={} +MANTIS.radiusscale[MANTIS.SamType.LONG]=1.1 +MANTIS.radiusscale[MANTIS.SamType.MEDIUM]=1.2 +MANTIS.radiusscale[MANTIS.SamType.SHORT]=1.75 +MANTIS.radiusscale[MANTIS.SamType.POINT]=3 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"}, +["Patriot"]={Range=99,Blindspot=0,Height=25,Type="Long",Radar="Patriot str"}, ["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"}, @@ -52564,80 +56905,122 @@ MANTIS.SamData={ ["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"}, +["Roland"]={Range=6,Blindspot=0,Height=5,Type="Short",Radar="Roland"}, +["Gepard"]={Range=5,Blindspot=0,Height=4,Type="Point",Radar="Gepard"}, ["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-9"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Strela",Point="true"}, ["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"}, +["SA-15"]={Range=11,Blindspot=0,Height=6,Type="Point",Radar="Tor 9A331",Point="true"}, +["SA-13"]={Range=5,Blindspot=0,Height=3,Type="Point",Radar="Strela",Point="true"}, ["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"}, +["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Linebacker",Point="true"}, ["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"}, +["C-RAM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="HEMTT_C-RAM_Phalanx",Point="true"}, ["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-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["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"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, +["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, +["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["IRIS-T SLM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"}, } 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-10B HDS"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85CE ln"}, +["SA-10C HDS"]={Range=75,Blindspot=5,Height=25,Type="Long",Radar="5P85SE ln"}, +["SA-17 HDS"]={Range=50,Blindspot=3,Height=50,Type="Medium",Radar="SA-17 "}, +["SA-12 HDS 2"]={Range=100,Blindspot=13,Height=30,Type="Long",Radar="S-300V 9A82 l"}, +["SA-12 HDS 1"]={Range=75,Blindspot=6,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"}, +["SAMPT Block 1 HDS"]={Range=120,Blindspot=1,Height=20,Type="long",Radar="SAMPT_MLT_Blk1"}, +["SAMPT Block 1INT HDS"]={Range=150,Blindspot=1,Height=25,Type="long",Radar="SAMPT_MLT_Blk1NT"}, +["SAMPT Block 2 HDS"]={Range=200,Blindspot=10,Height=70,Type="long",Radar="SAMPT_MLT_Blk2"}, } 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"}, +["RBS98M SMA"]={Range=20,Blindspot=0.2,Height=8,Type="Short",Radar="RBS-98"}, +["RBS70 SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-70"}, +["RBS70M SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS70"}, +["RBS90 SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-90"}, +["RBS90M SMA"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS90"}, +["RBS103A SMA"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_Rb103A"}, +["RBS103B SMA"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103B"}, +["RBS103AM SMA"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"}, +["RBS103BM SMA"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103B"}, +["Lvkv9040M SMA"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } 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"}, +["2S38 CHM"]={Range=6,Blindspot=0.1,Height=4.5,Type="Short",Radar="2S38"}, +["PantsirS1 CHM"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, +["PantsirS2 CHM"]={Range=30,Blindspot=1.2,Height=18,Type="Medium",Radar="PantsirS2"}, +["PGL-625 CHM"]={Range=10,Blindspot=1,Height=5,Type="Short",Radar="PGL_625"}, +["HQ-17A CHM"]={Range=15,Blindspot=1.5,Height=10,Type="Short",Radar="HQ17A"}, +["M903PAC2 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="MIM104_M903_PAC2"}, +["M903PAC3 CHM"]={Range=160,Blindspot=1,Height=40,Type="Long",Radar="MIM104_M903_PAC3"}, +["TorM2 CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["TorM2K CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2K",Point="true"}, +["TorM2M CHM"]={Range=16,Blindspot=1,Height=10,Type="Point",Radar="TorM2M",Point="true"}, +["NASAMS3-AMRAAMER CHM"]={Range=50,Blindspot=2,Height=35.7,Type="Medium",Radar="CH_NASAMS3_LN_AMRAAM_ER"}, +["NASAMS3-AIM9X2 CHM"]={Range=20,Blindspot=0.2,Height=18,Type="Short",Radar="CH_NASAMS3_LN_AIM9X2"}, +["C-RAM CHM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="CH_Centurion_C_RAM",Point="true"}, +["PGZ-09 CHM"]={Range=4,Blindspot=0.5,Height=3,Type="Point",Radar="CH_PGZ09",Point="true"}, +["S350-9M100 CHM"]={Range=15,Blindspot=1,Height=8,Type="Short",Radar="CH_S350_50P6_9M100"}, +["S350-9M96D CHM"]={Range=150,Blindspot=2.5,Height=30,Type="Long",Radar="CH_S350_50P6_9M96D"}, +["LAV-AD CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_LAVAD"}, +["HQ-22 CHM"]={Range=170,Blindspot=5,Height=27,Type="Long",Radar="CH_HQ22_LN"}, +["PGZ-95 CHM"]={Range=2.5,Blindspot=0.5,Height=2,Type="Point",Radar="CH_PGZ95",Point="true"}, +["LD-3000 CHM"]={Range=2.5,Blindspot=0.1,Height=3,Type="Point",Radar="CH_LD3000_stationary",Point="true"}, +["LD-3000M CHM"]={Range=2.5,Blindspot=0.1,Height=3,Type="Point",Radar="CH_LD3000",Point="true"}, +["FlaRakRad CHM"]={Range=8,Blindspot=1.5,Height=6,Type="Short",Radar="CH_FlaRakRad"}, +["IRIS-T SLM CHM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"}, +["M903PAC2KAT1 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="CH_MIM104_M903_PAC2_KAT1"}, +["Skynex CHM"]={Range=3.5,Blindspot=0.1,Height=3.5,Type="Point",Radar="CH_SkynexHX",Point="true"}, +["Skyshield CHM"]={Range=3.5,Blindspot=0.1,Height=3.5,Type="Point",Radar="CH_Skyshield_Gun",Point="true"}, +["WieselOzelot CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_Wiesel2Ozelot"}, +["BukM3-9M317M CHM"]={Range=70,Blindspot=0.25,Height=35,Type="Medium",Radar="CH_BukM3_9A317M"}, +["BukM3-9M317MA CHM"]={Range=70,Blindspot=0.25,Height=35,Type="Medium",Radar="CH_BukM3_9A317MA"}, +["SkySabre CHM"]={Range=30,Blindspot=0.5,Height=10,Type="Medium",Radar="CH_SkySabreLN"}, +["Stormer CHM"]={Range=7.5,Blindspot=0.3,Height=7,Type="Short",Radar="CH_StormerHVM"}, +["THAAD CHM"]={Range=200,Blindspot=40,Height=150,Type="Long",Radar="CH_THAAD_M1120"}, +["USInfantryFIM92K CHM"]={Range=8,Blindspot=0.16,Height=4.8,Type="Short",Radar="CH_USInfantry_FIM92"}, +["RBS98M CHM"]={Range=20,Blindspot=0.2,Height=8,Type="Short",Radar="RBS-98"}, +["RBS70 CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-70"}, +["RBS70M CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS70"}, +["RBS90 CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="RBS-90"}, +["RBS90M CHM"]={Range=8,Blindspot=0.25,Height=6,Type="Short",Radar="BV410_RBS90"}, +["RBS103A CHM"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_Rb103A"}, +["RBS103B CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_Rb103B"}, +["RBS103AM CHM"]={Range=160,Blindspot=1,Height=36,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103A"}, +["RBS103BM CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="LvS-103_Lavett103_HX_Rb103B"}, +["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) +self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} self.SAM_Table_Short={} +self.SAM_Table_PointDef={} self.dynamic=dynamic or false self.checkradius=25000 self.grouping=5000 @@ -52665,10 +57048,6 @@ 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={} @@ -52676,6 +57055,7 @@ self.ConflictZones={} self.maxlongrange=1 self.maxmidrange=2 self.maxshortrange=2 +self.maxpointdefrange=6 self.maxclassic=6 self.autoshorad=true self.ShoradGroupSet=SET_GROUP:New() @@ -52683,6 +57063,8 @@ self.FilterZones=Zones self.SkateZones=nil self.SkateNumber=3 self.shootandscoot=false +self.SmokeDecoy=false +self.SmokeDecoyColor=SMOKECOLOR.White self.UseEmOnOff=true if EmOnOff==false then self.UseEmOnOff=false @@ -52692,6 +57074,7 @@ self.advAwacs=true else self.advAwacs=false end +self:SetDLinkCacheTime() self.lid=string.format("MANTIS %s | ",self.name) if self.debug then BASE:TraceOnOff(true) @@ -52714,6 +57097,7 @@ end if self.advAwacs then table.insert(self.ewr_templates,awacs) end +self.logsamstatus=false 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) @@ -52730,7 +57114,7 @@ end if self.HQ_Template_CC then self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC) end -self.version="0.8.16" +self.checkcounter=1 self:I(string.format("***** Starting MANTIS Version %s *****",self.version)) self:SetStartState("Stopped") self:AddTransition("Stopped","Start","Running") @@ -52743,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -52775,11 +57264,78 @@ 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.AcceptZonesNo=UTILS.TableLength(self.AcceptZones) +self.RejectZonesNo=UTILS.TableLength(self.RejectZones) +self.ConflictZonesNo=UTILS.TableLength(self.ConflictZones) +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 or self.RejectZonesNo>0 or self.ConflictZonesNo>0 then self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -52799,12 +57355,18 @@ end self.engagerange=range return self end -function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic) +function MANTIS:SetSmokeDecoy(Onoff,Color) +self.SmokeDecoy=Onoff +self.SmokeDecoyColor=Color or SMOKECOLOR.White +return self +end +function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) 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 +self.maxpointdefrange=Point or 6 return self end function MANTIS:SetNewSAMRangeWhileRunning(range) @@ -52869,6 +57431,11 @@ end end return self end +function MANTIS:SetDLinkCacheTime(seconds) +self.DLinkCacheTime=math.abs(seconds or 120) +if self.DLinkCacheTime<5 then self.DLinkCacheTime=5 end +return self +end function MANTIS:SetDetectInterval(interval) self:T(self.lid.."SetDetectInterval") local interval=interval or 30 @@ -52946,6 +57513,20 @@ end end return self end +function MANTIS:_CheckAnyEWRAlive() +self:T(self.lid.."_CheckAnyEWRAlive") +local alive=false +if self.EWR_Group:CountAlive()>0 then +alive=true +end +if not alive and self.AWACS_Prefix then +local awacs=GROUP:FindByName(self.AWACS_Prefix) +if awacs and awacs:IsAlive()then +alive=true +end +end +return alive +end function MANTIS:_CalcAdvState() self:T(self.lid.."CalcAdvState") local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug) @@ -53011,7 +57592,8 @@ end function MANTIS:_CheckCoordinateInZones(coord) self:T(self.lid.."_CheckCoordinateInZones") local inzone=false -if#self.AcceptZones>0 then +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 then for _,_zone in pairs(self.AcceptZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -53021,7 +57603,7 @@ break end end end -if#self.RejectZones>0 and inzone then +if self.RejectZonesNo>0 then for _,_zone in pairs(self.RejectZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -53031,7 +57613,7 @@ break end end end -if#self.ConflictZones>0 and not inzone then +if self.ConflictZonesNo>0 then for _,_zone in pairs(self.ConflictZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -53043,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -53052,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -53065,11 +57661,10 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end -local friendlyset -if self.checkforfriendlies==true then -friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterOnce() +if self.checkforfriendlies==true and self.friendlyset==nil then +self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() end for _,_coord in pairs(set)do local coord=_coord @@ -53078,23 +57673,24 @@ if not targetdistance then targetdistance=samcoordinate:Get2DDistance(coord) end local zonecheck=true +self:T("self.usezones = "..tostring(self.usezones)) if self.usezones then zonecheck=self:_CheckCoordinateInZones(coord) end if self.verbose and self.debug then -local dectstring=coord:ToStringLLDMS() -local samstring=samcoordinate:ToStringLLDMS() +local samstring=samcoordinate:ToStringMGRS({MGRS_Accuracy=0}) +samstring=string.gsub(samstring,"%s","") local inrange="false" if targetdistance<=rad then inrange="true" end -local text=string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s",samstring,targetdistance,rad,inrange) +local text=string.format("Checking SAM at %s | Tgtdist %.1fkm | Rad %.1fkm | Inrange %s",samstring,targetdistance/1000,rad/1000,inrange) local m=MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) self:T(self.lid..text) end local nofriendlies=true if self.checkforfriendlies==true then -local closestfriend,distance=friendlyset:GetClosestGroup(samcoordinate) +local closestfriend,distance=self.friendlyset:GetClosestGroup(samcoordinate) if closestfriend and distance and distance290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 +local instatusred=0 +local instatusgreen=0 +local activeshorads=0 +local SEADactive=0 for _,_data in pairs(samset)do local samcoordinate=_data[2] local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false +if not shortsam then +shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false +end local samgroup=GROUP:FindByName(name) local IsInZone,Distance=self:_CheckObjectInZone(detset,samcoordinate,radius,height,dlink) local suppressed=self.SuppressedGroups[name]or false -local activeshorad=self.Shorad.ActiveGroups[name]or false -if IsInZone and not suppressed and not activeshorad then +local activeshorad=false +if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name]then +activeshorad=true +end +if IsInZone and(not suppressed)and(not activeshorad)then if samgroup:IsAlive()then local switch=false if self.UseEmOnOff and switchedon65 then +if self.checkcounter%3==0 then self:_RefreshSAMTable() end +self.checkcounter=self.checkcounter+1 +local instatusred=0 +local instatusgreen=0 +local activeshorads=0 if self.automode then local samset=self.SAM_Table_Long -self:_CheckLoop(samset,detset,dlink,self.maxlongrange) +local instatusredl,instatusgreenl,activeshoradsl=self:_CheckLoop(samset,detset,dlink,self.maxlongrange) local samset=self.SAM_Table_Medium -self:_CheckLoop(samset,detset,dlink,self.maxmidrange) +local instatusredm,instatusgreenm,activeshoradsm=self:_CheckLoop(samset,detset,dlink,self.maxmidrange) local samset=self.SAM_Table_Short -self:_CheckLoop(samset,detset,dlink,self.maxshortrange) +local instatusreds,instatusgreens,activeshoradss=self:_CheckLoop(samset,detset,dlink,self.maxshortrange) +local samset=self.SAM_Table_PointDef +instatusred,instatusgreen,activeshorads=self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) else local samset=self:_GetSAMTable() -self:_CheckLoop(samset,detset,dlink,self.maxclassic) +instatusred,instatusgreen,activeshorads=self:_CheckLoop(samset,detset,dlink,self.maxclassic) +end +local function GetReport() +local statusreport=REPORT:New("\nMANTIS Status "..self.name) +statusreport:Add("+-----------------------------+") +statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) +statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen)) +if self.Shorad then +statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads)) +end +statusreport:Add("+-----------------------------+") +return statusreport +end +if self.debug or self.verbose then +local statusreport=GetReport() +MESSAGE:New(statusreport:Text(),10):ToAll():ToLog() +elseif reporttolog==true then +local statusreport=GetReport() +MESSAGE:New(statusreport:Text(),10):ToLog() end return self end @@ -53472,7 +58193,7 @@ 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=SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.Coalition,self.UseEmOnOff,self.SmokeDecoy,self.SmokeDecoyColor) self.Shorad:SetDefenseLimits(80,95) self.ShoradLink=true self.Shorad.Groupset=self.ShoradGroupSet @@ -53481,13 +58202,39 @@ 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:HandleEvent(EVENTS.Hit,self._EventHandler) +self:HandleEvent(EVENTS.UnitLost,self._EventHandler) 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) +self:_Check(self.Detection,self.DLink,self.logsamstatus) +end +local EWRAlive=self:_CheckAnyEWRAlive() +local function FindSAMSRTR() +for i=1,1000 do +local randomsam=self.SAM_Group:GetRandom() +if randomsam and randomsam:IsAlive()then +if randomsam:IsSAM()then return randomsam end +end +end +end +if not EWRAlive then +local randomsam=FindSAMSRTR() +if randomsam and randomsam:IsAlive()then +if self.UseEmOnOff then +randomsam:EnableEmission(true) +else +randomsam:OptionAlarmStateRed() +end +local name=randomsam:GetName() +if self.SamStateTracker[name]~="RED"then +self:__RedState(1,randomsam) +self.SamStateTracker[name]="RED" +end +end end if self.autorelocate then local relointerval=self.relointerval @@ -53513,7 +58260,7 @@ 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)) +self:I(string.format("Site %s | Status %s",_name,_state)) end end local interval=self.detectinterval*-1 @@ -53551,7 +58298,7 @@ if self.ShoradLink then local Shorad=self.Shorad local radius=self.checkradius local ontime=self.ShoradTime -Shorad:WakeUpShorad(Name,radius,ontime) +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) self:__ShoradActivated(1,Name,radius,ontime) end return self @@ -53587,8 +58334,10 @@ shootandscoot=false, SkateNumber=3, SkateZones=nil, minscootdist=100, -minscootdist=3000, +maxscootdist=3000, scootrandomcoord=false, +SmokeDecoy=false, +SmokeDecoyColor=SMOKECOLOR.White } do SHORAD.Harms={ @@ -53614,13 +58363,14 @@ SHORAD.Mavs={ ["Kh31"]="Kh31", ["Kh66"]="Kh66", } -function SHORAD:New(Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition,UseEmOnOff) +function SHORAD:New(Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition,UseEmOnOff,SmokeDecoy,SmokeDecoyColor) 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 +if type(Coalition)=="number"then Coalition=string.lower(UTILS.GetCoalitionName(Coalition))end self.Coalition=Coalition or"blue" self.Samset=Samset or GroupSet self.ActiveTimer=ActiveTimer or 600 @@ -53632,7 +58382,11 @@ 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") +if SmokeDecoy then +self.SmokeDecoy=SmokeDecoy +self.SmokeDecoyColor=SmokeDecoyColor or SMOKECOLOR.White +end +self:I("*** SHORAD - Started Version 0.3.5") self.lid=string.format("SHORAD %s | ",self.name) self:_InitState() self:HandleEvent(EVENTS.Shot,self.HandleEventShot) @@ -53803,11 +58557,34 @@ local returnname=false for _,_groups in pairs(shoradset)do local groupname=_groups:GetName() if string.find(groupname,tgtgrp,1,true)then +if _groups:IsSAM()then returnname=true end end +end return returnname end +function SHORAD:_SmokeUnits(Group) +if self.SmokeDecoy==true then +if Group and Group:IsAlive()then +local units=Group:GetUnits() +for _,_unit in pairs(units)do +local unit=_unit +if unit and unit:IsAlive()then +local coordinate=unit:GetCoordinate() +if coordinate then +coordinate:SwitchSmokeOffsetOn() +coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,1,20) +coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,180,20) +coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,270,20) +coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,90,20) +end +end +end +end +end +return self +end function SHORAD:_ShotIsDetected() self:T(self.lid.." _ShotIsDetected") if self.debug then return true end @@ -53819,7 +58596,7 @@ IsDetected=true end return IsDetected end -function SHORAD:onafterWakeUpShorad(From,Event,To,TargetGroup,Radius,ActiveTimer,TargetCat) +function SHORAD:onafterWakeUpShorad(From,Event,To,TargetGroup,Radius,ActiveTimer,TargetCat,ShotAt) self:T(self.lid.." WakeUpShorad") self:T({TargetGroup,Radius,ActiveTimer,TargetCat}) local targetcat=TargetCat or Object.Category.UNIT @@ -53856,7 +58633,21 @@ end end local TDiff=4 for _,_group in pairs(shoradset)do -if _group:IsAnyInZone(targetzone)then +local groupname=_group:GetName() +if groupname==TargetGroup and ShotAt==true then +if self.UseEmOnOff then +_group:EnableEmission(false) +end +_group:OptionAlarmStateGreen() +self.ActiveGroups[groupname]=nil +local text=string.format("Shot at SHORAD %s! Evading!",_group:GetName()) +self:T(text) +local m=MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) +self:_SmokeUnits(_group) +if self.shootandscoot then +self:__ShootAndScoot(1,_group) +end +elseif _group:IsAnyInZone(targetzone)or groupname==TargetGroup then local text=string.format("Waking up SHORAD %s",_group:GetName()) self:T(text) local m=MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) @@ -53864,7 +58655,7 @@ if self.UseEmOnOff then _group:EnableEmission(true) end _group:OptionAlarmStateRed() -local groupname=_group:GetName() +self:_SmokeUnits(_group) if self.ActiveGroups[groupname]==nil then self.ActiveGroups[groupname]={Timing=ActiveTimer} local endtime=timer.getTime()+(ActiveTimer*math.random(75,100)/100) @@ -53922,7 +58713,7 @@ _targetgroup=tgtgrp _targetgroupname=tgtgrp:GetName() _targetskill=tgtgrp:GetUnit(1):GetSkill() self:T("*** Found Target = ".._targetgroupname) -self:WakeUpShorad(_targetgroupname,self.Radius,self.ActiveTimer,Object.Category.UNIT) +self:WakeUpShorad(_targetgroupname,self.Radius,self.ActiveTimer,Object.Category.UNIT,true) end end end @@ -53963,6 +58754,7 @@ self:T({EventData}) self:T(self.lid.." HandleEventShot") local ShootingWeapon=EventData.Weapon local ShootingWeaponName=EventData.WeaponName +if not EventData.IniGroup then return self end local weaponcoalition=EventData.IniGroup:GetCoalition() if self:_CheckCoalition(weaponcoalition)then local IsDetected=self:_ShotIsDetected() @@ -54028,7 +58820,7 @@ local shotatus=self:_CheckShotAtShorad(targetgroupname) local shotatsams=self:_CheckShotAtSams(targetgroupname) if shotatsams or shotatus then self:T({shotatsams=shotatsams,shotatus=shotatus}) -self:WakeUpShorad(targetgroupname,self.Radius,self.ActiveTimer,targetcat) +self:WakeUpShorad(targetgroupname,self.Radius,self.ActiveTimer,targetcat,true) end end end @@ -54038,7 +58830,7 @@ end end AICSAR={ ClassName="AICSAR", -version="0.1.16", +version="0.1.18", lid="", coalition=coalition.side.BLUE, template="", @@ -54083,6 +58875,8 @@ Speed=100, Altitude=1500, UseEventEject=false, Delay=100, +UseRescueZone=false, +RescueZone=nil, } AICSAR.Messages={ EN={ @@ -54126,7 +58920,7 @@ PILOTRESCUED=3.5, PILOTINHELO=2.6, }, } -function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) +function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Helonumber) local self=BASE:Inherit(self,FSM:New()) if Coalition and type(Coalition)=="string"then if Coalition=="blue"then @@ -54181,7 +58975,7 @@ self.DCSRadioGroup=nil self.DCSRadioQueue=nil self.MGRS_Accuracy=2 self.limithelos=true -self.helonumber=3 +self.helonumber=Helonumber or 3 self:InitLocalization() self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") self.PilotStore=FIFO:New() @@ -54223,12 +59017,17 @@ self.gettext:AddEntry("de","PILOTRESCUED",AICSAR.Messages.DE.PILOTRESCUED,AICSAR self.locale="en" return self end +function AICSAR:SetUsingRescueZone(Zone) +self.UseRescueZone=true +self.RescueZone=Zone +return self +end function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) self:T(self.lid.."SetSRSRadio") self.SRSRadio=OnOff and true self.SRSTTSRadio=false self.SRSFrequency=Frequency or 243 -self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" +self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRS:SetLabel("ACSR") self.SRS:SetCoalition(self.coalition) self.SRSModulation=Modulation or radio.modulation.AM @@ -54246,7 +59045,7 @@ self:T(self.lid.."SetSRSTTSRadio") self.SRSTTSRadio=OnOff and true self.SRSRadio=false self.SRSFrequency=Frequency or 243 -self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" +self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRSModulation=Modulation or radio.modulation.AM self.SRSPort=Port or MSRS.port or 5002 if OnOff then @@ -54332,7 +59131,6 @@ if self.UseEventEject then local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p) local _country=_event.initiator:getCountry() local _coalition=coalition.getCountryCoalition(_country) -local data=UTILS.DeepCopy(EventData) Unit.destroy(_event.initiator) self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition) end @@ -54341,6 +59139,13 @@ return self end function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition) local distancetofarp=_LandingPos:Get2DDistance(self.farp:GetCoordinate()) +if self.UseRescueZone==true and self.RescueZone~=nil then +if self.RescueZone:IsCoordinateInZone(_LandingPos)then +distancetofarp=self.maxdistance-10 +else +distancetofarp=self.maxdistance+10 +end +end local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale) local text="" local setting={} @@ -54407,6 +59212,13 @@ 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()) +if self.UseRescueZone==true and self.RescueZone~=nil then +if self.RescueZone:IsCoordinateInZone(_LandingPos)then +distancetofarp=self.maxdistance-10 +else +distancetofarp=self.maxdistance+10 +end +end local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale) local text="" local setting={} @@ -54468,6 +59280,7 @@ local newhelo=SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.rando :InitUnControlled(true) :OnSpawnGroup( function(Group) +Group:OptionPreferVerticalLanding() self:__HeloOnDuty(1,Group) end ) @@ -54506,6 +59319,9 @@ function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo) AICHeloUnloaded(helo,OpsGroupCargo) helo:__UnloadingDone(5) end +function helo:OnAfterLandAtAirbase(From,Event,To,airbase) +helo:Despawn(2) +end self.helos[Index]=helo return self end @@ -54536,7 +59352,8 @@ local state=helo:GetState() local name=helo:GetName() self:T("Helo group "..name.." in state "..state) if state=="Arrived"then -helo:__Stop(5) +helo.OnAfterDead=nil +helo:Despawn(35) self.helos[_index]=nil end else @@ -54565,7 +59382,6 @@ 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 @@ -55252,8 +60068,11 @@ verbose=0, alias="", debug=false, smokemenu=true, +RoundingPrecision=0, +increasegroundawareness=false, +MonitorFrequency=30, } -AUTOLASE.version="0.1.23" +AUTOLASE.version="0.1.31" function AUTOLASE:New(RecceSet,Coalition,Alias,PilotSet) BASE:T({RecceSet,Coalition,Alias,PilotSet}) local self=BASE:Inherit(self,BASE:New()) @@ -55306,6 +60125,7 @@ self.reporttimeshort=10 self.reporttimelong=30 self.smoketargets=false self.smokecolor=SMOKECOLOR.Red +self.smokeoffset=nil self.notifypilots=true self.targetsperrecce={} self.RecceUnits={} @@ -55321,6 +60141,11 @@ self.blacklistattributes={} self:SetLaserCodes({1688,1130,4785,6547,1465,4578}) self.playermenus={} self.smokemenu=true +self.threatmenu=true +self.RoundingPrecision=0 +self.increasegroundawareness=false +self.MonitorFrequency=30 +self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) self.lid=string.format("AUTOLASE %s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown") self:AddTransition("*","Monitor","*") self:AddTransition("*","Lasing","*") @@ -55339,18 +60164,33 @@ self:__Start(2) self:__Monitor(math.random(5,10)) return self end +function AUTOLASE:SetMonitorFrequency(Seconds) +self.MonitorFrequency=Seconds or 30 +return self +end function AUTOLASE:SetLaserCodes(LaserCodes) self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} return self end +function AUTOLASE:EnableImproveGroundUnitsDetection() +self.increasegroundawareness=true +return self +end +function AUTOLASE:DisableImproveGroundUnitsDetection() +self.increasegroundawareness=false +return self +end function AUTOLASE:SetPilotMenu() if self.usepilotset then local pilottable=self.pilotset:GetSetObjects()or{} +local grouptable={} for _,_unit in pairs(pilottable)do local Unit=_unit if Unit and Unit:IsAlive()then local Group=Unit:GetGroup() +local GroupName=Group:GetName()or"none" local unitname=Unit:GetName() +if not grouptable[GroupName]==true then if self.playermenus[unitname]then self.playermenus[unitname]:Remove()end local lasetopm=MENU_GROUP:New(Group,"Autolase",nil) self.playermenus[unitname]=lasetopm @@ -55360,6 +60200,13 @@ 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 +if self.threatmenu then +local threatmenutop=MENU_GROUP:New(Group,"Set min lasing threat",lasetopm) +for i=0,10,2 do +local text="Threatlevel "..tostring(i) +local threatmenu=MENU_GROUP_COMMAND:New(Group,text,threatmenutop,self.SetMinThreatLevel,self,i) +end +end for _,_grp in pairs(self.RecceSet.Set)do local grp=_grp local unit=grp:GetUnit(1) @@ -55375,6 +60222,8 @@ local changemenu=MENU_GROUP_COMMAND:New(Group,text,unittop,self.SetRecceLaserCod end end end +grouptable[GroupName]=true +end end end else @@ -55426,7 +60275,7 @@ 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.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRSFreq=Frequency or 271 self.SRSMod=Modulation or radio.modulation.AM self.Gender=Gender or MSRS.gender or"male" @@ -55507,12 +60356,30 @@ local Message="Smoking targets is now "..smktxt.."!" self:NotifyPilots(Message,10) return self end -function AUTOLASE:EnableSmokeMenu() +function AUTOLASE:SetRoundingPrecsion(IDP) +self.RoundingPrecision=IDP or 0 +return self +end +function AUTOLASE:EnableSmokeMenu(Offset) self.smokemenu=true +if Offset then +self.smokeoffset={} +self.smokeoffset.Distance=Offset.Distance or math.random(10,20) +self.smokeoffset.Angle=Offset.Angle or math.random(0,359) +end return self end function AUTOLASE:DisableSmokeMenu() self.smokemenu=false +self.smokeoffset=nil +return self +end +function AUTOLASE:EnableThreatLevelMenu() +self.threatmenu=true +return self +end +function AUTOLASE:DisableThreatLevelMenu() +self.threatmenu=false return self end function AUTOLASE:GetLosFromUnit(Unit) @@ -55544,7 +60411,8 @@ 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()} +local isground=(unit and unit.IsGround)and unit:IsGround()or false +self.RecceUnits[name]={name=name,unit=unit,cooldown=false,timestamp=timer.getAbsTime(),isground=isground} end end end @@ -55617,8 +60485,11 @@ name=string.match(name,"^(.*)#") end local code=self:GetLaserCode(unit:GetName()) report:Add(string.format("Recce %s has code %d",name,code)) +report:Add("---------------") end end +report:Add(string.format("Lasing min threat level %d",self.minthreatlevel)) +report:Add("---------------") local lines=0 for _ind,_entry in pairs(self.CurrentLasing)do local entry=_entry @@ -55638,22 +60509,27 @@ end if playername then local settings=_DATABASE:GetPlayerSettings(playername) if settings then -self:I("Get Settings ok!") +self:T("Get Settings ok!") if settings:IsA2G_MGRS()then locationstring=entry.coordinate:ToStringMGRS(settings) elseif settings:IsA2G_LL_DMS()then locationstring=entry.coordinate:ToStringLLDMS(settings) +elseif settings:IsA2G_LL_DDM()then +locationstring=entry.coordinate:ToStringLLDDM(settings) elseif settings:IsA2G_BR()then -locationstring=entry.coordinate:ToStringBR(Group:GetCoordinate()or Unit:GetCoordinate(),settings) +local startcoordinate=Unit:GetCoordinate()or Group:GetCoordinate() +locationstring=entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision) end end end -local text=string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring) +local text=string.format("+ %s lasing %s code %d\nat %s",reccename,typename,code,locationstring) report:Add(text) +report:Add("---------------") lines=lines+1 end if lines==0 then report:Add("No targets!") +report:Add("---------------") end local reporttime=self.reporttimelong if lines==0 then reporttime=self.reporttimeshort end @@ -55738,8 +60614,57 @@ end end return canlase end +function AUTOLASE:_Prescient() +for _,_data in pairs(self.RecceUnits)do +if _data.isground and _data.unit and _data.unit:IsAlive()then +local unit=_data.unit +local position=unit:GetCoordinate() +local needsinit=false +if position then +local lastposition=unit:GetProperty("lastposition") +if not lastposition then +unit:SetProperty("lastposition",position) +lastposition=position +needsinit=true +end +local dist=position:Get2DDistance(lastposition) +local TNow=timer.getAbsTime() +if dist>10 or needsinit==true or TNow-_data.timestamp>29 then +local hasunits,hasstatics,_,Units,Statics=position:ScanObjects(self.LaseDistance,true,true,false) +if hasunits then +self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName()) +for _,_target in pairs(Units)do +local target=_target +if target and target:GetCoalition()~=self.coalition then +if unit:IsLOS(target)and(not target:IsUnitDetected(unit))then +unit:KnowUnit(target,true,true) +end +end +end +end +if hasstatics then +self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) +for _,_static in pairs(Statics)do +local static=STATIC:Find(_static) +if static and static:GetCoalition()~=self.coalition and static:GetCoordinate()then +local IsLOS=position:IsLOS(static:GetCoordinate()) +if IsLOS then +unit:KnowUnit(static,true,true) +end +end +end +end +end +end +end +end +return self +end function AUTOLASE:onbeforeMonitor(From,Event,To) self:T({From,Event,To}) +if self.increasegroundawareness then +self:_Prescient() +end self:UpdateIntel() return self end @@ -55865,6 +60790,9 @@ coordinate=unit:GetCoordinate(), } if self.smoketargets then local coord=unit:GetCoordinate() +if self.smokeoffset then +coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true) +end local color=self:GetSmokeColor(reccename) coord:Smoke(color) end @@ -55874,7 +60802,8 @@ self:__Lasing(2,laserspot) end end end -self:__Monitor(-30) +local nextloop=-self.MonitorFrequency or-30 +self:__Monitor(nextloop) return self end function AUTOLASE:onbeforeRecceKIA(From,Event,To,RecceName) @@ -56070,7 +60999,7 @@ end TIRESIAS={ ClassName="TIRESIAS", debug=false, -version="0.0.4", +version=" 0.0.8", Interval=20, GroundSet=nil, VehicleSet=nil, @@ -56081,6 +61010,8 @@ AAARange=60, HeloSwitchRange=10, PlaneSwitchRange=25, SwitchAAA=true, +_cached_zones={}, +_cached_groupsets={}, } function TIRESIAS:New() local self=BASE:Inherit(self,FSM:New()) @@ -56088,9 +61019,10 @@ self:SetStartState("Stopped") self:AddTransition("Stopped","Start","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") -self.ExceptionSet=SET_GROUP:New():Clear(false) +self.ExceptionSet=SET_GROUP:New() +self._cached_zones={} self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) -self.lid=string.format("TIRESIAS %s | ",self.version) +self.lid="TIRESIAS "..self.version.." | " self:I(self.lid.."Managing ground groups!") self:__Start(1) return self @@ -56098,6 +61030,7 @@ end function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles) self.HeloSwitchRange=HeloMiles or 10 self.PlaneSwitchRange=PlaneMiles or 25 +self._cached_zones={} return self end function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA) @@ -56106,87 +61039,103 @@ self.SwitchAAA=(SwitchAAA==false)and false or true return self end function TIRESIAS:AddExceptionSet(Set) -self:T(self.lid.."AddExceptionSet") +self:T(self.lid.." AddExceptionSet") +if not self.ExceptionSet then +self.ExceptionSet=SET_GROUP:New() +end local exceptions=self.ExceptionSet -Set:ForEachGroupAlive( -function(grp) -if not grp.Tiresias then -grp.Tiresias={ -type="Exception", +local exception_data={ +type=" Exception", exception=true, } +Set:ForEachGroupAlive( +function(grp) +if grp:IsGround()and(not grp.Tiresias)then +grp.Tiresias=exception_data exceptions:AddGroup(grp,true) +BASE:T(" TIRESIAS: Added exception group: "..grp:GetName()) end -BASE:I("TIRESIAS: Added exception group: "..grp:GetName()) end ) return self end function TIRESIAS._FilterNotAAA(Group) local grp=Group -local isaaa=grp:IsAAA() -if isaaa==true and grp:IsGround()and not grp:IsShip()then -return false -else +local is_air=grp:IsAir() +local is_ship=grp:IsShip() +local is_AAA=grp:IsAAA() +if is_air or grp:IsShip()then return true end +return not is_AAA end function TIRESIAS._FilterNotSAM(Group) local grp=Group -local issam=grp:IsSAM() -if issam==true and grp:IsGround()and not grp:IsShip()then -return false -else +local is_air=grp:IsGround() +local is_ship=grp:IsShip() +local is_SAM=grp:IsSAM() +if is_air or grp:IsShip()then return true end +return not is_SAM end function TIRESIAS._FilterAAA(Group) local grp=Group -local isaaa=grp:IsAAA() -if isaaa==true and grp:IsGround()and not grp:IsShip()then -return true -else +local is_ground=grp:IsGround() +if(not is_ground)or grp:IsShip()then return false end +return grp:IsAAA() end function TIRESIAS._FilterSAM(Group) local grp=Group -local issam=grp:IsSAM() -if issam==true and grp:IsGround()and not grp:IsShip()then -return true -else +local is_ground=grp:IsGround() +if(not is_ground)or grp:IsShip()then return false end +return grp:IsSAM() end function TIRESIAS:_InitGroups() -self:T(self.lid.."_InitGroups") +self:T(self.lid.." _InitGroups") local EngageRange=self.AAARange local SwitchAAA=self.SwitchAAA +local aaa_data_template={ +type=" AAA", +invisible=true, +range=EngageRange, +exception=false, +AIOff=SwitchAAA, +} +local vehicle_data_template={ +type=" Vehicle", +invisible=true, +AIOff=true, +exception=false, +} +local sam_data_template={ +type=" SAM", +invisible=true, +exception=false, +} self.AAASet:ForEachGroupAlive( function(grp) -if not grp.Tiresias then +local tiresias_data=grp.Tiresias +if not tiresias_data then grp:OptionEngageRange(EngageRange) grp:SetCommandInvisible(true) if SwitchAAA then grp:SetAIOff() grp:EnableEmission(false) end -grp.Tiresias={ -type="AAA", -invisible=true, -range=EngageRange, -exception=false, -AIOff=SwitchAAA, -} -end -if grp.Tiresias and(not grp.Tiresias.exception==true)then -if grp.Tiresias.invisible and grp.Tiresias.invisible==false then +grp.Tiresias=aaa_data_template +elseif not tiresias_data.exception==true then +if not tiresias_data.invisible==true then grp:SetCommandInvisible(true) -grp.Tiresias.invisible=true -if SwitchAAA then +tiresias_data.invisible=true +if SwitchAAA==true then grp:SetAIOff() grp:EnableEmission(false) -grp.Tiresias.AIOff=true +tiresias_data.AIOff=true end end end @@ -56194,39 +61143,31 @@ end ) self.VehicleSet:ForEachGroupAlive( function(grp) -if not grp.Tiresias then +local tiresias_data=grp.Tiresias +if not tiresias_data then grp:SetAIOff() grp:SetCommandInvisible(true) -grp.Tiresias={ -type="Vehicle", -invisible=true, -AIOff=true, -exception=false, -} -end -if grp.Tiresias and(not grp.Tiresias.exception==true)then -if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible==false then +grp.Tiresias=vehicle_data_template +elseif not tiresias_data.exception==true then +if not tiresias_data.invisible then grp:SetCommandInvisible(true) grp:SetAIOff() -grp.Tiresias.invisible=true +tiresias_data.invisible=true +tiresias_data.AIOff=true end end end ) self.SAMSet:ForEachGroupAlive( function(grp) -if not grp.Tiresias then +local tiresias_data=grp.Tiresias +if not tiresias_data then grp:SetCommandInvisible(true) -grp.Tiresias={ -type="SAM", -invisible=true, -exception=false, -} -end -if grp.Tiresias and(not grp.Tiresias.exception==true)then -if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible==false then +grp.Tiresias=sam_data_template +elseif not tiresias_data.exception==true then +if not tiresias_data.invisible then grp:SetCommandInvisible(true) -grp.Tiresias.invisible=true +tiresias_data.invisible=true end end end @@ -56234,51 +61175,59 @@ end return self end function TIRESIAS:_EventHandler(EventData) -self:T(string.format("%s Event = %d",self.lid,EventData.id)) +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 unitname=event.IniUnitName or"none" -local _unit=event.IniUnit local _group=event.IniGroup if _group and _group:IsAlive()then -local radius=self.PlaneSwitchRange -if _group:IsHelicopter()then -radius=self.HeloSwitchRange -end +local radius=_group:IsHelicopter()and self.HeloSwitchRange or self.PlaneSwitchRange self:_SwitchOnGroups(_group,radius) end end return self end function TIRESIAS:_SwitchOnGroups(group,radius) -self:T(self.lid.."_SwitchOnGroups "..group:GetName().." Radius "..radius.." NM") -local zone=ZONE_GROUP:New("Zone-"..group:GetName(),group,UTILS.NMToMeters(radius)) -local ground=SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() +self:T(self.lid.." _SwitchOnGroups "..group:GetName().." Radius "..radius.." NM") +local group_name=group:GetName() +local cache_key=group_name.." _"..radius +local zone=self._cached_zones[cache_key] +if not zone then +zone=ZONE_GROUP:New(" Zone-"..group_name,group,UTILS.NMToMeters(radius)) +self._cached_zones[cache_key]=zone +else +zone:UpdateFromGroup(group) +end +zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +local ground=zone:GetScannedSetGroup() local count=ground:CountAlive() if self.debug then -local text=string.format("There are %d groups around this plane or helo!",count) -self:I(text) +self:I(string.format(" There are %d groups around this plane or helo!",count)) end +if count>0 then local SwitchAAA=self.SwitchAAA -if ground:CountAlive()>0 then +local group_coalition=group:GetCoalition() ground:ForEachGroupAlive( function(grp) -if grp.Tiresias and grp.Tiresias.type and(not grp.Tiresias.exception==true)then -if grp.Tiresias.invisible==true then +local tiresias_data=grp.Tiresias +if grp:GetCoalition()~=group_coalition +and tiresias_data +and tiresias_data.type +and not tiresias_data.exception==true then +if tiresias_data.invisible==true then grp:SetCommandInvisible(false) -grp.Tiresias.invisible=false +tiresias_data.invisible=false end -if grp.Tiresias.type=="Vehicle"and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then +local grp_type=tiresias_data.type +if grp_type=="Vehicle"and tiresias_data.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 +tiresias_data.AIOff=false +elseif SwitchAAA==true and grp_type=="AAA"and tiresias_data.AIOff==true then grp:SetAIOn() grp:EnableEmission(true) -grp.Tiresias.AIOff=false +tiresias_data.AIOff=false end else -BASE:E("TIRESIAS - This group has not been initialized or is an exception!") +BASE:T("TIRESIAS - This group "..tostring(grp:GetName()).." has not been initialized or is an exception!") end end ) @@ -56291,17 +61240,37 @@ local VehicleSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS. 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() +self.FlightSet=SET_GROUP:New():FilterCategories({" plane"," helicopter"}):FilterStart() local EngageRange=self.AAARange +local SwitchAAA=self.SwitchAAA 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", +local exception_data={ +type=" Exception", exception=true, } +local vehicle_data={ +type=" Vehicle", +invisible=true, +AIOff=true, +exception=false, +} +local aaa_data={ +type=" AAA", +invisible=true, +range=EngageRange, +exception=false, +AIOff=SwitchAAA, +} +local sam_data={ +type=" SAM", +invisible=true, +exception=false, +} +if 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=exception_data Object:SetAIOn() Object:SetCommandInvisible(false) Object:EnableEmission(true) @@ -56319,46 +61288,30 @@ ExceptionSet:AddGroup(grp,true) end end function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object) -BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName()) +BASE:T(" TIRESIAS: VEHICLE 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, -} +Object.Tiresias=vehicle_data 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()) +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, -} +Object.Tiresias=aaa_data end end function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object) if Object and Object:IsAlive()then -BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName()) +BASE:T(" TIRESIAS: SAM Object Added: "..Object:GetName()) Object:SetCommandInvisible(true) -Object.Tiresias={ -type="SAM", -invisible=true, -exception=false, -} +Object.Tiresias=sam_data end end self.VehicleSet=VehicleSet @@ -56371,10 +61324,7 @@ return self end function TIRESIAS:onbeforeStatus(From,Event,To) self:T({From,Event,To}) -if self:GetState()=="Stopped"then -return false -end -return self +return self:GetState()~=" Stopped" end function TIRESIAS:onafterStatus(From,Event,To) self:T({From,Event,To}) @@ -56382,22 +61332,22 @@ 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) +self:I(string.format(" Overall: %d | Vehicles: %d | AAA: %d | SAM: %d", +count+AAAcount+SAMcount,count,AAAcount,SAMcount)) end self:_InitGroups() -if self.FlightSet:CountAlive()>0 then +local flight_count=self.FlightSet:CountAlive() +if flight_count>0 then local Set=self.FlightSet:GetAliveSet() -for _,_plane in pairs(Set)do +local helo_range=self.HeloSwitchRange +local plane_range=self.PlaneSwitchRange +for _,_plane in pairs(Set or{})do local plane=_plane -local radius=self.PlaneSwitchRange -if plane:IsHelicopter()then -radius=self.HeloSwitchRange -end -self:_SwitchOnGroups(_plane,radius) +local radius=plane:IsHelicopter()and helo_range or plane_range +self:_SwitchOnGroups(plane,radius) end end -if self:GetState()~="Stopped"then +if self:GetState()~=" Stopped"then self:__Status(self.Interval) end return self @@ -56405,6 +61355,7 @@ end function TIRESIAS:onafterStop(From,Event,To) self:T({From,Event,To}) self:UnHandleEvent(EVENTS.PlayerEnterAircraft) +self._cached_zones={} return self end STRATEGO={ @@ -56412,7 +61363,7 @@ ClassName="STRATEGO", debug=false, drawzone=false, markzone=false, -version="0.2.9", +version="0.3.1", portweight=3, POIweight=1, maxrunways=3, @@ -56660,7 +61611,7 @@ end return self end function STRATEGO:GetToFrom(StartPoint,EndPoint) -self:T(self.lid.."GetToFrom") +self:T(self.lid.."GetToFrom "..tostring(StartPoint).." "..tostring(EndPoint)) local pstart=string.gsub(StartPoint,"[%p%s]",".") local pend=string.gsub(EndPoint,"[%p%s]",".") local fromto=pstart..";"..pend @@ -56798,6 +61749,8 @@ end function STRATEGO:GetHighestWeightNodes(Coalition) self:T(self.lid.."GetHighestWeightNodes") local weight=0 +local highest=0 +local highname=nil local airbases={} for _name,_data in pairs(self.airbasetable)do local okay=true @@ -56811,8 +61764,12 @@ weight=_data.weight if not airbases[weight]then airbases[weight]={}end table.insert(airbases[weight],_name) end +if _data.weight>highest and okay then +highest=_data.weight +highname=_name end -return airbases[weight],weight +end +return airbases[weight],weight,highest,highname end function STRATEGO:GetNextHighestWeightNodes(Weight,Coalition) self:T(self.lid.."GetNextHighestWeightNodes") @@ -57110,7 +62067,26 @@ end end return neighbors,nearest,shortestdist end -function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType) +function STRATEGO:_GetNextClosest(Start,End,InRoute) +local ecoord=self.airbasetable[End].coord +local nodes,nearest=self:FindNeighborNodes(Start) +local closest=nil +local closedist=1000*1000 +for _name,_dist in pairs(nodes)do +local kcoord=self.airbasetable[_name].coord +local nnodes=self.airbasetable[_name].connections>2 and true or false +if _name==End then nnodes=true end +if kcoord~=nil and ecoord~=nil and nnodes==true and InRoute[_name]~=true then +local dist=math.floor((kcoord:Get2DDistance(ecoord)/1000)+0.5) +if(dist2 and true or false -if _name==End then nnodes=true end -local dist=math.floor((kcoord:Get2DDistance(ecoord)/1000)+0.5) -if(dist0 then +MESSAGE:New("There is at least one node island!",15,"STRATEGO"):ToAllIf(self.debug):ToLog() +allin=false +if self.debug==true then +local zone1=DrawElastic(filled,draw) +local zone2=DrawElastic(unfilled,draw) +local vertices1=zone1:GetVerticiesVec2() +local vertices2=zone2:GetVerticiesVec2() +local corner1=nil +local corner2=nil +local mindist=math.huge +local found=false +for _,_edge in pairs(vertices1)do +for _,_edge2 in pairs(vertices2)do +local dist=UTILS.VecDist2D(_edge,_edge2) +if dist-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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -57424,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -57435,6 +63203,8 @@ C2A="C2A_Greyhound", RHINOE="FA-18E", RHINOF="FA-18F", GROWLER="EA-18G", +CORSAIR="F4U-1D", +CORSAIR_CW="F4U-1D CW", } AIRBOSS.CarrierType={ ROOSEVELT="CVN_71", @@ -57443,7 +63213,12 @@ WASHINGTON="CVN_73", TRUMAN="CVN_75", STENNIS="Stennis", FORRESTAL="Forrestal", +ENTERPRISE66="USS Enterprise 1966", +ENTERPRISEMODERN="cvn-65", VINSON="VINSON", +ESSEX="Essex", +BONHOMMERICHARD="USS Bon Homme Richard", +ESSEXSCB125="essex_scb125", HERMES="HERMES81", INVINCIBLE="hms_invincible", TARAWA="LHA_Tarawa", @@ -57477,6 +63252,7 @@ GROOVE_IM="Groove In the Middle", GROOVE_IC="Groove In Close", GROOVE_AR="Groove At the Ramp", GROOVE_IW="Groove In the Wires", +GROOVE_IWs="Groove In the Wires stopped?", GROOVE_AL="Groove Abeam Landing Spot", GROOVE_LC="Groove Level Cross", BOLTER="Bolter Pattern", @@ -57500,7 +63276,7 @@ HARD="TOPGUN Graduate", } AIRBOSS.MenuF10={} AIRBOSS.MenuF10Root=nil -AIRBOSS.version="1.3.3" +AIRBOSS.version="1.4.2" function AIRBOSS:New(carriername,alias) local self=BASE:Inherit(self,FSM:New()) self:F2({carriername=carriername,alias=alias}) @@ -57535,6 +63311,7 @@ self:SetBeaconRefresh() self:SetMaxLandingPattern() self:SetMaxMarshalStacks() self:SetMaxSectionSize() +self:SetMaxSectionDistance() self:SetMaxFlightsPerStack() self:SetHandleAION() self:SetExtraVoiceOvers(false) @@ -57578,8 +63355,18 @@ elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() +elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISE66 then +self:_InitEnterprise() +elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISEMODERN then +self:_InitEnterprise() elseif self.carriertype==AIRBOSS.CarrierType.VINSON then self:_InitStennis() +elseif self.carriertype==AIRBOSS.CarrierType.ESSEX then +self:_InitEssex() +elseif self.carriertype==AIRBOSS.CarrierType.BONHOMMERICHARD then +self:_InitBonHommeRichard() +elseif self.carriertype==AIRBOSS.CarrierType.ESSEXSCB125 then +self:_InitEssexSCB125() elseif self.carriertype==AIRBOSS.CarrierType.HERMES then self:_InitHermes() elseif self.carriertype==AIRBOSS.CarrierType.INVINCIBLE then @@ -57680,6 +63467,10 @@ self:AddTransition("*","Save","*") self:AddTransition("*","Stop","Stopped") return self end +function AIRBOSS:SetCarrierIllumination(Mode) +self.carrier:SetCarrierIlluminationMode(Mode) +return self +end function AIRBOSS:SetWelcomePlayers(Switch) self.welcome=Switch return self @@ -57905,7 +63696,12 @@ 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 +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 @@ -58023,19 +63819,19 @@ 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:SetPort(Port or MSRS.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) +self.SRS:SetGoogle(GoogleCreds) end if Voice then self.SRS:SetVoice(Voice) end -self.SRS:SetVolume(Volume or 1.0) +if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then +self.SRS.voice=MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B +end self.SRSQ=MSRSQUEUE:New("AIRBOSS") self.SRSQ:SetTransmitOnlyWithPlayers(true) if not self.PilotRadio then @@ -58165,6 +63961,17 @@ nmax=math.min(nmax,4) self.NmaxSection=nmax-1 return self end +function AIRBOSS:SetMaxSectionDistance(dmax) +if dmax then +if dmax<10 then +dmax=10 +elseif dmax>5000 then +dmax=5000 +end +end +self.maxsectiondistance=dmax or 100 +return self +end function AIRBOSS:SetMaxFlightsPerStack(nmax) nmax=nmax or 2 nmax=math.max(nmax,1) @@ -58291,7 +64098,7 @@ end self:_CheckRecoveryTimes() self.Tqueue=timer.getTime()-60 self:HandleEvent(EVENTS.Birth) -self:HandleEvent(EVENTS.Land) +self:HandleEvent(EVENTS.RunwayTouch) self:HandleEvent(EVENTS.EngineShutdown) self:HandleEvent(EVENTS.Takeoff) self:HandleEvent(EVENTS.Crash) @@ -58638,7 +64445,7 @@ 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.RunwayTouch) self:UnHandleEvent(EVENTS.EngineShutdown) self:UnHandleEvent(EVENTS.Takeoff) self:UnHandleEvent(EVENTS.Crash) @@ -58699,7 +64506,7 @@ 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.Xmax=UTILS.NMToMeters(7) self.BreakEarly.Zmin=-UTILS.NMToMeters(2) self.BreakEarly.Zmax=UTILS.NMToMeters(1) self.BreakEarly.LimitXmin=0 @@ -58708,7 +64515,7 @@ 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.Xmax=UTILS.NMToMeters(7) self.BreakLate.Zmin=-UTILS.NMToMeters(2) self.BreakLate.Zmax=UTILS.NMToMeters(1) self.BreakLate.LimitXmin=0 @@ -58717,7 +64524,7 @@ 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.Xmax=UTILS.NMToMeters(7) self.Abeam.Zmin=-UTILS.NMToMeters(2) self.Abeam.Zmax=500 self.Abeam.LimitXmin=-200 @@ -58774,7 +64581,7 @@ self.carrierparam.rwywidth=25 self.carrierparam.wire1=55 self.carrierparam.wire2=67 self.carrierparam.wire3=79 -self.carrierparam.wire4=92 +self.carrierparam.wire4=96 self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 end function AIRBOSS:_InitForrestal() @@ -58793,6 +64600,58 @@ self.carrierparam.wire3=64 self.carrierparam.wire4=74 self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 end +function AIRBOSS:_InitEnterprise() +self:_InitForrestal() +self.carrierparam.sterndist=-164.30 +self.carrierparam.deckheight=19.52 +self.carrierparam.totlength=335 +self.carrierparam.rwylength=223 +self.carrierparam.wire1=57.7 +self.carrierparam.wire2=69.6 +self.carrierparam.wire3=79.5 +self.carrierparam.wire4=90.0 +end +function AIRBOSS:_InitEssex() +self:_InitNimitz() +self.carrierparam.sterndist=-126 +self.carrierparam.deckheight=19.27 +self.carrierparam.totlength=268 +self.carrierparam.totwidthport=23 +self.carrierparam.totwidthstarboard=23 +self.carrierparam.rwyangle=0.0 +self.carrierparam.rwylength=265 +self.carrierparam.rwywidth=20 +self.carrierparam.wire1=21.9 +self.carrierparam.wire2=28.3 +self.carrierparam.wire3=34.7 +self.carrierparam.wire4=41.1 +self.carrierparam.wire5=47.4 +self.carrierparam.wire6=53.7 +self.carrierparam.wire7=59.0 +self.carrierparam.wire8=64.1 +self.carrierparam.wire9=72.7 +self.carrierparam.wire10=78.0 +self.carrierparam.wire11=85.5 +self.carrierparam.wire12=105.9 +self.carrierparam.wire13=113.3 +self.carrierparam.wire14=121.0 +self.carrierparam.wire15=128.5 +self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 +end +function AIRBOSS:_InitBonHommeRichard() +self:_InitEssex() +self.carrierparam.deckheight=16.95 +self.carrierparam.rwyangle=-11.4 +self.carrierparam.rwylength=97 +self.carrierparam.rwywidth=20 +self.carrierparam.wire1=40.4 +self.carrierparam.wire2=45 +self.carrierparam.wire3=51 +self.carrierparam.wire4=58.1 +end +function AIRBOSS:_InitEssexSCB125() +self:_InitBonHommeRichard() +end function AIRBOSS:_InitHermes() self:_InitStennis() self.carrierparam.sterndist=-105 @@ -59065,6 +64924,7 @@ 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.POWERsoft.duration=0.9 self.LSOCall.POWER.duration=0.45 self.LSOCall.RADIOCHECK.duration=0.90 self.LSOCall.RIGHTFORLINEUP.duration=0.70 @@ -59112,6 +64972,7 @@ 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.POWERsoft.duration=0.9 self.LSOCall.RADIOCHECK.duration=1.10 self.LSOCall.RIGHTFORLINEUP.duration=0.80 self.LSOCall.ROGERBALL.duration=1.00 @@ -59184,6 +65045,7 @@ RIGHTFORLINEUP={file="LSO-RightForLineup",suffix="ogg",loud=true,subtitle="Right 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}, +POWERsoft={file="LSO-Power-soft",suffix="ogg",loud=false,subtitle="Power-soft",duration=0.90,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}, @@ -59296,7 +65158,8 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early +local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then aoa.SLOW=9.8 @@ -59307,29 +65170,29 @@ 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.SLOW=self:_AoAUnit2Deg(playerData,17.5) +aoa.Slow=self:_AoAUnit2Deg(playerData,16.5) +aoa.OnSpeedMax=self:_AoAUnit2Deg(playerData,16.0) 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) +aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) +aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) +aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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.5 aoa.Slow=9.25 -aoa.OnSpeedMax=9.00 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 +elseif skyhawk then +aoa.SLOW=10.50 +aoa.Slow=9.50 +aoa.OnSpeedMax=9.25 aoa.OnSpeed=8.75 -aoa.OnSpeedMin=8.50 -aoa.Fast=8.25 -aoa.FAST=8.00 +aoa.OnSpeedMin=8.25 +aoa.Fast=8.00 +aoa.FAST=7.00 elseif harrier then aoa.SLOW=16.0 aoa.Slow=13.5 @@ -59338,12 +65201,20 @@ aoa.OnSpeed=10.0 aoa.OnSpeedMin=9.5 aoa.Fast=8.0 aoa.FAST=7.5 +elseif corsair 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 +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -59353,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -59371,6 +65242,7 @@ 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 corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local alt local aoa local dist @@ -59407,6 +65279,9 @@ speed=UTILS.KnotsToMps(250) elseif goshawk then alt=UTILS.FeetToMeters(800) speed=UTILS.KnotsToMps(300) +elseif corsair then +alt=UTILS.FeetToMeters(300) +speed=UTILS.KnotsToMps(120) end elseif step==AIRBOSS.PatternStep.BREAKENTRY then if hornet or tomcat or harrier then @@ -59418,24 +65293,36 @@ speed=UTILS.KnotsToMps(250) elseif goshawk then alt=UTILS.FeetToMeters(800) speed=UTILS.KnotsToMps(300) +elseif corsair then +alt=UTILS.FeetToMeters(200) +speed=UTILS.KnotsToMps(110) 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) +elseif corsair then +alt=UTILS.FeetToMeters(200) +speed=UTILS.KnotsToMps(100) 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) +elseif corsair then +alt=UTILS.FeetToMeters(150) +speed=UTILS.KnotsToMps(100) 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) +elseif corsair then +alt=UTILS.FeetToMeters(150) +speed=UTILS.KnotsToMps(90) end aoa=aoaac.OnSpeed if goshawk then @@ -59454,6 +65341,9 @@ elseif skyhawk then alt=UTILS.FeetToMeters(500) elseif harrier then alt=UTILS.FeetToMeters(425) +elseif corsair then +alt=UTILS.FeetToMeters(90) +speed=UTILS.KnotsToMps(90) end aoa=aoaac.OnSpeed elseif step==AIRBOSS.PatternStep.WAKE then @@ -59463,6 +65353,8 @@ elseif tomcat then alt=UTILS.FeetToMeters(430) elseif skyhawk then alt=UTILS.FeetToMeters(370) +elseif corsair then +alt=UTILS.FeetToMeters(80) end aoa=aoaac.OnSpeed elseif step==AIRBOSS.PatternStep.FINAL then @@ -59474,6 +65366,8 @@ elseif skyhawk then alt=UTILS.FeetToMeters(300) elseif harrier then alt=UTILS.FeetToMeters(312) +elseif corsair then +alt=UTILS.FeetToMeters(80) end aoa=aoaac.OnSpeed end @@ -59864,6 +65758,8 @@ elseif flight.actype==AIRBOSS.AircraftCarrier.F14A_AI or flight.actype==AIRBOSS. Speed=UTILS.KnotsToKmph(175) elseif flight.actype==AIRBOSS.AircraftCarrier.S3B or flight.actype==AIRBOSS.AircraftCarrier.S3BTANKER then Speed=UTILS.KnotsToKmph(140) +elseif flight.actype==AIRBOSS.AircraftCarrier.CORSAIR or flight.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW then +Speed=UTILS.KnotsToKmph(100) end local Carrier=self:GetCoordinate() local hdg=self:GetHeading() @@ -59961,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -60324,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -60590,6 +66487,55 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<20 and math.abs(playerData.unit:GetRoll())>=10 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<10 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -60691,7 +66637,7 @@ end end function AIRBOSS:_SetTimeInGroove(playerData) if playerData.TIG0 then -playerData.Tgroove=timer.getTime()-playerData.TIG0 +playerData.Tgroove=timer.getTime()-playerData.TIG0-1.5 else playerData.Tgroove=999 end @@ -60745,7 +66691,7 @@ self:_AddF10Commands(_unitName) self:ScheduleOnce(1,self._NewPlayer,self,_unitName) end end -function AIRBOSS:OnEventLand(EventData) +function AIRBOSS:OnEventRunwayTouch(EventData) self:F3({eventland=EventData}) if EventData==nil then self:E(self.lid.."ERROR: EventData=nil in event LAND!") @@ -60971,13 +66917,13 @@ 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) +self:T(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!") +self:T(EventData) return end if EventData.IniUnit==nil then -self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") -self:E(EventData) +self:T(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") +self:T(EventData) return end local _unitName=EventData.IniUnitName @@ -61262,7 +67208,17 @@ function AIRBOSS:_DirtyUp(playerData) self:_CheckCorridor(playerData) local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case)) if inzone then -self:_PlayerHint(playerData) +playerData.Tgroove=timer.getTime()-playerData.TIG0-1.5 +playerData.wrappedUpAtWakeLittle=false +playerData.wrappedUpAtWakeFull=false +playerData.wrappedUpAtWakeUnderline=false +playerData.wrappedUpAtStartLittle=false +playerData.wrappedUpAtStartFull=false +playerData.wrappedUpAtStartUnderline=false +playerData.AAatWakeLittle=false +playerData.AAatWakeFull=false +playerData.AAatWakeUnderline=false +playerData.AFU=false if playerData.actype==AIRBOSS.AircraftCarrier.HORNET or playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B @@ -61396,6 +67352,16 @@ self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.NINETY) end end function AIRBOSS:_Ninety(playerData) +playerData.wrappedUpAtWakeLittle=false +playerData.wrappedUpAtWakeFull=false +playerData.wrappedUpAtWakeUnderline=false +playerData.wrappedUpAtStartLittle=false +playerData.wrappedUpAtStartFull=false +playerData.wrappedUpAtStartUnderline=false +playerData.AAatWakeLittle=false +playerData.AAatWakeFull=false +playerData.AAatWakeUnderline=false +playerData.AFU=false local X,Z=self:_GetDistances(playerData.unit) if self:_CheckAbort(X,Z,self.Ninety)then self:_AbortPattern(playerData,X,Z,self.Ninety,true) @@ -61448,6 +67414,9 @@ groovedata.Alt=self:_GetAltCarrier(playerData.unit) groovedata.AoA=playerData.unit:GetAoA() groovedata.GSE=self:_Glideslope(playerData.unit) groovedata.LUE=self:_Lineup(playerData.unit,true) +groovedata.LUEwire=self:_LineupWIRE(playerData.unit,true) +groovedata.LeftNozzle=self:_NozzleArgumentLeft(playerData.unit) +groovedata.RightNozzle=self:_NozzleArgumentRight(playerData.unit) groovedata.Roll=playerData.unit:GetRoll() groovedata.Pitch=playerData.unit:GetPitch() groovedata.Yaw=playerData.unit:GetYaw() @@ -61486,6 +67455,7 @@ local RXX=UTILS.NMToMeters(0.750) local RIM=UTILS.NMToMeters(0.500) local RIC=UTILS.NMToMeters(0.250) local RAR=UTILS.NMToMeters(0.040) +local RIW=UTILS.NMToMeters(-0.020) local groovedata=self:_GetGrooveData(playerData) table.insert(playerData.trapsheet,groovedata) local X=groovedata.X @@ -61498,7 +67468,7 @@ local rho=groovedata.Rho local lineupError=groovedata.LUE local glideslopeError=groovedata.GSE local AoA=groovedata.AoA -if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and(math.abs(groovedata.Roll)<=4.0 and playerData.unit:IsInZone(self:_GetZoneLineup()))then +if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and(math.abs(groovedata.Roll)<=3.5 or playerData.unit:IsInZone(self:_GetZoneLineup()))then playerData.TIG0=timer.getTime() self:RadioTransmission(self.LSORadio,self.LSOCall.CALLTHEBALL,nil,nil,nil,true) playerData.Tlso=timer.getTime() @@ -61519,6 +67489,8 @@ self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_AL) else self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IW) end +elseif rho<=RIW and playerData.step==AIRBOSS.PatternStep.GROOVE_IW then +playerData.groove.IW=UTILS.DeepCopy(groovedata) elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AL then playerData.groove.AL=UTILS.DeepCopy(groovedata) local ZoneALS=self:_GetZoneAbeamLandingSpot() @@ -61562,22 +67534,53 @@ local gd=playerData.groove[gs] if gd then self:T3(gd) local d=UTILS.MetersToNM(rho) +if playerData.case~=3 then +if playerData.wrappedUpAtWakeUnderline==true or playerData.wrappedUpAtStartUnderline==true then +gd.WrappedUp="_WU_" +elseif playerData.wrappedUpAtWakeUnderline==false and playerData.wrappedUpAtStartUnderline==false then +if playerData.wrappedUpAtWakeFull==true or playerData.wrappedUpAtStartFull==true then +gd.WrappedUp="WU" +elseif playerData.wrappedUpAtStartFull==false then +if playerData.wrappedUpAtWakeLittle==true or playerData.wrappedUpAtStartLittle==true then +gd.WrappedUp="(WU)" +end +end +else +end +if playerData.AAatWakeUnderline==true then +gd.AngledApch="_AA_" +elseif playerData.AAatWakeUnderline==false then +if playerData.AAatWakeFull==true then +gd.AngledApch="AA" +elseif playerData.AAatWakeFull==false then +if playerData.AAatWakeLittle==true then +gd.AngledApch="(AA)" +end +end +else +end +if playerData.AFU==true then +gd.AFU="AFU" +else +end +else +end if 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" +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" +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)" +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)" +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 @@ -61748,6 +67751,8 @@ 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) +elseif self.carriertype==AIRBOSS.CarrierType.ESSEX then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(-1,FB+90,true,true) else self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(9.5,FB+90,true,true) end @@ -61850,13 +67855,13 @@ end playerData.wire=wire local text=string.format("Trapped %d-wire.",wire) if wire==3 then -text=text.." Well done!" +text=text.." " elseif wire==2 then -text=text.." Not bad, maybe you even get the 3rd next time." +text=text.." " elseif wire==4 then -text=text.." That was scary. You can do better than this!" +text=text.." " elseif wire==1 then -text=text.." Try harder next time!" +text=text.." " end self:MessageToPlayer(playerData,text,"LSO","") local hint=string.format("Trapped %d-wire.",wire) @@ -62186,6 +68191,33 @@ 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 +local lueWire=self:_LineupWIRE(playerData.unit,true) +text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) +local unitClient=Unit.getByName(unit:GetName()) +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early +if hornet then +local nozzlePosL=0 +local burnerPosL=unitClient:getDrawArgumentValue(28) +if burnerPosL<0.2 then +nozzlePosL=unitClient:getDrawArgumentValue(89) +else +nozzlePosL=0 +end +local nozzlePosR=0 +local burnerPosR=unitClient:getDrawArgumentValue(29) +if burnerPosR<0.2 then +nozzlePosR=unitClient:getDrawArgumentValue(90) +else +nozzlePosR=0 +end +text=text..string.format("\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ",nozzlePosL,nozzlePosR) +end +if tomcat then +local nozzlePosL=unitClient:getDrawArgumentValue(434) +local nozzlePosR=unitClient:getDrawArgumentValue(433) +text=text..string.format("\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ",nozzlePosL,nozzlePosR) +end MESSAGE:New(text,1,nil,true):ToClient(playerData.client) end function AIRBOSS:_Glideslope(unit,optangle) @@ -62246,6 +68278,61 @@ local z=UTILS.VecDot(Z,C) local lineup=math.deg(math.atan2(z,x)) return lineup end +function AIRBOSS:_LineupWIRE(unit,runway) +local landingcoord=self:_GetOptLandingCoordinateWIRE() +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:_NozzleArgumentLeft(unit) +local unitClient=Unit.getByName(unit:GetName()) +local typeName=unit:GetTypeName() +local nozzlePosL=0 +local burnerPosL=0 +if typeName=="FA-18C_hornet"then +burnerPosL=unitClient:getDrawArgumentValue(28) +if burnerPosL<0.2 then +nozzlePosL=unitClient:getDrawArgumentValue(89) +else +nozzlePosL=0 +end +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then +nozzlePosL=unitClient:getDrawArgumentValue(434) +end +return nozzlePosL +end +function AIRBOSS:_NozzleArgumentRight(unit) +local unitClient=Unit.getByName(unit:GetName()) +local typeName=unit:GetTypeName() +local nozzlePosR=0 +local burnerPosR=0 +if typeName=="FA-18C_hornet"then +burnerPosR=unitClient:getDrawArgumentValue(29) +if burnerPosR<0.2 then +nozzlePosR=unitClient:getDrawArgumentValue(90) +else +nozzlePosR=0 +end +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then +nozzlePosR=unitClient:getDrawArgumentValue(433) +end +return nozzlePosR +end function AIRBOSS:_GetAltCarrier(unit) local h=unit:GetAltitude()-self.carrierparam.deckheight-2 return h @@ -62270,6 +68357,16 @@ self.landingcoord.y=self.landingcoord.y+2 end return self.landingcoord end +function AIRBOSS:_GetOptLandingCoordinateWIRE() +self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) +local FB=self:GetFinalBearing(false) +local case=self.case +if self.carrierparam.wire3 then +self.landingcoord:Translate(self.carrierparam.wire3+500,FB,true,true) +end +self.landingcoord.y=self.landingcoord.y+2 +return self.landingcoord +end function AIRBOSS:_GetLandingSpotCoordinate() self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) local hdg=self:GetHeading() @@ -62282,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -62494,6 +68589,9 @@ advice=advice+self.LSOCall.POWER.duration elseif glideslopeError=25 then +grade="_LIG_" elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<55 then grade="FAST V/STOL Groove" elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t<75 then @@ -62551,7 +68655,7 @@ grade="SLOW V/STOL Groove" else grade="LIG" end -if t>=16.4 and t<=16.6 then +if t>=16.49 and t<=16.6 then grade="_OK_" end if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and(t>=60.0 and t<=65.0)then @@ -62563,25 +68667,43 @@ function AIRBOSS:_LSOgrade(playerData) local function count(base,pattern) return select(2,string.gsub(base,pattern,"")) end +local TIG="" +if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then +TIG=self:_EvalGrooveTime(playerData)or"N/A" +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 GIW,nIW=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IW) local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B -local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR -local N=nXX+nIM+nIC+nAR +local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR.." "..GIW.." "..TIG +local gradeWithDeviations=GXX.."["..nXX.."] "..GIM.."["..nIM.."] "..GIC.."["..nIC.."] "..GAR.."["..nAR.."] "..GIW.."["..nIW.."]" +env.info("LSO Grade [with deviation count]: "..gradeWithDeviations) +local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove -local TgrooveUnicorn=Tgroove and(Tgroove>=15.0 and Tgroove<=18.99)or false +local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)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 +if N==0 and TgrooveVstolUnicorn then grade="_OK_" points=5.0 G="Unicorn" +end +if N==0 and TgrooveUnicorn then +if playerData.wire==3 then +grade="_OK_" +points=5.0 +G="Unicorn" +else +grade="OK" +points=4.0 +end else if vtol then local Gb=GXX.." "..GIM @@ -62627,6 +68749,7 @@ 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) +env.info(text) if playerData.wop then if playerData.lig then grade="WO" @@ -62669,6 +68792,10 @@ grade="CUT" points=0.0 end end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 +end +env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G end function AIRBOSS:_Flightdata2Text(playerData,groovestep) @@ -62687,20 +68814,24 @@ local step=fdata.Step local AOA=fdata.AoA local GSE=fdata.GSE local LUE=fdata.LUE +local LUEwire=fdata.LUEwire +local Lnoz=fdata.LeftNozzle +local Rnoz=fdata.RightNozzle local ROL=fdata.Roll +local GT=fdata.GT 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") +if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=3.5 and playerData.case<3 then +if LUE>3.2 then +P=underline("LUL") +elseif LUE>2.2 then +P="LUL" +elseif LUE>1.2 then +P=little("LUL") end end local O=nil -if step==AIRBOSS.PatternStep.GROOVE_XX then +if step==AIRBOSS.PatternStep.GROOVE_XX and playerData.case<3 then if LUEacaoa.SLOW then S=underline("SLO") elseif AOA>acaoa.Slow then @@ -62723,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -62737,59 +68871,141 @@ A="LO" elseif GSEself.lue.RIGHT then +local DW=nil +local Rol=nil +local Noz=nil +if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then +else +if LUE>self.lue.RIGHT and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then D=underline("LUL") -elseif LUE>self.lue.Right then +elseif LUE>self.lue.Right and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then D="LUL" -elseif LUE>self.lue._max then +elseif LUE>self.lue._max and step~=AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then D=little("LUL") -elseif playerData.case<3 then -if LUE1.2 then +DW=underline("LL") +elseif LUEwire>0.4 then +DW="LL" +elseif LUEwire>0.25 then +DW=little("LL") +elseif LUEwire<-1.17 then +DW=underline("LR") +elseif LUEwire<-0.46 then +DW="LR" +elseif LUEwire<-0.25 then +DW=little("LR") +else +end +if ROL>5 and ROL<=10 then +Rol=little("LRWD") +elseif ROL>10 and ROL<=15 then +Rol=("LRWD") +elseif ROL>15 then +Rol=underline("LRWD") +elseif ROL<-5 and ROL>=-10 then +Rol=little("LLWD") +elseif ROL<-10 and ROL>=-15 then +Rol=("LLWD") +elseif ROL<-15 then +Rol=underline("LLWD") +else +end +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if hornet then +if Lnoz>0.6 and Rnoz>0.6 then +Noz=underline("EG") +else +end +end +if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then +local grooveTime=playerData.Tgroove +if grooveTime>19 or grooveTime<15 then +GT="" +end end end local G="" local n=0 +if stepMod=="XX"then +if playerData.case<3 then +if fdata.WrappedUp then +env.info("Adding WrappedUp deviation.") +G=G..fdata.WrappedUp +n=n+1 +end +if fdata.AngledApch then +env.info("Adding AngledApch deviation.") +G=G..fdata.AngledApch +n=n+1 +end +if fdata.AFU then +env.info("Adding AFU deviation.") +G=G..fdata.AFU +n=n+1 +end +end +end if fdata.FlyThrough then G=G..fdata.FlyThrough end -if P then -G=G..P -n=n -end if S then +env.info("Adding speed deviation.") G=G..S n=n+1 end if A then +env.info("Adding altitude deviation.") G=G..A n=n+1 end if D then +env.info("Adding line up deviation.") G=G..D n=n+1 end if fdata.Drift then +env.info("Adding drift deviation.") G=G..fdata.Drift n=n end if O then +env.info("Adding overshoot deviation.") G=G..O n=n+1 end +if DW then +env.info("Adding landed L/R deviation.") +G=G..DW +n=n+1 +end +if Rol then +env.info("Adding landed rol deviation.") +G=G..Rol +n=n+1 +end +if Noz then +env.info("Adding eased guns deviation.") +G=G..Noz +n=n+1 +end +if GT then +G=G..GT +n=n+1 +end local step=self:_GS(step) step=step:gsub("XX","X") if G~=""then @@ -63258,7 +69474,7 @@ 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)) +text=text..string.format("\nTime in the groove %.1f seconds.",playerData.Tgroove) end end playerData.lastdebrief=UTILS.DeepCopy(playerData.debrief) @@ -63688,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -63707,8 +69923,10 @@ 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 template=group:GetTemplate() local numbers={} +if template then +local units=template.units for _,unit in pairs(units)do local n=tostring(unit.onboard_num) local name=unit.name @@ -63720,6 +69938,17 @@ end numbers[name]=n end self:T2(self.lid..text) +else +if playeronly then +return 101 +else +local units=group:GetUnits() +for i,_unit in pairs(units)do +local name=_unit:GetName() +numbers[name]=100+i +end +end +end return numbers end function AIRBOSS:_GetTowerFrequency() @@ -63848,7 +70077,7 @@ if u and pn then return u,pn end local DCSunit=Unit.getByName(_unitName) -if DCSunit then +if DCSunit and DCSunit.getPlayerName then local playername=DCSunit:getPlayerName() local unit=UNIT:Find(DCSunit) self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) @@ -64343,7 +70572,7 @@ else self:E(self.lid..string.format("ERROR: Unknown radio sender %s!",tostring(sender))) return end -local numbers=_split(number) +local numbers=_split(tostring(number)) local wait=0 for i=1,#numbers do local n=numbers[i] @@ -64379,7 +70608,7 @@ if Sender==""then self:E(self.lid..string.format("ERROR: Sender unknown!")) return end -local numbers=_split(number) +local numbers=_split(tostring(number)) local wait=0 for i=1,#numbers do local n=numbers[i] @@ -64842,6 +71071,15 @@ 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!" +playerData.wrappedUpAtWakeLittle=false +playerData.wrappedUpAtWakeFull=false +playerData.wrappedUpAtWakeUnderline=false +playerData.wrappedUpAtStartLittle=false +playerData.wrappedUpAtStartFull=false +playerData.wrappedUpAtStartUnderline=false +playerData.AAatWakeLittle=false +playerData.AAatWakeFull=false +playerData.AAatWakeUnderline=false local lead=self:_GetFlightLead(playerData) self:_SetPlayerStep(lead,AIRBOSS.PatternStep.EMERGENCY) for _,sec in pairs(lead.section)do @@ -65011,7 +71249,7 @@ if _unit and _playername then local playerData=self.players[_playername] if playerData then local mycoord=_unit:GetCoordinate() -local dmax=100 +local dmax=self.maxsectiondistance local text if self.NmaxSection==0 then text=string.format("negative, setting sections is disabled in this mission. You stay alone.") @@ -65604,7 +71842,7 @@ 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" +text=text.."\n* arc turn out with WHITE flares" end end if case==3 then @@ -65637,7 +71875,7 @@ 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" +text=text.."\n* arc turn out with BLUE smoke" end end if case==3 then @@ -66216,6 +72454,7 @@ self.orientation=self.carrier:GetOrientationX() self.orientlast=self.carrier:GetOrientationX() self.position=self.carrier:GetCoordinate() self:__Status(10) +return self end function RECOVERYTANKER:onafterStatus(From,Event,To) local time=timer.getTime() @@ -66571,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -66617,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -66815,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -66834,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -66844,16 +73093,15 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) +return self end function RESCUEHELO:onafterStatus(From,Event,To) local time=timer.getTime() @@ -67062,6 +73310,10 @@ Syria=5, MarianaIslands=2, Falklands=12, SinaiMap=5, +Kola=15, +Afghanistan=3, +Iraq=4.4, +GermanyCW=0.1, } ATIS.ICAOPhraseology={ Caucasus=true, @@ -67073,83 +73325,89 @@ Syria=true, MarianaIslands=true, Falklands=true, SinaiMap=true, +Kola=true, +Afghanistan=true, +Iraq=true, +GermanyCW=true, } ATIS.Sound={ -ActiveRunway={filename="ActiveRunway.ogg",duration=0.99}, -AdviceOnInitial={filename="AdviceOnInitial.ogg",duration=3.00}, -Airport={filename="Airport.ogg",duration=0.66}, -Altimeter={filename="Altimeter.ogg",duration=0.68}, -At={filename="At.ogg",duration=0.41}, -CloudBase={filename="CloudBase.ogg",duration=0.82}, -CloudCeiling={filename="CloudCeiling.ogg",duration=0.61}, -CloudsBroken={filename="CloudsBroken.ogg",duration=1.07}, -CloudsFew={filename="CloudsFew.ogg",duration=0.99}, -CloudsNo={filename="CloudsNo.ogg",duration=1.01}, -CloudsNotAvailable={filename="CloudsNotAvailable.ogg",duration=2.35}, -CloudsOvercast={filename="CloudsOvercast.ogg",duration=0.83}, -CloudsScattered={filename="CloudsScattered.ogg",duration=1.18}, -Decimal={filename="Decimal.ogg",duration=0.54}, -DegreesCelsius={filename="DegreesCelsius.ogg",duration=1.27}, -DegreesFahrenheit={filename="DegreesFahrenheit.ogg",duration=1.23}, -DewPoint={filename="DewPoint.ogg",duration=0.65}, -Dust={filename="Dust.ogg",duration=0.54}, -Elevation={filename="Elevation.ogg",duration=0.78}, -EndOfInformation={filename="EndOfInformation.ogg",duration=1.15}, -Feet={filename="Feet.ogg",duration=0.45}, -Fog={filename="Fog.ogg",duration=0.47}, -Gusting={filename="Gusting.ogg",duration=0.55}, -HectoPascal={filename="HectoPascal.ogg",duration=1.15}, -Hundred={filename="Hundred.ogg",duration=0.47}, -InchesOfMercury={filename="InchesOfMercury.ogg",duration=1.16}, -Information={filename="Information.ogg",duration=0.85}, -Kilometers={filename="Kilometers.ogg",duration=0.78}, -Knots={filename="Knots.ogg",duration=0.59}, -Left={filename="Left.ogg",duration=0.54}, -MegaHertz={filename="MegaHertz.ogg",duration=0.87}, -Meters={filename="Meters.ogg",duration=0.59}, -MetersPerSecond={filename="MetersPerSecond.ogg",duration=1.14}, -Miles={filename="Miles.ogg",duration=0.60}, -MillimetersOfMercury={filename="MillimetersOfMercury.ogg",duration=1.53}, -Minus={filename="Minus.ogg",duration=0.64}, -N0={filename="N-0.ogg",duration=0.55}, -N1={filename="N-1.ogg",duration=0.41}, -N2={filename="N-2.ogg",duration=0.37}, -N3={filename="N-3.ogg",duration=0.41}, -N4={filename="N-4.ogg",duration=0.37}, -N5={filename="N-5.ogg",duration=0.43}, -N6={filename="N-6.ogg",duration=0.55}, -N7={filename="N-7.ogg",duration=0.43}, -N8={filename="N-8.ogg",duration=0.38}, -N9={filename="N-9.ogg",duration=0.55}, -NauticalMiles={filename="NauticalMiles.ogg",duration=1.04}, -None={filename="None.ogg",duration=0.43}, -QFE={filename="QFE.ogg",duration=0.63}, -QNH={filename="QNH.ogg",duration=0.71}, -Rain={filename="Rain.ogg",duration=0.41}, -Right={filename="Right.ogg",duration=0.44}, -Snow={filename="Snow.ogg",duration=0.48}, -SnowStorm={filename="SnowStorm.ogg",duration=0.82}, -StatuteMiles={filename="StatuteMiles.ogg",duration=1.15}, -SunriseAt={filename="SunriseAt.ogg",duration=0.92}, -SunsetAt={filename="SunsetAt.ogg",duration=0.95}, -Temperature={filename="Temperature.ogg",duration=0.64}, -Thousand={filename="Thousand.ogg",duration=0.55}, -ThunderStorm={filename="ThunderStorm.ogg",duration=0.81}, -TimeLocal={filename="TimeLocal.ogg",duration=0.90}, -TimeZulu={filename="TimeZulu.ogg",duration=0.86}, -TowerFrequency={filename="TowerFrequency.ogg",duration=1.19}, -Visibilty={filename="Visibility.ogg",duration=0.79}, -WeatherPhenomena={filename="WeatherPhenomena.ogg",duration=1.07}, -WindFrom={filename="WindFrom.ogg",duration=0.60}, +ActiveRunway={filename="ActiveRunway.ogg",duration=0.85}, +ActiveRunwayDeparture={filename="ActiveRunwayDeparture.ogg",duration=1.50}, +ActiveRunwayArrival={filename="ActiveRunwayArrival.ogg",duration=1.38}, +AdviceOnInitial={filename="AdviceOnInitial.ogg",duration=2.98}, +Airport={filename="Airport.ogg",duration=0.55}, +Altimeter={filename="Altimeter.ogg",duration=0.91}, +At={filename="At.ogg",duration=0.32}, +CloudBase={filename="CloudBase.ogg",duration=0.69}, +CloudCeiling={filename="CloudCeiling.ogg",duration=0.53}, +CloudsBroken={filename="CloudsBroken.ogg",duration=0.81}, +CloudsFew={filename="CloudsFew.ogg",duration=0.74}, +CloudsNo={filename="CloudsNo.ogg",duration=0.69}, +CloudsNotAvailable={filename="CloudsNotAvailable.ogg",duration=2.64}, +CloudsOvercast={filename="CloudsOvercast.ogg",duration=0.82}, +CloudsScattered={filename="CloudsScattered.ogg",duration=0.89}, +Decimal={filename="Decimal.ogg",duration=0.71}, +DegreesCelsius={filename="DegreesCelsius.ogg",duration=1.08}, +DegreesFahrenheit={filename="DegreesFahrenheit.ogg",duration=1.07}, +DewPoint={filename="DewPoint.ogg",duration=0.59}, +Dust={filename="Dust.ogg",duration=0.37}, +Elevation={filename="Elevation.ogg",duration=0.92}, +EndOfInformation={filename="EndOfInformation.ogg",duration=1.24}, +Feet={filename="Feet.ogg",duration=0.34}, +Fog={filename="Fog.ogg",duration=0.41}, +Gusting={filename="Gusting.ogg",duration=0.58}, +HectoPascal={filename="HectoPascal.ogg",duration=0.92}, +Hundred={filename="Hundred.ogg",duration=0.53}, ILSFrequency={filename="ILSFrequency.ogg",duration=1.30}, -InnerNDBFrequency={filename="InnerNDBFrequency.ogg",duration=1.56}, -OuterNDBFrequency={filename="OuterNDBFrequency.ogg",duration=1.59}, -RunwayLength={filename="RunwayLength.ogg",duration=0.91}, -VORFrequency={filename="VORFrequency.ogg",duration=1.38}, -TACANChannel={filename="TACANChannel.ogg",duration=0.88}, -PRMGChannel={filename="PRMGChannel.ogg",duration=1.18}, -RSBNChannel={filename="RSBNChannel.ogg",duration=1.14}, -Zulu={filename="Zulu.ogg",duration=0.62}, +InchesOfMercury={filename="InchesOfMercury.ogg",duration=1.26}, +Information={filename="Information.ogg",duration=0.99}, +InnerNDBFrequency={filename="InnerNDBFrequency.ogg",duration=1.69}, +Kilometers={filename="Kilometers.ogg",duration=0.93}, +Knots={filename="Knots.ogg",duration=0.46}, +Left={filename="Left.ogg",duration=0.41}, +MegaHertz={filename="MegaHertz.ogg",duration=0.83}, +Meters={filename="Meters.ogg",duration=0.55}, +MetersPerSecond={filename="MetersPerSecond.ogg",duration=1.03}, +Miles={filename="Miles.ogg",duration=0.44}, +MillimetersOfMercury={filename="MillimetersOfMercury.ogg",duration=1.59}, +Minus={filename="Minus.ogg",duration=0.55}, +N0={filename="N-0.ogg",duration=0.52}, +N1={filename="N-1.ogg",duration=0.35}, +N2={filename="N-2.ogg",duration=0.41}, +N3={filename="N-3.ogg",duration=0.34}, +N4={filename="N-4.ogg",duration=0.37}, +N5={filename="N-5.ogg",duration=0.40}, +N6={filename="N-6.ogg",duration=0.46}, +N7={filename="N-7.ogg",duration=0.52}, +N8={filename="N-8.ogg",duration=0.36}, +N9={filename="N-9.ogg",duration=0.51}, +NauticalMiles={filename="NauticalMiles.ogg",duration=0.93}, +None={filename="None.ogg",duration=0.33}, +OuterNDBFrequency={filename="OuterNDBFrequency.ogg",duration=1.70}, +PRMGChannel={filename="PRMGChannel.ogg",duration=1.27}, +QFE={filename="QFE.ogg",duration=0.90}, +QNH={filename="QNH.ogg",duration=0.94}, +Rain={filename="Rain.ogg",duration=0.35}, +Right={filename="Right.ogg",duration=0.31}, +RSBNChannel={filename="RSBNChannel.ogg",duration=1.26}, +RunwayLength={filename="RunwayLength.ogg",duration=0.81}, +Snow={filename="Snow.ogg",duration=0.40}, +SnowStorm={filename="SnowStorm.ogg",duration=0.73}, +StatuteMiles={filename="StatuteMiles.ogg",duration=0.90}, +SunriseAt={filename="SunriseAt.ogg",duration=0.82}, +SunsetAt={filename="SunsetAt.ogg",duration=0.87}, +TACANChannel={filename="TACANChannel.ogg",duration=0.81}, +Temperature={filename="Temperature.ogg",duration=0.70}, +Thousand={filename="Thousand.ogg",duration=0.58}, +ThunderStorm={filename="ThunderStorm.ogg",duration=0.79}, +TimeLocal={filename="TimeLocal.ogg",duration=0.83}, +TimeZulu={filename="TimeZulu.ogg",duration=0.83}, +TowerFrequency={filename="TowerFrequency.ogg",duration=1.05}, +Visibilty={filename="Visibility.ogg",duration=1.16}, +VORFrequency={filename="VORFrequency.ogg",duration=1.28}, +WeatherPhenomena={filename="WeatherPhenomena.ogg",duration=1.09}, +WindFrom={filename="WindFrom.ogg",duration=0.63}, +Zulu={filename="Zulu.ogg",duration=0.51}, } ATIS.Messages={ EN= @@ -67332,10 +73590,69 @@ TACAN="Tacan", FARP="Farp", DELIMITER="Punto", }, +FR={ +HOURS="Heures", +TIME="Temps", +NOCLOUDINFO="Informations sur la couverture nuageuse non disponibles", +OVERCAST="Ciel couvert", +BROKEN="Nuages fragmentés", +SCATTERED="Nuages épars", +FEWCLOUDS="Nuages rares", +NOCLOUDS="Clair", +AIRPORT="Aéroport", +INFORMATION="Information", +SUNRISEAT="Levé du soleil à %s heure locale", +SUNSETAT="Couché du soleil à %s heure locale", +WINDFROMMS="Vent du %s pour %s mètres par seconde", +WINDFROMKNOTS="Vent du %s pour %s noeuds", +GUSTING="Rafale de vent", +VISIKM="Visibilité %s kilomètres", +VISISM="Visibilité %s Miles", +RAIN="Pluie", +TSTORM="Orage", +SNOW="Neige", +SSTROM="Tempête de neige", +FOG="Brouillard", +DUST="Poussière", +PHENOMENA="Phénomène météorologique", +CLOUDBASEM="Couverture nuageuse de %s à %s mètres", +CLOUDBASEFT="Couverture nuageuse de %s à %s pieds", +TEMPERATURE="Température", +DEWPOINT="Point de rosée", +ALTIMETER="Altimètre", +ACTIVERUN="Décollages piste", +ACTIVELANDING="Atterrissages piste", +LEFT="Gauche", +RIGHT="Droite", +RWYLENGTH="Longueur de piste", +METERS="Mètre", +FEET="Pieds", +ELEVATION="Hauteur", +TOWERFREQ="Fréquences de la tour", +ILSFREQ="Fréquences ILS", +OUTERNDB="Fréquences Outer NDB", +INNERNDB="Fréquences Inner NDB", +VORFREQ="Fréquences VOR", +VORFREQTTS="Fréquences V O R", +TACANCH="Canal TACAN %d", +RSBNCH="Canal RSBN", +PRMGCH="Canal PRMG", +ADVISE="Informez le contrôle que vous avez copié l'information", +STATUTE="Statute Miles", +DEGREES="Degré celcius", +FAHRENHEIT="Degré Fahrenheit", +INCHHG="Pouces de mercure", +MMHG="Millimètres de mercure", +HECTO="Hectopascals", +METERSPER="Mètres par seconde", +TACAN="TAKAN", +FARP="FARPE", +DELIMITER="Décimal", +} } ATIS.locale="en" _ATIS={} -ATIS.version="1.0.0" +ATIS.version="1.0.1" function ATIS:New(AirbaseName,Frequency,Modulation) local self=BASE:Inherit(self,FSM:New()) self.airbasename=AirbaseName @@ -67371,7 +73688,7 @@ return self end function ATIS:_InitLocalization() self:T(self.lid.."_InitLocalization") -self.gettext=TEXTANDSOUND:New("AWACS","en") +self.gettext=TEXTANDSOUND:New("ATIS","en") self.locale="en" for locale,table in pairs(self.Messages)do local Locale=string.lower(tostring(locale)) @@ -67387,11 +73704,46 @@ function ATIS:SetLocale(locale) self.locale=string.lower(locale) return self end -function ATIS:SetSoundfilesPath(path) -self.soundpath=tostring(path or"ATIS Soundfiles/") +function ATIS:SetSoundfilesPath(pathMain,pathAirports,pathNato) +self.soundpath=tostring(pathMain or"ATIS Soundfiles/") +if pathAirports==nil then +self.soundpathAirports=self.soundpath..env.mission.theatre.."/" +else +self.soundpathAirports=pathAirports +end +if pathNato==nil then +self.soundpathNato=self.soundpath.."NATO Alphabet/" +else +self.soundpathNato=pathNato +end self:T(self.lid..string.format("Setting sound files path to %s",self.soundpath)) return self end +function ATIS:SetSoundfilesInfo(csvfile) +local function getSound(filename) +for key,_soundfile in pairs(self.Sound)do +local soundfile=_soundfile +if filename==soundfile.filename then +return soundfile +end +end +return nil +end +local data=UTILS.ReadCSV(csvfile) +if data then +for i,sound in pairs(data)do +local soundfile=getSound(sound.filename..".ogg") +if soundfile then +soundfile.duration=tonumber(sound.duration) +else +self:E(string.format("ERROR: Could not get info for sound file %s",sound.filename)) +end +end +else +self:E(string.format("ERROR: Could not read sound csv file!")) +end +return self +end function ATIS:SetRadioRelayUnitName(unitname) self.relayunitname=unitname self:T(self.lid..string.format("Setting radio relay unit to %s",self.relayunitname)) @@ -67675,16 +74027,16 @@ self.radioqueue=RADIOQUEUE:New(self.frequency,self.modulation,string.format("ATI self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate()) self.radioqueue:SetSenderUnitName(self.relayunitname) self.radioqueue:SetRadioPower(self.power) -self.radioqueue:SetDigit(0,ATIS.Sound.N0.filename,ATIS.Sound.N0.duration,self.soundpath) -self.radioqueue:SetDigit(1,ATIS.Sound.N1.filename,ATIS.Sound.N1.duration,self.soundpath) -self.radioqueue:SetDigit(2,ATIS.Sound.N2.filename,ATIS.Sound.N2.duration,self.soundpath) -self.radioqueue:SetDigit(3,ATIS.Sound.N3.filename,ATIS.Sound.N3.duration,self.soundpath) -self.radioqueue:SetDigit(4,ATIS.Sound.N4.filename,ATIS.Sound.N4.duration,self.soundpath) -self.radioqueue:SetDigit(5,ATIS.Sound.N5.filename,ATIS.Sound.N5.duration,self.soundpath) -self.radioqueue:SetDigit(6,ATIS.Sound.N6.filename,ATIS.Sound.N6.duration,self.soundpath) -self.radioqueue:SetDigit(7,ATIS.Sound.N7.filename,ATIS.Sound.N7.duration,self.soundpath) -self.radioqueue:SetDigit(8,ATIS.Sound.N8.filename,ATIS.Sound.N8.duration,self.soundpath) -self.radioqueue:SetDigit(9,ATIS.Sound.N9.filename,ATIS.Sound.N9.duration,self.soundpath) +self.radioqueue:SetDigit(0,self.Sound.N0.filename,self.Sound.N0.duration,self.soundpath) +self.radioqueue:SetDigit(1,self.Sound.N1.filename,self.Sound.N1.duration,self.soundpath) +self.radioqueue:SetDigit(2,self.Sound.N2.filename,self.Sound.N2.duration,self.soundpath) +self.radioqueue:SetDigit(3,self.Sound.N3.filename,self.Sound.N3.duration,self.soundpath) +self.radioqueue:SetDigit(4,self.Sound.N4.filename,self.Sound.N4.duration,self.soundpath) +self.radioqueue:SetDigit(5,self.Sound.N5.filename,self.Sound.N5.duration,self.soundpath) +self.radioqueue:SetDigit(6,self.Sound.N6.filename,self.Sound.N6.duration,self.soundpath) +self.radioqueue:SetDigit(7,self.Sound.N7.filename,self.Sound.N7.duration,self.soundpath) +self.radioqueue:SetDigit(8,self.Sound.N8.filename,self.Sound.N8.duration,self.soundpath) +self.radioqueue:SetDigit(9,self.Sound.N9.filename,self.Sound.N9.duration,self.soundpath) self.radioqueue:Start(1,0.1) end self:HandleEvent(EVENTS.BaseCaptured) @@ -67830,17 +74182,26 @@ self:T3(string.format("ZULU =%s",tostring(ZULU))) self:T3(string.format("NATO =%s",tostring(NATO))) local hours=self.gettext:GetEntry("HOURS",self.locale) local sunrise=coord:GetSunrise() +local SUNRISE="no time" +local NorthPolar=true +if tostring(sunrise)~="N/S"and tostring(sunrise)~="N/R"then sunrise=UTILS.Split(sunrise,":") -local SUNRISE=string.format("%s%s",sunrise[1],sunrise[2]) +SUNRISE=string.format("%s%s",sunrise[1],sunrise[2]) if self.useSRS then SUNRISE=string.format("%s %s %s",sunrise[1],sunrise[2],hours) end +NorthPolar=false +end local sunset=coord:GetSunset() +local SUNSET="no time" +if tostring(sunset)~="N/S"and tostring(sunset)~="N/R"then sunset=UTILS.Split(sunset,":") -local SUNSET=string.format("%s%s",sunset[1],sunset[2]) +SUNSET=string.format("%s%s",sunset[1],sunset[2]) if self.useSRS then SUNSET=string.format("%s %s %s",sunset[1],sunset[2],hours) end +NorthPolar=false +end local temperature=coord:GetTemperature(height+5) local dewpoint=temperature-(100-self.relHumidity)/5 if self.TDegF then @@ -67849,33 +74210,31 @@ dewpoint=UTILS.CelsiusToFahrenheit(dewpoint) end local TEMPERATURE=string.format("%d",math.abs(temperature)) local DEWPOINT=string.format("%d",math.abs(dewpoint)) -local clouds,visibility,turbulence,fog,dust,static=self:GetMissionWeather() -if fog and fog.thicknessheight+25 then +dust=true +visibility=math.min(visibility,dustdens) end -if dust and height+25>UTILS.FeetToMeters(1500)then -dust=nil -end -local visibilitymin=visibility -if fog then -if fog.visibility0 and fheight>height+25 then +fog=true +visibility=math.min(visibility,fvis) end end local VISIBILITY="" if self.metric then -local reportedviz=UTILS.Round(visibilitymin/1000) +local reportedviz=UTILS.Round(visibility/1000) if reportedviz>10 then reportedviz=10 end VISIBILITY=string.format("%d",reportedviz) else -local reportedviz=UTILS.Round(UTILS.MetersToSM(visibilitymin)) +local reportedviz=UTILS.Round(UTILS.MetersToSM(visibility)) if reportedviz>10 then reportedviz=10 end @@ -67886,7 +74245,63 @@ local cloudceil=clouds.base+clouds.thickness local clouddens=clouds.density local cloudspreset=clouds.preset or"Nothing" local precepitation=0 -if cloudspreset:find("Preset10")then +if 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 +elseif cloudspreset:find("RainyPreset4")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset5")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset6")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("NEWRAINPRESET4")then +clouddens=5 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("RainyPreset")then +clouddens=9 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +elseif cloudspreset:find("Preset10")then clouddens=4 elseif cloudspreset:find("Preset11")then clouddens=4 @@ -67940,34 +74355,8 @@ 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 +self:E(string.format("WARNING! Unknown weather preset: %s",tostring(cloudspreset))) end local CLOUDBASE=string.format("%d",UTILS.MetersToFeet(cloudbase)) local CLOUDCEIL=string.format("%d",UTILS.MetersToFeet(cloudceil)) @@ -67982,25 +74371,25 @@ CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase) CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil) end local CloudCover={} -CloudCover=ATIS.Sound.CloudsNotAvailable +CloudCover=self.Sound.CloudsNotAvailable local CLOUDSsub=self.gettext:GetEntry("NOCLOUDINFO",self.locale) if static then if clouddens>=9 then -CloudCover=ATIS.Sound.CloudsOvercast +CloudCover=self.Sound.CloudsOvercast CLOUDSsub=self.gettext:GetEntry("OVERCAST",self.locale) elseif clouddens>=7 then -CloudCover=ATIS.Sound.CloudsBroken +CloudCover=self.Sound.CloudsBroken CLOUDSsub=self.gettext:GetEntry("BROKEN",self.locale) elseif clouddens>=4 then -CloudCover=ATIS.Sound.CloudsScattered +CloudCover=self.Sound.CloudsScattered CLOUDSsub=self.gettext:GetEntry("SCATTERED",self.locale) elseif clouddens>=1 then -CloudCover=ATIS.Sound.CloudsFew +CloudCover=self.Sound.CloudsFew CLOUDSsub=self.gettext:GetEntry("FEWCLOUDS",self.locale) else CLOUDBASE=nil CLOUDCEIL=nil -CloudCover=ATIS.Sound.CloudsNo +CloudCover=self.Sound.CloudsNo CLOUDSsub=self.gettext:GetEntry("NOCLOUDS",self.locale) end end @@ -68013,38 +74402,38 @@ 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) +self.radioqueue:NewTransmission(string.format("%s.ogg",self.airbasename),3.0,self.soundpathAirports,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) +self:Transmission(self.Sound.Information,0.5,subtitle) +self.radioqueue:NewTransmission(string.format("%s.ogg",NATO),0.75,self.soundpathNato) 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) +self:Transmission(self.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) +if not self.useSRS and NorthPolar==false then +self:Transmission(self.Sound.SunriseAt,0.5,subtitle) self.radioqueue:Number2Transmission(SUNRISE,nil,0.2) -self:Transmission(ATIS.Sound.TimeLocal,0.2) +self:Transmission(self.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) +if not self.useSRS and NorthPolar==false then +self:Transmission(self.Sound.SunsetAt,0.5,subtitle) self.radioqueue:Number2Transmission(SUNSET,nil,0.5) -self:Transmission(ATIS.Sound.TimeLocal,0.2) +self:Transmission(self.Sound.TimeLocal,0.2) end alltext=alltext..";\n"..subtitle end @@ -68063,17 +74452,17 @@ 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:Transmission(self.Sound.WindFrom,1.0,subtitle) self.radioqueue:Number2Transmission(WINDFROM) -self:Transmission(ATIS.Sound.At,0.2) +self:Transmission(self.Sound.At,0.2) self.radioqueue:Number2Transmission(WINDSPEED) if self.metric then -self:Transmission(ATIS.Sound.MetersPerSecond,0.2) +self:Transmission(self.Sound.MetersPerSecond,0.2) else -self:Transmission(ATIS.Sound.Knots,0.2) +self:Transmission(self.Sound.Knots,0.2) end if turbulence>0 then -self:Transmission(ATIS.Sound.Gusting,0.2) +self:Transmission(self.Sound.Gusting,0.2) end end alltext=alltext..";\n"..subtitle @@ -68085,12 +74474,12 @@ 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:Transmission(self.Sound.Visibilty,1.0,subtitle) self.radioqueue:Number2Transmission(VISIBILITY) if self.metric then -self:Transmission(ATIS.Sound.Kilometers,0.2) +self:Transmission(self.Sound.Kilometers,0.2) else -self:Transmission(ATIS.Sound.StatuteMiles,0.2) +self:Transmission(self.Sound.StatuteMiles,0.2) end end alltext=alltext..";\n"..subtitle @@ -68131,21 +74520,21 @@ 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) +self:Transmission(self.Sound.WeatherPhenomena,1.0,subtitle) if precepitation==1 then -self:Transmission(ATIS.Sound.Rain,0.5) +self:Transmission(self.Sound.Rain,0.5) elseif precepitation==2 then -self:Transmission(ATIS.Sound.ThunderStorm,0.5) +self:Transmission(self.Sound.ThunderStorm,0.5) elseif precepitation==3 then -self:Transmission(ATIS.Sound.Snow,0.5) +self:Transmission(self.Sound.Snow,0.5) elseif precepitation==4 then -self:Transmission(ATIS.Sound.SnowStorm,0.5) +self:Transmission(self.Sound.SnowStorm,0.5) end if fog then -self:Transmission(ATIS.Sound.Fog,0.5) +self:Transmission(self.Sound.Fog,0.5) end if dust then -self:Transmission(ATIS.Sound.Dust,0.5) +self:Transmission(self.Sound.Dust,0.5) end end alltext=alltext..";\n"..subtitle @@ -68164,28 +74553,28 @@ 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) +self:Transmission(self.Sound.CloudBase,1.0,subtitle) if tonumber(CLOUDBASE1000)>0 then self.radioqueue:Number2Transmission(CLOUDBASE1000) -self:Transmission(ATIS.Sound.Thousand,0.1) +self:Transmission(self.Sound.Thousand,0.1) end if tonumber(CLOUDBASE0100)>0 then self.radioqueue:Number2Transmission(CLOUDBASE0100) -self:Transmission(ATIS.Sound.Hundred,0.1) +self:Transmission(self.Sound.Hundred,0.1) end -self:Transmission(ATIS.Sound.CloudCeiling,0.5) +self:Transmission(self.Sound.CloudCeiling,0.5) if tonumber(CLOUDCEIL1000)>0 then self.radioqueue:Number2Transmission(CLOUDCEIL1000) -self:Transmission(ATIS.Sound.Thousand,0.1) +self:Transmission(self.Sound.Thousand,0.1) end if tonumber(CLOUDCEIL0100)>0 then self.radioqueue:Number2Transmission(CLOUDCEIL0100) -self:Transmission(ATIS.Sound.Hundred,0.1) +self:Transmission(self.Sound.Hundred,0.1) end if self.metric then -self:Transmission(ATIS.Sound.Meters,0.1) +self:Transmission(self.Sound.Meters,0.1) else -self:Transmission(ATIS.Sound.Feet,0.1) +self:Transmission(self.Sound.Feet,0.1) end end end @@ -68207,15 +74596,15 @@ end end local _TEMPERATURE=subtitle if not self.useSRS then -self:Transmission(ATIS.Sound.Temperature,1.0,subtitle) +self:Transmission(self.Sound.Temperature,1.0,subtitle) if temperature<0 then -self:Transmission(ATIS.Sound.Minus,0.2) +self:Transmission(self.Sound.Minus,0.2) end self.radioqueue:Number2Transmission(TEMPERATURE) if self.TDegF then -self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2) +self:Transmission(self.Sound.DegreesFahrenheit,0.2) else -self:Transmission(ATIS.Sound.DegreesCelsius,0.2) +self:Transmission(self.Sound.DegreesCelsius,0.2) end end alltext=alltext..";\n"..subtitle @@ -68235,15 +74624,15 @@ end end local _DEWPOINT=subtitle if not self.useSRS then -self:Transmission(ATIS.Sound.DewPoint,1.0,subtitle) +self:Transmission(self.Sound.DewPoint,1.0,subtitle) if dewpoint<0 then -self:Transmission(ATIS.Sound.Minus,0.2) +self:Transmission(self.Sound.Minus,0.2) end self.radioqueue:Number2Transmission(DEWPOINT) if self.TDegF then -self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2) +self:Transmission(self.Sound.DegreesFahrenheit,0.2) else -self:Transmission(ATIS.Sound.DegreesCelsius,0.2) +self:Transmission(self.Sound.DegreesCelsius,0.2) end end alltext=alltext..";\n"..subtitle @@ -68278,30 +74667,30 @@ end end local _ALTIMETER=subtitle if not self.useSRS then -self:Transmission(ATIS.Sound.Altimeter,1.0,subtitle) +self:Transmission(self.Sound.Altimeter,1.0,subtitle) if not self.qnhonly then -self:Transmission(ATIS.Sound.QNH,0.5) +self:Transmission(self.Sound.QNH,0.5) end self.radioqueue:Number2Transmission(QNH[1]) if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then -self:Transmission(ATIS.Sound.Decimal,0.2) +self:Transmission(self.Sound.Decimal,0.2) end self.radioqueue:Number2Transmission(QNH[2]) if not self.qnhonly then -self:Transmission(ATIS.Sound.QFE,0.75) +self:Transmission(self.Sound.QFE,0.75) self.radioqueue:Number2Transmission(QFE[1]) if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then -self:Transmission(ATIS.Sound.Decimal,0.2) +self:Transmission(self.Sound.Decimal,0.2) end self.radioqueue:Number2Transmission(QFE[2]) end if self.PmmHg then -self:Transmission(ATIS.Sound.MillimetersOfMercury,0.1) +self:Transmission(self.Sound.MillimetersOfMercury,0.1) else if self.metric then -self:Transmission(ATIS.Sound.HectoPascal,0.1) +self:Transmission(self.Sound.HectoPascal,0.1) else -self:Transmission(ATIS.Sound.InchesOfMercury,0.1) +self:Transmission(self.Sound.InchesOfMercury,0.1) end end end @@ -68309,7 +74698,7 @@ alltext=alltext..";\n"..subtitle local _RUNACT if not self.ATISforFARPs then local subtitle="" -if runwayLanding then +if runwayLanding and runwayLanding~=runwayTakeoff then local actrun=self.gettext:GetEntry("ACTIVELANDING",self.locale) subtitle=string.format("%s %s",actrun,runwayLanding) if rwyLandingLeft==true then @@ -68318,6 +74707,15 @@ elseif rwyLandingLeft==false then subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale) end alltext=alltext..";\n"..subtitle +if not self.useSRS then +self:Transmission(self.Sound.ActiveRunwayArrival,1.0,subtitle) +self.radioqueue:Number2Transmission(runwayLanding) +if rwyLandingLeft==true then +self:Transmission(self.Sound.Left,0.2) +elseif rwyLandingLeft==false then +self:Transmission(self.Sound.Right,0.2) +end +end end if runwayTakeoff then local actrun=self.gettext:GetEntry("ACTIVERUN",self.locale) @@ -68327,18 +74725,18 @@ subtitle=subtitle.." "..self.gettext:GetEntry("LEFT",self.locale) elseif rwyTakeoffLeft==false then subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale) end +alltext=alltext..";\n"..subtitle +if not self.useSRS then +self:Transmission(self.Sound.ActiveRunwayDeparture,1.0,subtitle) +self.radioqueue:Number2Transmission(runwayTakeoff) +if rwyTakeoffLeft==true then +self:Transmission(self.Sound.Left,0.2) +elseif rwyTakeoffLeft==false then +self:Transmission(self.Sound.Right,0.2) +end +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 @@ -68356,19 +74754,19 @@ else subtitle=subtitle.." "..feet end if not self.useSRS then -self:Transmission(ATIS.Sound.RunwayLength,1.0,subtitle) +self:Transmission(self.Sound.RunwayLength,1.0,subtitle) if tonumber(L1000)>0 then self.radioqueue:Number2Transmission(L1000) -self:Transmission(ATIS.Sound.Thousand,0.1) +self:Transmission(self.Sound.Thousand,0.1) end if tonumber(L0100)>0 then self.radioqueue:Number2Transmission(L0100) -self:Transmission(ATIS.Sound.Hundred,0.1) +self:Transmission(self.Sound.Hundred,0.1) end if self.metric then -self:Transmission(ATIS.Sound.Meters,0.1) +self:Transmission(self.Sound.Meters,0.1) else -self:Transmission(ATIS.Sound.Feet,0.1) +self:Transmission(self.Sound.Feet,0.1) end end alltext=alltext..";\n"..subtitle @@ -68390,19 +74788,19 @@ else subtitle=subtitle.." "..feet end if not self.useSRS then -self:Transmission(ATIS.Sound.Elevation,1.0,subtitle) +self:Transmission(self.Sound.Elevation,1.0,subtitle) if tonumber(L1000)>0 then self.radioqueue:Number2Transmission(L1000) -self:Transmission(ATIS.Sound.Thousand,0.1) +self:Transmission(self.Sound.Thousand,0.1) end if tonumber(L0100)>0 then self.radioqueue:Number2Transmission(L0100) -self:Transmission(ATIS.Sound.Hundred,0.1) +self:Transmission(self.Sound.Hundred,0.1) end if self.metric then -self:Transmission(ATIS.Sound.Meters,0.1) +self:Transmission(self.Sound.Meters,0.1) else -self:Transmission(ATIS.Sound.Feet,0.1) +self:Transmission(self.Sound.Feet,0.1) end end alltext=alltext..";\n"..subtitle @@ -68418,16 +74816,16 @@ 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) +self:Transmission(self.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:Transmission(self.Sound.Decimal,0.2) self.radioqueue:Number2Transmission(f[2]) end -self:Transmission(ATIS.Sound.MegaHertz,0.2) +self:Transmission(self.Sound.MegaHertz,0.2) end end alltext=alltext..";\n"..subtitle @@ -68437,15 +74835,15 @@ 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) +self:Transmission(self.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:Transmission(self.Sound.Decimal,0.2) self.radioqueue:Number2Transmission(f[2]) end -self:Transmission(ATIS.Sound.MegaHertz,0.2) +self:Transmission(self.Sound.MegaHertz,0.2) end alltext=alltext..";\n"..subtitle end @@ -68454,15 +74852,15 @@ 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) +self:Transmission(self.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:Transmission(self.Sound.Decimal,0.2) self.radioqueue:Number2Transmission(f[2]) end -self:Transmission(ATIS.Sound.MegaHertz,0.2) +self:Transmission(self.Sound.MegaHertz,0.2) end alltext=alltext..";\n"..subtitle end @@ -68471,15 +74869,15 @@ 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) +self:Transmission(self.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:Transmission(self.Sound.Decimal,0.2) self.radioqueue:Number2Transmission(f[2]) end -self:Transmission(ATIS.Sound.MegaHertz,0.2) +self:Transmission(self.Sound.MegaHertz,0.2) end alltext=alltext..";\n"..subtitle end @@ -68491,15 +74889,15 @@ 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) +self:Transmission(self.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:Transmission(self.Sound.Decimal,0.2) self.radioqueue:Number2Transmission(f[2]) end -self:Transmission(ATIS.Sound.MegaHertz,0.2) +self:Transmission(self.Sound.MegaHertz,0.2) end alltext=alltext..";\n"..subtitle end @@ -68507,9 +74905,9 @@ 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:Transmission(self.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) +self.radioqueue:NewTransmission("Xray.ogg",0.75,self.soundpathNato,nil,0.2) end alltext=alltext..";\n"..subtitle end @@ -68517,7 +74915,7 @@ 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:Transmission(self.Sound.RSBNChannel,1.0,subtitle) self.radioqueue:Number2Transmission(tostring(self.rsbn),nil,0.2) end alltext=alltext..";\n"..subtitle @@ -68527,7 +74925,7 @@ 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:Transmission(self.Sound.PRMGChannel,1.0,subtitle) self.radioqueue:Number2Transmission(tostring(ndb.frequency),nil,0.5) end alltext=alltext..";\n"..subtitle @@ -68538,8 +74936,8 @@ 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) +self:Transmission(self.Sound.AdviceOnInitial,0.5,subtitle) +self.radioqueue:NewTransmission(string.format("%s.ogg",NATO),0.75,self.soundpathNato) end alltext=alltext..";\n"..subtitle self:Report(alltext) @@ -68576,7 +74974,7 @@ 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) +local duration=MSRS.getSpeechTime(text,0.95) self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2) self.SRSText=text end @@ -68695,18 +75093,13 @@ 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 self:T("FF weather:") self:T({clouds=clouds}) self:T({visibility=visibility}) self:T({turbulence=turbulence}) -self:T({fog=fog}) self:T({dust=dust}) self:T({static=static}) -return clouds,visibility,turbulence,fog,dust,static +return clouds,visibility,turbulence,dust,static end function ATIS:_GetThousandsAndHundreds(n) local N=UTILS.Round(n/1000,1) @@ -68729,6 +75122,7 @@ Positionable=nil, HasBeenDropped=false, PerCrateMass=0, Stock=nil, +Stock0=nil, Mark=nil, DontShowInMenu=false, Location=nil, @@ -68741,6 +75135,7 @@ CRATE="Crate", REPAIR="Repair", ENGINEERS="Engineers", STATIC="Static", +GCLOADABLE="GC_Loadable", } function CTLD_CARGO:New(ID,Name,Templates,Sorte,HasBeenMoved,LoadDirectly,CratesNeeded,Positionable,Dropped,PerCrateMass,Stock,Subcategory,DontShowInMenu,Location) local self=BASE:Inherit(self,BASE:New()) @@ -68756,15 +75151,58 @@ self.Positionable=Positionable or nil self.HasBeenDropped=Dropped or false self.PerCrateMass=PerCrateMass or 0 self.Stock=Stock or nil +self.Stock0=Stock or nil self.Mark=nil self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false +self.ResourceMap=nil +self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end +self.StaticShape=nil +self.TypeNames=nil +self.StaticCategory="Cargos" if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end +function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) +self.StaticCategory=Category or"Cargos" +self.StaticType=TypeName or"container_cargo" +self.StaticShape=ShapeName +return self +end +function CTLD_CARGO:GetStaticTypeAndShape() +return self.StaticCategory,self.StaticType,self.StaticShape +end +function CTLD_CARGO:AddUnitTypeName(UnitTypes) +if not self.TypeNames then self.TypeNames={}end +if type(UnitTypes)~="table"then UnitTypes={UnitTypes}end +for _,_singletype in pairs(UnitTypes or{})do +self.TypeNames[_singletype]=_singletype +end +return self +end +function CTLD_CARGO:UnitCanCarry(Unit) +if self.TypeNames==nil then return true end +local typename=Unit:GetTypeName()or"none" +if self.TypeNames[typename]then +return true +else +return false +end +end +function CTLD_CARGO:SetStaticResourceMap(ResourceMap) +self.ResourceMap=ResourceMap +return self +end +function CTLD_CARGO:GetStaticResourceMap() +return self.ResourceMap +end function CTLD_CARGO:GetLocation() return self.Location end @@ -68786,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -68811,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -68821,6 +75263,20 @@ else return-1 end end +function CTLD_CARGO:GetStock0() +if self.Stock0 then +return self.Stock0 +else +return-1 +end +end +function CTLD_CARGO:GetRelativeStock() +if self.Stock and self.Stock0 then +return math.floor((self.Stock/self.Stock0)*100) +else +return-1 +end +end function CTLD_CARGO:AddStock(Number) if self.Stock then local number=Number or 1 @@ -69051,6 +75507,18 @@ debug=false, wpZones={}, dropOffZones={}, pickupZones={}, +DynamicCargo={}, +ChinookTroopCircleRadius=5, +TroopUnloadDistGround=5, +TroopUnloadDistGroundHerc=25, +TroopUnloadDistGroundHook=15, +TroopUnloadDistHoverHook=5, +TroopUnloadDistHover=1.5, +UserSetGroup=nil, +LoadedGroupsTable={}, +keeploadtable=true, +allowCATransport=false, +VehicleMoveFormation=AI.Task.VehicleFormation.VEE, } CTLD.RadioModulation={ AM=0, @@ -69076,14 +75544,26 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, +["OH58D"]={type="OH58D",crates=false,troops=false,cratelimit=0,trooplimit=0,length=14,cargoweightlimit=400}, +["CH-47Fbl1"]={type="CH-47Fbl1",crates=true,troops=true,cratelimit=4,trooplimit=31,length=20,cargoweightlimit=10800}, +["MosquitoFBMkVI"]={type="MosquitoFBMkVI",crates=true,troops=false,cratelimit=2,trooplimit=0,length=13,cargoweightlimit=1800}, +["M 818"]={type="M 818",crates=true,troops=true,cratelimit=4,trooplimit=12,length=9,cargoweightlimit=4500}, } -CTLD.version="1.0.53" +CTLD.FixedWingTypes={ +["Hercules"]="Hercules", +["Bronco"]="Bronco", +["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", +} +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -69126,11 +75606,17 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") +self:AddTransition("*","CratesPacked","*") +self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") +self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") self:AddTransition("*","Stop","Stopped") self.PilotGroups={} @@ -69169,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -69182,11 +75670,17 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true +self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false -self.HercMinAngels=165 -self.HercMaxAngels=2000 -self.HercMaxSpeed=77 +self.enableFixedWing=false +self.FixedMinAngels=165 +self.FixedMaxAngels=2000 +self.FixedMaxSpeed=77 +self.validateAndRepositionUnits=false self.suppressmessages=false self.repairtime=300 self.buildtime=300 @@ -69200,22 +75694,34 @@ self.enableLoadSave=false self.filepath=nil self.saveinterval=600 self.eventoninject=true +self.keeploadtable=true +self.LoadedGroupsTable={} self.usesubcats=false self.subcats={} self.subcatsTroop={} +self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 +self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true self.surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} +self.enableChinookGCLoading=true +self.ChinookTroopCircleRadius=5 +self.UserSetGroup=nil 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.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do math.random() end +self.allowCATransport=false +self.CATransportSet=nil self:_GenerateVHFrequencies() self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() @@ -69238,6 +75744,11 @@ capabilities.cargoweightlimit=0 end return capabilities end +function CTLD:AllowCATransport(OnOff,ClientSet) +self.allowCATransport=OnOff +self.CATransportSet=ClientSet +return self +end function CTLD:_GenerateUHFrequencies() self:T(self.lid.." _GenerateUHFrequencies") self.FreeUHFFrequencies={} @@ -69284,25 +75795,128 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end 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 +if self:IsFixedWing(_unit)and self.enableFixedWing then +local unitname=event.IniUnitName or"none" +self.Loaded_Cargo[unitname]=nil +self:_RefreshF10Menus() +end +if _unit:IsGround()and self.allowCATransport then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil self:_RefreshF10Menus() end return -elseif event.id==EVENTS.PlayerLeaveUnit then +elseif event.id==EVENTS.Land or event.id==EVENTS.Takeoff then +local unitname=event.IniUnitName +if self.CtldUnits[unitname]then +local _group=event.IniGroup +local _unit=event.IniUnit +self:_RefreshLoadCratesMenu(_group,_unit) +if self:IsFixedWing(_unit)and self.enableFixedWing then +self:_RefreshDropCratesMenu(_group,_unit) +end +end +elseif event.id==EVENTS.PlayerLeaveUnit or event.id==EVENTS.UnitLost then local unitname=event.IniUnitName or"none" +if self.CtldUnits[unitname]then +local lostcargo=UTILS.DeepCopy(self.Loaded_Cargo[unitname]or{}) +self:__HelicopterLost(1,unitname,lostcargo) +end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end +elseif event.id==EVENTS.NewDynamicCargo then +self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) +self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo +elseif event.id==EVENTS.DynamicCargoLoaded then +self:T(self.lid.."GC Loaded Event "..event.IniDynamicCargoName) +local dcargo=event.IniDynamicCargo +local client=CLIENT:FindByPlayerName(dcargo.Owner) +if client and client:IsAlive()then +local unitname=client:GetName()or"none" +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +end +loaded.Cratesloaded=loaded.Cratesloaded+1 +table.insert(loaded.Cargo,dcargo) +self.Loaded_Cargo[unitname]=nil +self.Loaded_Cargo[unitname]=loaded +local Group=client:GetGroup() +self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) +self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) +end +elseif event.id==EVENTS.DynamicCargoUnloaded then +self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) +local dcargo=event.IniDynamicCargo +local client=CLIENT:FindByPlayerName(dcargo.Owner) +if client and client:IsAlive()then +local unitname=client:GetName()or"none" +local loaded={} +if self.Loaded_Cargo[unitname]then +loaded=self.Loaded_Cargo[unitname] +loaded.Cratesloaded=loaded.Cratesloaded-1 +if loaded.Cratesloaded<0 then loaded.Cratesloaded=0 end +local Loaded={} +for _,_item in pairs(loaded.Cargo or{})do +self:T(self.lid.."UNLOAD checking: ".._item:GetName()) +self:T(self.lid.."UNLOAD state: "..tostring(_item:WasDropped())) +if _item and _item:GetType()==CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName~=_item:GetName()and not _item:WasDropped()then +table.insert(Loaded,_item) +else +table.insert(Loaded,_item) +end +end +loaded.Cargo=nil +loaded.Cargo=Loaded +self.Loaded_Cargo[unitname]=nil +self.Loaded_Cargo[unitname]=loaded +else +loaded={} +loaded.Troopsloaded=0 +loaded.Cratesloaded=0 +loaded.Cargo={} +self.Loaded_Cargo[unitname]=loaded +end +local Group=client:GetGroup() +self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) +self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) +end +elseif event.id==EVENTS.DynamicCargoRemoved then +self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) +self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -69330,8 +75944,25 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end +function CTLD:AddAllowedFixedWingType(typename) +if type(typename)=="string"then +self.FixedWingTypes[typename]=typename +elseif typename and typename.ClassName and typename:IsInstanceOf("UNIT")then +local TypeName=typename:GetTypeName()or"none" +self.FixedWingTypes[TypeName]=TypeName +else +self:E(self.lid.."No valid typename or no UNIT handed!") +end +return self +end function CTLD:PreloadTroops(Unit,Troopname) self:T(self.lid.." PreloadTroops") local name=Troopname or"Unknown" @@ -69463,10 +76094,12 @@ self:T({cargotype=loadcargotype}) loaded.Troopsloaded=loaded.Troopsloaded+troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname]=loaded -self:_SendMessage("Troops boarded!",10,false,Group) +self:_SendMessage(string.format("%s boarded!",cgoname),10,false,Group) +self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -69562,7 +76195,8 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) +local hassecondaries=false 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 @@ -69581,6 +76215,7 @@ local unitcoord=unit:GetCoordinate() local nearestGroup=nil local nearestGroupIndex=-1 local nearestDistance=10000000 +local maxdistance=0 local nearestList={} local distancekeys={} local extractdistance=self.CrateDistance*self.ExtractFactor @@ -69592,6 +76227,11 @@ if distance<=extractdistance and distance~=-1 and(TNow-vtime>300)then nearestGroup=v nearestGroupIndex=k nearestDistance=distance +if math.floor(distance)>maxdistance then maxdistance=math.floor(distance)end +if nearestList[math.floor(distance)]then +distance=maxdistance+1 +maxdistance=distance +end table.insert(nearestList,math.floor(distance),v) distancekeys[#distancekeys+1]=math.floor(distance) end @@ -69637,14 +76277,16 @@ 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 +local running=math.floor(nearestDistance/4)+20 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:ScheduleOnce(running,self._SendMessage,self,string.format("%s boarded!",Cargotype.Name),10,false,Group) +self:_SendMessage(string.format("%s boarding!",Cargotype.Name),10,false,Group) +self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) -self:__TroopsExtracted(running,Group,Unit,nearestGroup) +local groupname=nearestGroup:GetName() +self:__TroopsExtracted(running,Group,Unit,nearestGroup,groupname) local coord=Unit:GetCoordinate()or Group:GetCoordinate() local Point if coord then @@ -69652,18 +76294,22 @@ 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) +nearestGroup:RouteToVec2(Point,5) end end +hassecondaries=false if type(Cargotype.Templates)=="table"and Cargotype.Templates[2]then for _,_key in pairs(Cargotype.Templates)do table.insert(secondarygroups,_key) +hassecondaries=true end end -nearestGroup:Destroy(false,running) +local destroytimer=math.random(10,20) +nearestGroup:Destroy(false,destroytimer) end end end +if hassecondaries==true then for _,_name in pairs(secondarygroups)do for _,_group in pairs(nearestList)do if _group and _group:IsAlive()then @@ -69674,21 +76320,344 @@ end end end end +end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -69717,21 +76686,26 @@ 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 +if not self.debug then return false 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) +local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false end -local IsHerc=self:IsHercules(Unit) +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false +end +local IsHerc=self:IsFixedWing(Unit) +local IsHook=self:IsHook(Unit) +local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -69746,44 +76720,55 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 local addon=0 -if IsHerc then +if IsHerc or IsHook or IsTruck then addon=180 end +heading=(heading+addon)%360 +local row=1 +local column=1 +local initialdist=IsHerc and 16 or(capabilities.length+2) +local startpos=position:Translate(initialdist,heading) +if self.placeCratesAhead==true then +cratedistance=initialdist +end +local cratecoord=nil for i=1,number do local cratealias=string.format("%s-%s-%d",cratename,cratetemplate,math.random(1,100000)) -if not self.placeCratesAhead then +if not self.placeCratesAhead or drop==true then cratedistance=(i-1)*2.5+capabilities.length if cratedistance>self.CrateDistance then cratedistance=self.CrateDistance end +if self:IsUnitInAir(Unit)and self:IsFixedWing(Unit)then +rheading=math.random(20,60) +else rheading=UTILS.RandomGaussian(0,30,-90,90,100) -rheading=math.fmod((heading+rheading+addon),360) +end +rheading=math.fmod((heading+rheading),360) +cratecoord=position:Translate(cratedistance,rheading) 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 +cratedistance=(row-1)*6 +rheading=90 +row=row+1 +cratecoord=startpos:Translate(cratedistance,rheading) +if row>4 then +row=1 +startpos:Translate(6,heading,nil,true) 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 +local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() +local basetype=CType or self.basetype or"container_cargo" +CCat=CCat or"Cargos" +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -69794,17 +76779,31 @@ 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) +local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitLinkToUnit(Ship,dist,Offy,0) -:Spawn(270,cratealias) +if CShape then +spawnstatic:InitShape(CShape) +end +if isstatic then +local map=cargotype:GetStaticResourceMap() +spawnstatic.TemplateStaticUnit.resourcePayload=map +end +self.Spawned_Crates[self.CrateCounter]=spawnstatic:Spawn(270,cratealias) else -self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) +local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCoordinate(cratecoord) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) -:Spawn(270,cratealias) +if CShape then +spawnstatic:InitShape(CShape) +end +if isstatic then +local map=cargotype:GetStaticResourceMap() +spawnstatic.TemplateStaticUnit.resourcePayload=map +end +self.Spawned_Crates[self.CrateCounter]=spawnstatic:Spawn(270,cratealias) end local templ=cargotype:GetTemplates() local sorte=cargotype:GetType() @@ -69813,24 +76812,52 @@ 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) +local map=cargotype:GetStaticResourceMap() +realcargo:SetStaticResourceMap(map) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) +if cargotype.TypeNames then +realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) +end 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) +local map=cargotype:GetStaticResourceMap() +realcargo:SetStaticResourceMap(map) +if cargotype.TypeNames then +realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) +end +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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 +else +if not quiet then self:_SendMessage(text,10,false,Group) -return self end -function CTLD:InjectStatics(Zone,Cargo,RandomCoord) +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end +self:_RefreshLoadCratesMenu(Group,Unit) +return true +end +function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") local cratecoord=Zone:GetCoordinate() if RandomCoord then @@ -69846,40 +76873,52 @@ local cratetemplate="Container" local cratename=cargotype:GetName() local cgotype=cargotype:GetType() local cgomass=cargotype:GetMass() +local cratenumber=cargotype:GetCratesNeeded()or 1 +if FromLoad==true then cratenumber=1 end +for i=1,cratenumber do 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" +local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() +local basetype=CType or self.basetype or"container_cargo" +CCat=CCat or"Cargos" if isstatic then basetype=cratetemplate end self.CrateCounter=self.CrateCounter+1 -self.Spawned_Crates[self.CrateCounter]=SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) +local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitCoordinate(cratecoord) -:Spawn(270,cratealias) +if CShape then +spawnstatic:InitShape(CShape) +end +if isstatic then +local map=cargotype:GetStaticResourceMap() +spawnstatic.TemplateStaticUnit.resourcePayload=map +end +self.Spawned_Crates[self.CrateCounter]=spawnstatic: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) +end 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) +self:InjectStatics(Zone,cargotype,true,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 crates,number,loadedbygc,indexgc=self:_FindCratesNearby(_group,_unit,finddist,true,true) +if number>0 or indexgc>0 then local text=REPORT:New("Crates Found Nearby:") text:Add("------------------------------------------------------------") for _,_entry in pairs(crates)do @@ -69896,35 +76935,106 @@ if text:GetCount()==1 then text:Add(" N O N E") end text:Add("------------------------------------------------------------") +if indexgc>0 then +text:Add("Probably ground crew loadable (F8)") +for _,_entry in pairs(loadedbygc)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 +end 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:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +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) +local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true,true) if number>0 then +local removedIDs={} 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 +local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end -entry:GetPositionable():Destroy(false) +table.insert(removedIDs,entry:GetID()) end if text:GetCount()==1 then text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end +self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -69950,18 +77060,20 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() local existingcrates=self.Spawned_Cargo local index=0 +local indexg=0 local found={} +local LoadedbyGC={} local loadedmass=0 local unittype="none" local capabilities={} -local maxmass=2000 local maxloadable=2000 +local IsHook=self:IsHook(_unit) if not _ignoreweight then maxloadable=self:_GetMaxLoadableMass(_unit) end @@ -69969,20 +77081,38 @@ 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() +local staticid=cargo:GetID() self:T(self.lid.." Found cargo mass: "..weight) if static and static:IsAlive()then +local restricthooktononstatics=self.enableChinookGCLoading and IsHook +self:T(self.lid.." restricthooktononstatics: "..tostring(restricthooktononstatics)) +local cargoisstatic=cargo:GetType()==CTLD_CARGO.Enum.STATIC and true or false +self:T(self.lid.." Cargo is static: "..tostring(cargoisstatic)) +local restricted=cargoisstatic and restricthooktononstatics +self:T(self.lid.." Loading restricted: "..tostring(restricted)) local staticpos=static:GetCoordinate() +local cando=cargo:UnitCanCarry(_unit) +if ignoretype==true then cando=true end +self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) -if distance<=finddist and static and(weight<=maxloadable or _ignoreweight)then +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distancecrateind 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() +local crateMap={} +for _,cObj in pairs(nearcrates)do +if not cObj:HasMoved()or self.allowcratepickupagain then +local cName=cObj:GetName()or"Unknown" +crateMap[cName]=crateMap[cName]or{} +table.insert(crateMap[cName],cObj) end end -end -if crateind>0 then -local crate=nearcrates[crateind] +for cName,crateList in pairs(crateMap)do +if capacity<=0 then break end +table.sort(crateList,function(a,b)return a:GetID()>b:GetID()end) +local needed=crateList[1]:GetCratesNeeded()or 1 +local totalFound=#crateList +local loadedHere=0 +while loaded.Cratesloaded0 then +local fullSets=math.floor(loadedHere/needed) +local leftover=loadedHere%needed +if needed>1 then +if fullSets>0 and leftover==0 then +self:_SendMessage(string.format("Loaded %d %s.",fullSets,cName),10,false,Group) +elseif fullSets>0 and leftover>0 then +self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).",fullSets,cName,leftover),10,false,Group) +else +self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.",loadedHere,needed,cName),15,false,Group) +end +else +self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cName),10,false,Group) +end end end self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) +self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshLoadCratesMenu(Group,Unit) self:_CleanupTrackedCrates(crateidsloaded) +self:__CratesPickedUp(1,Group,Unit,loaded.Cargo) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) end end return self @@ -70103,9 +77257,13 @@ 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 +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped()then loadedmass=loadedmass+cargo.PerCrateMass end +if type==CTLD_CARGO.Enum.GCLOADABLE then +local mass=cargo:GetCargoWeight() +loadedmass=loadedmass+mass +end end end return loadedmass @@ -70136,7 +77294,16 @@ 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 finddist=self.CrateDistance or 35 +local hercInnerCrates=nil +local hercInnerCount=0 +if self:IsC130J(Unit)or self:IsHook(Unit)then +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local innerCrates,innerCount=self:_FindCratesNearby(Group,Unit,innerDist,true,true) +hercInnerCrates=innerCrates +hercInnerCount=innerCount or 0 +end +if self.Loaded_Cargo[unitname]or hercInnerCount>0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -70158,17 +77325,40 @@ end report:Add("------------------------------------------------------------") report:Add(" -- CRATES --") local cratecount=0 -for _,_cargo in pairs(cargotable)do +local accumCrates={} +for _,_cargo in pairs(cargotable or{})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())) +if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CTLD_CARGO.Enum.GCLOADABLE)and(not cargo:WasDropped()or self.allowcratepickupagain)then +local cName=cargo:GetName() +local needed=cargo:GetCratesNeeded()or 1 +accumCrates[cName]=accumCrates[cName]or{count=0,needed=needed} +accumCrates[cName].count=accumCrates[cName].count+1 +end +if type==CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped()then +report:Add(string.format("GC loaded Crate: %s size 1",cargo:GetName())) cratecount=cratecount+1 end end +for cName,data in pairs(accumCrates)do +cratecount=cratecount+data.count +report:Add(string.format("Crate: %s %d/%d",cName,data.count,data.needed)) +end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -70261,8 +77451,20 @@ 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 +function CTLD:IsFixedWing(Unit) +local typename=Unit:GetTypeName()or"none" +for _,_name in pairs(self.FixedWingTypes or{})do +if _name and(typename==_name or string.find(typename,_name,1,true))then +return true +end +end +return false +end +function CTLD:IsHook(Unit) +if not Unit then return false end +local typeName=Unit:GetTypeName() +if not typeName then return false end +if string.find(typeName,"CH.47")then return true else return false @@ -70272,8 +77474,8 @@ 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)) +local slightshift=math.abs(math.random(1,500)/100) +local newcenter=Coordinate:Translate(Radius+slightshift,((Heading+270+math.random(1,10))%360)) for i=1,360,math.floor(360/numbertroops)do local phead=((Heading+270+i)%360) local post=newcenter:Translate(Radius,phead) @@ -70300,11 +77502,12 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) -local IsHerc=self:IsHercules(Unit) -if IsHerc then +local IsHerc=self:IsFixedWing(Unit) +local IsHook=self:IsHook(Unit) +if IsHerc and(not IsHook)then hoverunload=self:IsCorrectFlightParameters(Unit) end local grounded=not self:IsUnitInAir(Unit) @@ -70332,19 +77535,31 @@ 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 +if IsHerc or IsHook then Angle=(heading+180)%360 end +local offset=hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround +if IsHerc then offset=self.TroopUnloadDistGroundHerc or 25 end +if IsHook then +offset=self.TroopUnloadDistGroundHook or 15 +if hoverunload and self.TroopUnloadDistHoverHook then +offset=self.TroopUnloadDistHoverHook or 5 +end +end randomcoord:Translate(offset,Angle,nil,true) end local tempcount=0 +local ishook=self:IsHook(Unit) +if ishook then tempcount=self.ChinookTroopCircleRadius or 5 end 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 rad=2.5+(tempcount*2) local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type) end @@ -70361,7 +77576,7 @@ end end else self:_SendMessage("Troops have returned to base!",10,false,Group) -self:__TroopsRTB(1,Group,Unit) +self:__TroopsRTB(1,Group,Unit,zonename,zone) end local loaded={} loaded.Troopsloaded=0 @@ -70383,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -70391,7 +77609,9 @@ end end self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded +self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -70412,9 +77632,14 @@ return self end end end +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to drop cargo!",10,false,Group) +if not self.debug then return self end +end local hoverunload=self:IsCorrectHover(Unit) -local IsHerc=self:IsHercules(Unit) -if IsHerc then +local IsHerc=self:IsFixedWing(Unit) +local IsHook=self:IsHook(Unit) +if IsHerc and(not IsHook)then hoverunload=self:IsCorrectFlightParameters(Unit) end local grounded=not self:IsUnitInAir(Unit) @@ -70422,13 +77647,36 @@ 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 +local droppedCount={} +local neededMap={} 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 +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and type~=CTLD_CARGO.Enum.GCLOADABLE and(not cargo:WasDropped()or self.allowcratepickupagain)then self:_GetCrates(Group,Unit,cargo,1,true) cargo:SetWasDropped(true) cargo:SetHasMoved(true) +local cname=cargo:GetName()or"Unknown" +droppedCount[cname]=(droppedCount[cname]or 0)+1 +if not neededMap[cname]then +neededMap[cname]=cargo:GetCratesNeeded()or 1 +end +end +end +for cname,count in pairs(droppedCount)do +local needed=neededMap[cname]or 1 +if needed>1 then +local full=math.floor(count/needed) +local left=count%needed +if full>0 and left==0 then +self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group) +elseif full>0 and left>0 then +self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group) +else +self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) +end +else +self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) end end local loaded={} @@ -70443,10 +77691,16 @@ if type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS then table.insert(loaded.Cargo,_cargo) loaded.Troopsloaded=loaded.Troopsloaded+size end +if type==CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped()then +table.insert(loaded.Cargo,_cargo) +loaded.Cratesloaded=loaded.Cratesloaded+size +end end self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) +self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -70456,9 +77710,12 @@ end end return self end -function CTLD:_BuildCrates(Group,Unit,Engineering) +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end +function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") -if self:IsHercules(Unit)and self.enableHercules and not Engineering then +if self:IsFixedWing(Unit)and self.enableFixedWing 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) @@ -70472,11 +77729,19 @@ 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 baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end +local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -70486,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -70508,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -70536,17 +77812,54 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then -local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate()) +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) -self:__CratesBuildStarted(1,Group,Unit) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) else -self:_BuildObjectFromCrates(Group,Unit,build) +self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) +end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end end end end @@ -70560,25 +77873,36 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) -return self +self:_RefreshLoadCratesMenu(Group,Unit) +self:__CratesPacked(1,Group,Unit,_entry) +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end -return self +if not packedAny then +self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) +return false +end +return true 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 crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false @@ -70646,7 +77970,7 @@ if not Engineering then self:_SendMessage(string.format("No crates within %d met end return self end -function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) +function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,MultiDrop) self:T(self.lid.." _BuildObjectFromCrates") if Group and Group:IsAlive()or(RepairLocation and not Repair)then local name=Build.Name @@ -70667,6 +77991,9 @@ 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 MultiDrop and(not Repair)and canmove then +local randomcoord=zone:GetRandomCoordinate(35):GetVec2() +end if Repair then randomcoord=RepairLocation:GetVec2() end @@ -70676,10 +78003,14 @@ local alias=string.format("%s-%d",_template,math.random(1,100000)) if canmove then self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord) else self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord) end if Repair then @@ -70693,20 +78024,28 @@ self:T(self.lid.."Group KIA while building!") end return self end +function CTLD:_GetVehicleFormation() +local VehicleMoveFormation=self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE +if type(self.VehicleMoveFormation)=="table"then +VehicleMoveFormation=self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)] +end +return VehicleMoveFormation +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) +self:T({canmove=outcome,name=name,zone=zone,dist=distance,max=self.movetroopsdistance}) 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() +local formation=self:_GetVehicleFormation() Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) -Group:OptionROEOpenFirePossible() -Group:RouteToVec2(coordinate,5) +Group:OptionROEOpenFire() +Group:RouteGroundTo(zonecoord,25,formation) end return self end @@ -70727,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -70736,25 +78076,360 @@ end self:_CleanupTrackedCrates(destIDs) return self end +function CTLD:_DropAndBuild(Group,Unit) +if self.nobuildinloadzones then +if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)then +self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) +return self +end +end +self:_UnloadCrates(Group,Unit) +timer.scheduleFunction(function()self:_BuildCrates(Group,Unit,false,true)end,{},timer.getTime()+1) +end +function CTLD:_DropSingleAndBuild(Group,Unit,setIndex) +if self.nobuildinloadzones then +if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)then +self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) +return self +end +end +self:_UnloadSingleCrateSet(Group,Unit,setIndex) +timer.scheduleFunction(function()self:_BuildCrates(Group,Unit,false)end,{},timer.getTime()+1) +end +function CTLD:_PackAndLoad(Group,Unit) +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +if not self:_PackCratesNearby(Group,Unit)then +return self +end +timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) +return self +end +function CTLD:_PackAndRemove(Group,Unit) +if not self:_PackCratesNearby(Group,Unit)then +return self +end +timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) +return self +end +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self +end +function CTLD:_GetAllAndLoad(Group,Unit) +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) +end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for _name,_entry in pairs(self.subcats)do -subcatmenus[_name]=MENU_GROUP:New(_group,_name,cratesmenu) +for _,cargoObj in pairs(self.Cargo_Crates)do +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) 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) +for _,cargoObj in pairs(self.Cargo_Statics)do +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end -menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates,self,_group,_unit,entry) +else +for _,cargoObj in pairs(self.Cargo_Crates)do +addCrateMenuEntry(cargoObj,cratesmenu) 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) +for _,cargoObj in pairs(self.Cargo_Statics)do +addCrateMenuEntry(cargoObj,cratesmenu) 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) +if self.usesubcats==true then +local subcatmenus={} +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then +subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end -menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry) +return subcatmenus[catName] +end +for _,cargoObj in pairs(self.Cargo_Crates)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) 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) +for _,cargoObj in pairs(self.Cargo_Statics)do +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) end -menus[menucount]=MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates,self,_group,_unit,entry) +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) +end +end +else +for _,cargoObj in pairs(self.Cargo_Crates)do +if not cargoObj.DontShowInMenu then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,cargoObj) +end +end +for _,cargoObj in pairs(self.Cargo_Statics)do +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +local stock=cargoObj:GetStock() +if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end +MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,cargoObj) 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) +end +local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) +_group.MyLoadCratesMenu=loadCratesMenu +MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) +local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates) +topcrates.DropCratesMenu=dropCratesMenu 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() +MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates,self._BuildCrates,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Repair",topcrates,self._RepairCrates,self,_group,_unit):Refresh() +end +local removecratesmenu=MENU_GROUP:New(_group,"Remove crates",topcrates) +MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu,self._RemoveCratesNearby,self,_group,_unit) +if self.onestepmenu then +local mPack=MENU_GROUP:New(_group,"Pack crates",topcrates) +MENU_GROUP_COMMAND:New(_group,"Pack",mPack,self._PackCratesNearby,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Pack and Load",mPack,self._PackAndLoad,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Pack and Remove",mPack,self._PackAndRemove,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit) else -unloadmenu:Refresh() +MENU_GROUP_COMMAND:New(_group,"Pack crates",topcrates,self._PackCratesNearby,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit) +end +local uName=_unit:GetName() +local loadedData=self.Loaded_Cargo[uName] +if loadedData and loadedData.Cargo then +local cargoByName={} +for _,cgo in pairs(loadedData.Cargo)do +if cgo and(not cgo:WasDropped())then +local cname=cgo:GetName() +local cneeded=cgo:GetCratesNeeded() +cargoByName[cname]=cargoByName[cname]or{count=0,needed=cneeded} +cargoByName[cname].count=cargoByName[cname].count+1 end end -if self:IsHercules(_unit)then -local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu,self._ShowFlightParams,self,_group,_unit):Refresh() +for name,info in pairs(cargoByName)do +local line=string.format("Drop %s (%d/%d)",name,info.count,info.needed) +MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,self,_group,_unit,name) +end +end +end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end else -local hoverpars=MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu,self._ShowHoverParams,self,_group,_unit):Refresh() +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end +MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) +MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) +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) +MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,false) +local smokeself=MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu) +MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Red) +MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Blue) +MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Green) +MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.Orange) +MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself,self.SmokePositionNow,self,_unit,false,SMOKECOLOR.White) +MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu,self.SmokeZoneNearBy,self,_unit,true) +MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu,self.SmokePositionNow,self,_unit,true) +MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu,self.DropBeaconNow,self,_unit):Refresh() +if self:IsFixedWing(_unit)then +MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu,self._ShowFlightParams,self,_group,_unit):Refresh() +else +MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu,self._ShowHoverParams,self,_group,_unit):Refresh() end self.MenusDone[_unitName]=true +self:_RefreshLoadCratesMenu(_group,_unit) +self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -70930,6 +78768,666 @@ end end return self end +function CTLD:_RefreshLoadCratesMenu(Group,Unit) +if not Group.MyLoadCratesMenu then return end +Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end +local d=self.CrateDistance or 35 +local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) +if n==0 then +MENU_GROUP_COMMAND:New(Group,"No crates found! Rescan?",Group.MyLoadCratesMenu,function()self:_RefreshLoadCratesMenu(Group,Unit)end) +return +end +MENU_GROUP_COMMAND:New(Group,"Load ALL",Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit) +local cargoByName={} +for _,crate in pairs(nearby)do +local name=crate:GetName() +cargoByName[name]=cargoByName[name]or{} +table.insert(cargoByName[name],crate) +end +local lineIndex=1 +for cName,list in pairs(cargoByName)do +local needed=list[1]:GetCratesNeeded()or 1 +table.sort(list,function(a,b)return a:GetID()=needed then +label=string.format("%d. Load %s",lineIndex,cName) +i=i+needed +else +label=string.format("%d. Load %s (%d/%d)",lineIndex,cName,left,needed) +i=#list+1 +end +MENU_GROUP_COMMAND:New(Group,label,Group.MyLoadCratesMenu,self._LoadSingleCrateSet,self,Group,Unit,cName) +lineIndex=lineIndex+1 +end +end +end +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) +self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) +local grounded=not self:IsUnitInAir(Unit) +local hover=self:CanHoverLoad(Unit) +if not grounded and not hover then +self:_SendMessage("You must land or hover to load crates!",10,false,Group) +return self +end +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) +return self +end +local finddist=self.CrateDistance or 35 +local cratesNearby,number=self:_FindCratesNearby(Group,Unit,finddist,false,false) +if number==0 then +self:_SendMessage("No crates found in range!",10,false,Group) +return self +end +local matchingCrates={} +local needed=nil +for _,crateObj in pairs(cratesNearby)do +if crateObj:GetName()==cargoName then +needed=needed or crateObj:GetCratesNeeded() +table.insert(matchingCrates,crateObj) +end +end +if not needed then +self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10,false,Group) +return self +end +local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end +local unitName=Unit:GetName() +local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} +local capabilities=self:_GetUnitCapabilities(Unit) +local capacity=capabilities.cratelimit or 0 +if loadedData.Cratesloaded>=capacity then +self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress +return self +end +local spaceLeft=capacity-loadedData.Cratesloaded +local toLoad=math.min(found,needed,spaceLeft) +if toLoad<1 then +self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress +return self +end +local crateIDsLoaded={} +for i=1,toLoad do +local crate=matchingCrates[i] +crate:SetHasMoved(true) +crate:SetWasDropped(false) +table.insert(loadedData.Cargo,crate) +loadedData.Cratesloaded=loadedData.Cratesloaded+1 +local stObj=crate:GetPositionable() +if stObj and stObj:IsAlive()then +stObj:Destroy(false) +end +table.insert(crateIDsLoaded,crate:GetID()) +end +self.Loaded_Cargo[unitName]=loadedData +self:_UpdateUnitCargoMass(Unit) +local newSpawned={} +for _,cObj in ipairs(self.Spawned_Cargo)do +local keep=true +for i=1,toLoad do +if matchingCrates[i]and cObj:GetID()==matchingCrates[i]:GetID()then +keep=false +break +end +end +if keep then +table.insert(newSpawned,cObj) +end +end +self.Spawned_Cargo=newSpawned +local loadedHere=toLoad +if details or(not batch)then +if loadedHere=capacity then +self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) +else +local fullSets=math.floor(loadedHere/needed) +local leftover=loadedHere%needed +if needed>1 then +if fullSets>0 and leftover==0 then +self:_SendMessage(string.format("Loaded %d %s.",fullSets,cargoName),10,false,Group) +elseif fullSets>0 and leftover>0 then +self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).",fullSets,cargoName,leftover),10,false,Group) +else +self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.",loadedHere,needed,cargoName),15,false,Group) +end +else +self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) +end +end +end +self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end +return self +end +function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) +self:T(self.lid.." _UnloadSingleCrateSet") +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 +if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then +self:_SendMessage("You need to open the door(s) to drop cargo!",10,false,Group) +if not self.debug then return self end +end +local unitName=Unit:GetName() +if not self.CrateGroupList or not self.CrateGroupList[unitName]then +self:_SendMessage("No crate groups found for this unit!",10,false,Group) +if not self.debug then return self end +return self +end +local chunk=self.CrateGroupList[unitName][setIndex] +if not chunk then +self:_SendMessage("No crate set found or index invalid!",10,false,Group) +if not self.debug then return self end +return self +end +if#chunk==0 then +self:_SendMessage("No crate found in that set!",10,false,Group) +if not self.debug then return self end +return self +end +local grounded=not self:IsUnitInAir(Unit) +local hoverunload=self:IsCorrectHover(Unit) +local isHerc=self:IsFixedWing(Unit) +local isHook=self:IsHook(Unit) +if isHerc and not isHook then +hoverunload=self:IsCorrectFlightParameters(Unit) +end +if not grounded and not hoverunload then +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 +if not self.debug then return self end +return self +end +local crateObj=chunk[1] +if not crateObj then +self:_SendMessage("No crate found in that set!",10,false,Group) +if not self.debug then return self end +return self +end +local needed=crateObj:GetCratesNeeded()or 1 +self:_GetCrates(Group,Unit,crateObj,#chunk,true) +for _,cObj in ipairs(chunk)do +cObj:SetWasDropped(true) +cObj:SetHasMoved(true) +end +local cname=crateObj:GetName()or"Unknown" +local count=#chunk +if needed>1 then +if count==needed then +self:_SendMessage(string.format("Dropped %d %s.",1,cname),10,false,Group) +else +self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) +end +else +self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) +end +local loadedData=self.Loaded_Cargo[unitName] +if loadedData and loadedData.Cargo then +local newList={} +local newCratesCount=0 +for _,cObj in ipairs(loadedData.Cargo)do +if not cObj:WasDropped()then +table.insert(newList,cObj) +local ct=cObj:GetType() +if ct~=CTLD_CARGO.Enum.TROOPS and ct~=CTLD_CARGO.Enum.ENGINEERS then +newCratesCount=newCratesCount+1 +end +end +end +loadedData.Cargo=newList +loadedData.Cratesloaded=newCratesCount +self.Loaded_Cargo[unitName]=loadedData +end +self:_UpdateUnitCargoMass(Unit) +self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) +return self +end +function CTLD:_RefreshDropCratesMenu(Group,Unit) +if not Group.CTLDTopmenu then return end +local topCrates=Group.MyTopCratesMenu +if not topCrates then return end +if topCrates.DropCratesMenu then +topCrates.DropCratesMenu:RemoveSubMenus() +else +topCrates.DropCratesMenu=MENU_GROUP:New(Group,"Drop Crates",topCrates) +end +local dropCratesMenu=topCrates.DropCratesMenu +local loadedData=self.Loaded_Cargo[Unit:GetName()] +if not loadedData or not loadedData.Cargo then +MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function()end) +return +end +local cargoByName={} +local dropableCrates=0 +for _,cObj in ipairs(loadedData.Cargo)do +if cObj and not cObj:WasDropped()then +local cType=cObj:GetType() +if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then +local name=cObj:GetName()or"Unknown" +cargoByName[name]=cargoByName[name]or{} +table.insert(cargoByName[name],cObj) +dropableCrates=dropableCrates+1 +end +end +end +if dropableCrates==0 then +MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function()end) +return +end +if not self.onestepmenu then +MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) +self.CrateGroupList=self.CrateGroupList or{} +self.CrateGroupList[Unit:GetName()]={} +local lineIndex=1 +for cName,list in pairs(cargoByName)do +local needed=list[1]:GetCratesNeeded()or 1 +table.sort(list,function(a,b)return a:GetID()0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do +local chunk={} +for n=i,i+needed-1 do table.insert(chunk,list[n])end +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +i=i+needed +end +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) +lineIndex=lineIndex+1 +end +end +else +local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",mAll,self._UnloadCrates,self,Group,Unit) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit) +end +self.CrateGroupList=self.CrateGroupList or{} +self.CrateGroupList[Unit:GetName()]={} +local lineIndex=1 +for cName,list in pairs(cargoByName)do +local needed=list[1]:GetCratesNeeded()or 1 +table.sort(list,function(a,b)return a:GetID()0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do +local chunk={} +for n=i,i+needed-1 do table.insert(chunk,list[n])end +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +i=i+needed +end +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end +table.insert(self.CrateGroupList[Unit:GetName()],chunk) +local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) +lineIndex=lineIndex+1 +end +end +end +end +function CTLD:_UnloadSingleTroopByID(Group,Unit,chunkID) +self:T(self.lid.." _UnloadSingleTroopByID chunkID="..tostring(chunkID)) +local droppingatbase=false +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=self.returntroopstobase +end +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 hoverunload=self:IsCorrectHover(Unit) +local isHerc=self:IsFixedWing(Unit) +local isHook=self:IsHook(Unit) +if isHerc and not isHook 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 +if not self.TroopsIDToChunk or not self.TroopsIDToChunk[chunkID]then +self:_SendMessage(string.format("No troop cargo chunk found for ID %d!",chunkID),10,false,Group) +if not self.debug then return self end +return self +end +local chunk=self.TroopsIDToChunk[chunkID] +if not chunk or#chunk==0 then +self:_SendMessage(string.format("Troop chunk is empty for ID %d!",chunkID),10,false,Group) +if not self.debug then return self end +return self +end +local foundCargo=chunk[1] +if not foundCargo then +self:_SendMessage(string.format("No troop cargo at chunk %d!",chunkID),10,false,Group) +if not self.debug then return self end +return self +end +local cType=foundCargo:GetType() +local name=foundCargo:GetName()or"none" +local tmpl=foundCargo:GetTemplates()or{} +local zoneradius=self.troopdropzoneradius or 100 +local factor=1 +if isHerc then +factor=foundCargo: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 grounded or hoverunload then +randomcoord=Group:GetCoordinate() +local Angle=(heading+270)%360 +if isHerc or isHook then +Angle=(heading+180)%360 +end +local offset=hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround +if isHerc then +offset=self.TroopUnloadDistGroundHerc or 25 +end +if isHook then +offset=self.TroopUnloadDistGroundHook or 15 +if hoverunload and self.TroopUnloadDistHoverHook then +offset=self.TroopUnloadDistHoverHook or 5 +end +end +randomcoord:Translate(offset,Angle,nil,true) +end +local tempcount=0 +if isHook then +tempcount=self.ChinookTroopCircleRadius or 5 +end +for _,_template in pairs(tmpl)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*2) +local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) +self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) +:InitDelayOff() +:InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) +:SpawnFromVec2(randomcoord:GetVec2()) +self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],cType) +end +foundCargo:SetWasDropped(true) +if cType==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 +table.remove(chunk,1) +if#chunk==0 then +self.TroopsIDToChunk[chunkID]=nil +end +else +self:_SendMessage("Troops have returned to base!",10,false,Group) +self:__TroopsRTB(1,Group,Unit,zonename,zone) +if self.TroopsIDToChunk and self.TroopsIDToChunk[chunkID]then +local chunk=self.TroopsIDToChunk[chunkID] +if#chunk>0 then +local firstObj=chunk[1] +local cName=firstObj:GetName() +local gentroops=self.Cargo_Troops +for _id,_troop in pairs(gentroops)do +if _troop.Name==cName then +local st=_troop:GetStock() +if st and tonumber(st)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end +end +end +firstObj:SetWasDropped(true) +table.remove(chunk,1) +if#chunk==0 then +self.TroopsIDToChunk[chunkID]=nil +end +end +end +end +local cargoList=self.Loaded_Cargo[unitName].Cargo +for i=#cargoList,1,-1 do +if cargoList[i]:WasDropped()then +table.remove(cargoList,i) +end +end +local troopsLoaded=0 +local cratesLoaded=0 +for _,cargo in ipairs(cargoList)do +local cT=cargo:GetType() +if cT==CTLD_CARGO.Enum.TROOPS or cT==CTLD_CARGO.Enum.ENGINEERS then +troopsLoaded=troopsLoaded+1 +else +cratesLoaded=cratesLoaded+1 +end +end +self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded +self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded +self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) +else +local isHerc=self:IsFixedWing(Unit) +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:_RefreshDropTroopsMenu(Group,Unit) +local theGroup=Group +local theUnit=Unit +if not theGroup.CTLDTopmenu then return end +local topTroops=theGroup.MyTopTroopsMenu +if not topTroops then return end +if topTroops.DropTroopsMenu then +topTroops.DropTroopsMenu:Remove() +end +local dropTroopsMenu=MENU_GROUP:New(theGroup,"Drop Troops",topTroops) +topTroops.DropTroopsMenu=dropTroopsMenu +MENU_GROUP_COMMAND:New(theGroup,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,theGroup,theUnit) +local loadedData=self.Loaded_Cargo[theUnit:GetName()] +if not loadedData or not loadedData.Cargo then return end +local troopsByName={} +for _,cargoObj in ipairs(loadedData.Cargo)do +if cargoObj +and(cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) +and not cargoObj:WasDropped() +then +local name=cargoObj:GetName()or"Unknown" +troopsByName[name]=troopsByName[name]or{} +table.insert(troopsByName[name],cargoObj) +end +end +self.TroopsIDToChunk=self.TroopsIDToChunk or{} +for tName,objList in pairs(troopsByName)do +table.sort(objList,function(a,b)return a:GetID()distance then +self:T("Zone Active: "..tostring(active)) +if(zone:IsVec2InZone(unitVec2)or Zonetype==CTLD.CargoZoneType.MOVE)and active==true and distance0 and not Troopstable[genname]then +Troopstable[genname]={ +Stock0=generic:GetStock0(), +Stock=generic:GetStock(), +StockR=generic:GetRelativeStock(), +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=generic:GetStock(), +} +if Restock==true then +Troopstable[genname].GenericCargo=generic +end +end +end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end +for _id,_cargo in pairs(self.Cargo_Troops)do +local generic=_cargo +local genname=generic:GetName() +if generic and generic:GetStock0()>0 and not Troopstable[genname]then +Troopstable[genname]={ +Stock0=generic:GetStock0(), +Stock=generic:GetStock(), +StockR=generic:GetRelativeStock(), +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=generic:GetStock(), +} +if Restock==true then +Troopstable[genname].GenericCargo=generic +end +end +end +for _index,_group in pairs(self.DroppedTroops)do +if _group and _group:IsAlive()then +self:T("Looking at ".._group:GetName().." in the field") +local generic=self:GetGenericCargoObjectFromGroupName(_group:GetName()) +if generic then +local genname=generic:GetName() +self:T("Found Generic "..genname.." in the field. Adding.") +if generic:GetStock0()>0 then +Troopstable[genname].Infield=Troopstable[genname].Infield+1 +Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo +end +else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else +self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) +end +end +end +end +for _unitname,_loaded in pairs(self.Loaded_Cargo)do +local _unit=UNIT:FindByName(_unitname) +if _unit and _unit:IsAlive()then +local unitname=_unit:GetName() +local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} +for _,_cgo in pairs(loadedcargo)do +local cargo=_cgo +local type=cargo.CargoType +local gname=cargo:GetName() +local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then +if gcargo and gcargo:GetStock0()>0 then +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) +if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then +Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 +end +if(type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then +local counting=gcargo.CratesNeeded +local added=1 +if counting>1 then +added=added/counting +end +Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+added +end +Troopstable[gname].Sum=Troopstable[gname].Infield+Troopstable[gname].Stock+Troopstable[gname].Inhelo+Troopstable[gname].CratesInfield +end +end +end +end +end +if self.Spawned_Cargo then +for i=#self.Spawned_Cargo,1,-1 do +local cargo=self.Spawned_Cargo[i] +if cargo and cargo:GetPositionable()and cargo:GetPositionable():IsAlive()then +local genname=cargo:GetName() +local gcargo=self:_FindCratesCargoObject(genname) +if Troopstable[genname]and gcargo and gcargo:GetStock0()>0 then +local needed=gcargo.CratesNeeded or 1 +local added=1 +if needed>1 then +added=added/needed +end +Troopstable[genname].CratesInfield=Troopstable[genname].CratesInfield+added +Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock ++Troopstable[genname].Inhelo+Troopstable[genname].CratesInfield +end +end +end +for i=#self.Spawned_Cargo,1,-1 do +local cargo=self.Spawned_Cargo[i] +if cargo and cargo:GetPositionable()and cargo:GetPositionable():IsAlive()then +local genname=cargo:GetName() +if Troopstable[genname]then +if Troopstable[genname].Inhelo==0 and Troopstable[genname].CratesInfield<1 then +Troopstable[genname].CratesInfield=0 +Troopstable[genname].Sum=Troopstable[genname].Stock +cargo:GetPositionable():Destroy(false) +table.remove(self.Spawned_Cargo,i) +local leftover=Troopstable[genname].Stock0-(Troopstable[genname].Infield+Troopstable[genname].Inhelo+Troopstable[genname].CratesInfield) +if leftover0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_dist0 and _closest.distanceself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end +end +for _,_mashes in pairs(MashSets)do +for _,_mashUnit in pairs(_mashes or{})do +local _mashcoord +if _mashUnit and(not _mashUnit:IsInstanceOf("ZONE_BASE"))and _mashUnit:IsAlive()then +_mashcoord=_mashUnit:GetCoordinate() +elseif _mashUnit and _mashUnit:IsInstanceOf("ZONE_BASE")then +_mashcoord=_mashUnit:GetCoordinate() end -for _,_mashUnit in pairs(_mashes)do -if _mashUnit and _mashUnit:IsAlive()then -local _mashcoord=_mashUnit:GetCoordinate() _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance +MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance +return _shortestDistance,MashName,Coordinate else return-1 end @@ -73979,7 +83221,7 @@ local allheligroupset=self.allheligroupset local _allHeliGroups=allheligroupset:GetSetObjects() local _UnitList={} for _key,_group in pairs(_allHeliGroups)do -local _unit=_group:GetUnit(1) +local _unit=_group:GetFirstUnitAlive() if _unit then if _unit:IsAlive()and _unit:IsPlayer()then local unitName=_unit:GetName() @@ -74001,7 +83243,15 @@ local _rootPath=MENU_GROUP:New(_group,menuname) local _rootMenu1=MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath,self._DisplayActiveSAR,self,_unitName) local _rootMenu2=MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath,self._CheckOnboard,self,_unitName) local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath,self._SignalFlare,self,_unitName) -local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName):Refresh() +local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) +if self.AllowIRStrobe then +local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) +else +_rootMenu4:Refresh() +end end end end @@ -74013,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -74064,8 +83315,9 @@ if clock>12 then clock=clock-12 end end return clock end -function CSAR:_AddBeaconToGroup(_group,_freq) +function CSAR:_AddBeaconToGroup(_group,_freq,BeaconName) self:T(self.lid.." _AddBeaconToGroup") +if self.CreateRadioBeacons==false then return end local _group=_group if _group==nil then for _i,_current in ipairs(self.UsedVHFFrequencies)do @@ -74081,16 +83333,17 @@ 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)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end return self end function CSAR:_RefreshRadioBeacons() self:T(self.lid.." _RefreshRadioBeacons") +if self.CreateRadioBeacons==false then return end if self:_CountActiveDownedPilots()>0 then local PilotTable=self.downedPilots for _,_pilot in pairs(PilotTable)do @@ -74098,8 +83351,12 @@ self:T({_pilot.name}) local pilot=_pilot local group=pilot.group local frequency=pilot.frequency or 0 +local bname=pilot.BeaconName or pilot.name..math.random(1,100000) if group and group:IsAlive()and frequency>0 then -self:_AddBeaconToGroup(group,frequency) +else +if frequency>0 then +trigger.action.stopRadioTransmission(bname) +end end end end @@ -74122,11 +83379,30 @@ local limit=self.maxdownedpilots local islimited=self.limitmaxdownedpilots local count=self:_CountActiveDownedPilots() if islimited and(count>=limit)then +if self.useFIFOLimitReplacement then +local oldIndex=-1 +local oldDownedPilot=nil +for _index,_downedpilot in pairs(self.downedPilots)do +oldIndex=_index +oldDownedPilot=_downedpilot +break +end +if oldDownedPilot then +oldDownedPilot.group:Destroy(false) +oldDownedPilot.alive=false +self:_CheckDownedPilotTable() +return false +end +end return true else return false end end +function CSAR:SetOwnSetPilotGroups(Set) +self.UserSetGroup=Set +return self +end function CSAR:onafterStart(From,Event,To) self:T({From,Event,To}) self:I(self.lid.."Started ("..self.version..")") @@ -74137,7 +83413,9 @@ 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 +if self.UserSetGroup then +self.allheligroupset=self.UserSetGroup +elseif self.allowbronco then local prefixes=self.csarPrefix or{} self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart() elseif self.useprefix then @@ -74147,6 +83425,8 @@ else self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end self.mash=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() +self.staticmashes=SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() +self.zonemashes=SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart() if not self.coordinate then local csarhq=self.mash:GetRandom() if csarhq then @@ -74365,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -74374,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -74444,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -74467,7 +83748,7 @@ 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) +self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,false,description,nil) end return self end @@ -74488,7 +83769,7 @@ capFormation=nil, capOptionVaryStartTime=nil, capOptionVaryEndTime=nil, } -AIRWING.version="0.9.5" +AIRWING.version="0.9.7" function AIRWING:New(warehousename,airwingname) local self=BASE:Inherit(self,LEGION:New(warehousename,airwingname)) if not self then @@ -74520,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -74782,19 +84080,29 @@ point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed) return text end function AIRWING:UpdatePatrolPointMarker(point) -if self.markpoints then +if self and 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) +if point.IsZonePoint and point.IsZonePoint==true and point.patrolzone then +local Coordinate=point.patrolzone:GetCoordinate() +point.marker:UpdateCoordinate(Coordinate) +point.marker:UpdateText(text,1.5) +else 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 +function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) local patrolpoint={} patrolpoint.type=Type or"Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10,15)),math.random(360)) +if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then +patrolpoint.IsZonePoint=true +patrolpoint.patrolzone=Coordinate +patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate() +else +patrolpoint.IsZonePoint=false +end patrolpoint.heading=Heading or math.random(360) patrolpoint.leg=LegLength or 15 patrolpoint.altitude=Altitude or math.random(10,20)*1000 @@ -74803,7 +84111,7 @@ patrolpoint.noccupied=0 patrolpoint.refuelsystem=RefuelSystem if self.markpoints then patrolpoint.marker=MARKER:New(Coordinate,"New Patrol Point"):ToAll() -AIRWING.UpdatePatrolPointMarker(patrolpoint) +self:UpdatePatrolPointMarker(patrolpoint) end return patrolpoint end @@ -74856,6 +84164,26 @@ function AIRWING:SetTakeoffAir() self:SetTakeoffType("Air") return self end +function AIRWING:SetLandingStraightIn() +self.OptionLandingStraightIn=true +return self +end +function AIRWING:SetLandingForcePair() +self.OptionLandingForcePair=true +return self +end +function AIRWING:SetLandingRestrictPair() +self.OptionLandingRestrictPair=true +return self +end +function AIRWING:SetLandingOverheadBreak() +self.OptionLandingOverheadBreak=true +return self +end +function AIRWING:SetOptionPreferVerticalLanding() +self.OptionPreferVerticalLanding=true +return self +end function AIRWING:SetDespawnAfterLanding(Switch) if Switch then self.despawnAfterLanding=Switch @@ -74884,6 +84212,7 @@ self:CheckTANKER() self:CheckAWACS() self:CheckRescuhelo() self:CheckRECON() +self:_TacticalOverview() self:CheckTransportQueue() self:CheckMissionQueue() if self.verbose>=1 then @@ -74926,6 +84255,9 @@ if PatrolPoints and#PatrolPoints>0 then table.sort(PatrolPoints,sort) for _,_patrolpoint in pairs(PatrolPoints)do local patrolpoint=_patrolpoint +if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint==true and patrolpoint.patrolzone then +patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate() +end if(RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem)or RefuelSystem==nil or patrolpoint.refuelsystem==nil then return patrolpoint end @@ -74956,7 +84288,7 @@ missionCAP:SetTime(ClockStart) end missionCAP.patroldata=patrol patrol.noccupied=patrol.noccupied+1 -if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(missionCAP) end return self @@ -74982,7 +84314,7 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(missionRECON) end return self @@ -75006,7 +84338,7 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(mission) end for i=1,self.nflightsTANKERprobe-Nprob do @@ -75015,7 +84347,7 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(mission) end return self @@ -75034,19 +84366,21 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(mission) end return self end function AIRWING:CheckRescuhelo() local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) +if self.airbase then 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 +end return self end function AIRWING:GetTankerForFlight(flightgroup) @@ -75089,6 +84423,18 @@ self:T(self.lid..string.format("Group %s on %s mission %s",FlightGroup:GetName() if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission) end +if self.OptionLandingForcePair then +FlightGroup:SetOptionLandingForcePair() +elseif self.OptionLandingOverheadBreak then +FlightGroup:SetOptionLandingOverheadBreak() +elseif self.OptionLandingRestrictPair then +FlightGroup:SetOptionLandingRestrictPair() +elseif self.OptionLandingStraightIn then +FlightGroup:SetOptionLandingStraightIn() +end +if self.OptionPreferVerticalLanding then +FlightGroup:SetOptionPreferVertical() +end end function AIRWING:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads) if MissionTypes then @@ -75169,7 +84515,7 @@ ClassName="ARMYGROUP", formationPerma=nil, engage={}, } -ARMYGROUP.version="1.0.1" +ARMYGROUP.version="1.0.3" function ARMYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -75208,6 +84554,7 @@ self:_InitGroup() self:HandleEvent(EVENTS.Birth,self.OnEventBirth) self:HandleEvent(EVENTS.Dead,self.OnEventDead) self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) +self:HandleEvent(EVENTS.UnitLost,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) @@ -75400,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -75741,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -75855,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -75963,31 +85310,12 @@ 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 +if not self.tacanDefault then self:SetDefaultTACAN(nil,nil,nil,nil,true) +end +if not self.tacan then self.tacan=UTILS.DeepCopy(self.tacanDefault) +end local units=self.group:GetUnits() local dcsgroup=Group.getByName(self.groupname) local size0=dcsgroup:getInitialSize() @@ -76111,6 +85439,7 @@ conditionFailure={}, conditionPush={}, conditionSuccessSet=false, conditionFailureSet=false, +repeatDelay=1, } _AUFTRAGSNR=0 AUFTRAG.Type={ @@ -76151,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -76159,6 +85489,7 @@ REARMING="Rearming", CAPTUREZONE="Capture Zone", NOTHING="Nothing", PATROLRACETRACK="Patrol Racetrack", +STRAFING="Strafing", } AUFTRAG.SpecialTask={ FORMATION="Formation", @@ -76173,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -76220,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -76301,11 +85633,13 @@ mission.categories={AUFTRAG.Category.HELICOPTER} mission.DCStask=mission:GetDCSMissionTask() return mission end -function AUFTRAG:NewLANDATCOORDINATE(Coordinate,OuterRadius,InnerRadius,Time,Speed,MissionAlt) +function AUFTRAG:NewLANDATCOORDINATE(Coordinate,OuterRadius,InnerRadius,Time,Speed,MissionAlt,CombatLanding,DirectionAfterLand) local mission=AUFTRAG:New(AUFTRAG.Type.LANDATCOORDINATE) mission:_TargetFromObject(Coordinate) mission.stayTime=Time or 300 mission.stayAt=Coordinate +mission.combatLand=CombatLanding +mission.directionAfter=DirectionAfterLand self:SetMissionSpeed(Speed or 150) self:SetMissionAltitude(MissionAlt or 1000) if OuterRadius then @@ -76407,7 +85741,12 @@ 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) +local mission +if Leg==0 then +mission=AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed) +else +mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) +end mission.type=AUFTRAG.Type.TANKER mission:_SetLogID() mission.refuelSystem=RefuelSystem @@ -76446,7 +85785,7 @@ 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.engageZone=ZoneCAP or Coordinate mission.engageTargetTypes=TargetTypes or{"Air"} mission.missionTask=ENUMS.MissionTask.CAP mission.optionROE=ENUMS.ROE.OpenFire @@ -76575,10 +85914,27 @@ mission.categories={AUFTRAG.Category.AIRCRAFT} mission.DCStask=mission:GetDCSMissionTask() return mission end -function AUFTRAG:NewSTRIKE(Target,Altitude) +function AUFTRAG:NewSEADInZone(TargetZone,Altitude,TargetTypes,Duration) +local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) +mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) +mission.engageZone=TargetZone +mission.engageTargetTypes=TargetTypes or{"Air Defence"} +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() +mission:SetDuration(Duration or 1800) +return mission +end +function AUFTRAG:NewSTRIKE(Target,Altitude,EngageWeaponType) local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE) mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) mission.missionTask=ENUMS.MissionTask.GROUNDATTACK @@ -76590,10 +85946,10 @@ mission.categories={AUFTRAG.Category.AIRCRAFT} mission.DCStask=mission:GetDCSMissionTask() return mission end -function AUFTRAG:NewBOMBING(Target,Altitude) +function AUFTRAG:NewBOMBING(Target,Altitude,EngageWeaponType,Divebomb) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) mission:_TargetFromObject(Target) -mission.engageWeaponType=ENUMS.WeaponFlag.Auto +mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) mission.missionTask=ENUMS.MissionTask.GROUNDATTACK @@ -76601,6 +85957,24 @@ mission.missionAltitude=mission.engageAltitude*0.8 mission.missionFraction=0.5 mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.NoReaction +mission.optionDivebomb=Divebomb or nil +mission.dTevaluate=5*60 +mission.categories={AUFTRAG.Category.AIRCRAFT} +mission.DCStask=mission:GetDCSMissionTask() +return mission +end +function AUFTRAG:NewSTRAFING(Target,Altitude,Length) +local mission=AUFTRAG:New(AUFTRAG.Type.STRAFING) +mission:_TargetFromObject(Target) +mission.engageWeaponType=805337088 +mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL +mission.engageAltitude=UTILS.FeetToMeters(Altitude or 1000) +mission.engageLength=Length +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() @@ -76631,7 +86005,7 @@ 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.engageLength=CarpetLength or 500 mission.engageAsGroup=false mission.engageDirection=nil mission.missionTask=ENUMS.MissionTask.GROUNDATTACK @@ -76687,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -76758,6 +86132,7 @@ mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.optionROE=ENUMS.ROE.OpenFire mission.optionAlarm=0 mission.missionFraction=0.0 +mission.missionWaypointRadius=0.0 mission.dTevaluate=8*60 mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL} mission.DCStask=mission:GetDCSMissionTask() @@ -76798,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -76806,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -76816,7 +86192,7 @@ local params={} params.formation=Formation or"Off Road" params.zone=mission:GetObjective() params.altitude=mission.missionAltitude -params.speed=mission.missionSpeed +params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil mission.DCStask.params=params return mission end @@ -76836,10 +86212,24 @@ 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.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -76975,31 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) +elseif MissionType==AUFTRAG.Type.STRAFING then +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -77060,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -77164,6 +86558,10 @@ function AUFTRAG:SetRepeat(Nrepeat) self.Nrepeat=Nrepeat or 0 return self end +function AUFTRAG:SetRepeatDelay(RepeatDelay) +self.repeatDelay=RepeatDelay +return self +end function AUFTRAG:SetRepeatOnFailure(Nrepeat) self.NrepeatFailure=Nrepeat or 0 return self @@ -77619,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -77823,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -77978,6 +87385,11 @@ groupdata.waypointcoordinate=coordinate end return self end +function AUFTRAG:SetIngressCoordinate(coordinate) +self.missionIngressCoord=coordinate +self.missionIngressCoordAlt=UTILS.MetersToFeet(coordinate.y)or 10000 +return self +end function AUFTRAG:GetGroupWaypointCoordinate(opsgroup) local groupdata=self:GetGroupData(opsgroup) if groupdata then @@ -78026,6 +87438,7 @@ return groupdata.waypointEgressUID end end function AUFTRAG:CheckGroupsDone() +local fsmState=self:GetState() for groupname,data in pairs(self.groupdata)do local groupdata=data if groupdata then @@ -78063,10 +87476,20 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +return false +end return true end function AUFTRAG:OnEventUnitLost(EventData) @@ -78207,7 +87630,7 @@ if repeatme then self.repeatedSuccess=self.repeatedSuccess+1 local N=math.max(self.NrepeatSuccess,self.Nrepeat) self:T(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!",self.repeated+1,N)) -self:Repeat() +self:__Repeat(self.repeatDelay) else self:T(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1)) self:Stop() @@ -78227,7 +87650,7 @@ if repeatme then self.repeatedFailure=self.repeatedFailure+1 local N=math.max(self.NrepeatFailure,self.Nrepeat) self:T(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!",self.repeated+1,N)) -self:Repeat() +self:__Repeat(self.repeatDelay) else self:T(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1)) self:Stop() @@ -78329,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -78510,18 +87933,52 @@ function AUFTRAG:SetMissionWaypointRandomization(Radius) self.missionWaypointRadius=Radius return self end -function AUFTRAG:SetMissionEgressCoord(Coordinate,Altitude) +function AUFTRAG:SetMissionEgressCoord(Coordinate,Altitude,Speed) if Coordinate:IsInstanceOf("ZONE_BASE")then Coordinate=Coordinate:GetCoordinate() end self.missionEgressCoord=Coordinate if Altitude then self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) +self.missionEgressCoordAlt=UTILS.FeetToMeters(Altitude) end +self.missionEgressCoordSpeed=Speed and Speed or nil +return self +end +function AUFTRAG:SetMissionIngressCoord(Coordinate,Altitude,Speed) +if Coordinate:IsInstanceOf("ZONE_BASE")then +Coordinate=Coordinate:GetCoordinate() +end +self.missionIngressCoord=Coordinate +if Altitude then +self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude) +self.missionIngressCoordAlt=UTILS.FeetToMeters(Altitude or 10000) +end +self.missionIngressCoordSpeed=Speed and Speed or nil +return self +end +function AUFTRAG:SetMissionHoldingCoord(Coordinate,Altitude,Speed,Duration) +if Coordinate:IsInstanceOf("ZONE_BASE")then +Coordinate=Coordinate:GetCoordinate() +end +self.missionHoldingCoord=Coordinate +self.missionHoldingDuration=Duration or 900 +if Altitude then +self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude) +self.missionHoldingCoordAlt=UTILS.FeetToMeters(Altitude or 10000) +end +self.missionHoldingCoordSpeed=Speed and Speed or nil +return self end function AUFTRAG:GetMissionEgressCoord() return self.missionEgressCoord end +function AUFTRAG:GetMissionIngressCoord() +return self.missionIngressCoord +end +function AUFTRAG:GetMissionHoldingCoord() +return self.missionHoldingCoord +end function AUFTRAG:_GetMissionWaypointCoordSet() if self.missionWaypointCoord then local coord=self.missionWaypointCoord @@ -78539,8 +87996,20 @@ coord.y=self.missionAltitude end return coord end -local waypointcoord=COORDINATE:New(0,0,0) local coord=group:GetCoordinate() +if self.missionHoldingCoord then +coord=self.missionHoldingCoord +if self.missionHoldingCoorddAlt then +coord:SetAltitude(self.missionHoldingCoordAlt,true) +end +end +if self.missionIngressCoord then +coord=self.missionIngressCoord +if self.missionIngressCoordAlt then +coord:SetAltitude(self.missionIngressCoordAlt,true) +end +end +local waypointcoord=COORDINATE:New(0,0,0) if coord then waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(),self.missionFraction) else @@ -78638,16 +88107,29 @@ 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) +local coords=self.engageTarget:GetCoordinates() +for _,coord in pairs(coords)do +local DCStask=CONTROLLABLE.TaskBombing(nil,coord:GetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.optionDivebomb) +table.insert(DCStasks,DCStask) +end +elseif self.type==AUFTRAG.Type.STRAFING then +local DCStask=CONTROLLABLE.TaskStrafing(nil,self:GetTargetVec2(),self.engageQuantity,self.engageLength,self.engageWeaponType,self.engageWeaponExpend,self.engageDirection,self.engageAsGroup) 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) +local DCStask=CONTROLLABLE.TaskCarpetBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.engageLength) 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) +local Vec2=self.engageZone:GetVec2() +local Radius +if self.engageZone:IsInstanceOf("COORDINATE")then +Radius=UTILS.NMToMeters(20) +else +Radius=self.engageZone:GetRadius() +end +local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,Vec2,Radius,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) @@ -78667,7 +88149,7 @@ DCStask.id=AUFTRAG.SpecialTask.PATROLZONE local param={} param.zone=self:GetObjective() param.altitude=self.missionAltitude -param.speed=self.missionSpeed +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) local DCSenroute=CONTROLLABLE.EnRouteTaskFAC(self,self.facFreq,self.facModu) @@ -78694,15 +88176,34 @@ DCStask.id=AUFTRAG.SpecialTask.RECON local param={} param.target=self.engageTarget param.altitude=self.missionAltitude -param.speed=self.missionSpeed +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil param.lastindex=nil DCStask.params=param table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.SEAD then +if self.engageZone then +self.engageZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +local ScanUnitSet=self.engageZone:GetScannedSetUnit() +local SeadUnitSet=SET_UNIT:New() +for _,_unit in pairs(ScanUnitSet.Set)do +local unit=_unit +if unit and unit:IsAlive()and unit:HasSEAD()then +self:T("Adding UNIT for SEAD: "..unit:GetName()) +local task=CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318) +table.insert(DCStasks,task) +SeadUnitSet:AddUnit(unit) +end +end +self.engageTarget=TARGET:New(SeadUnitSet) +else self:_GetDCSAttackTask(self.engageTarget,DCStasks) +end 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) +local coords=self.engageTarget:GetCoordinates() +for _,coord in pairs(coords)do +local DCStask=CONTROLLABLE.TaskAttackMapObject(nil,coord:GetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType) table.insert(DCStasks,DCStask) +end elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then local DCStask=CONTROLLABLE.EnRouteTaskTanker(nil) table.insert(self.enrouteTasks,DCStask) @@ -78766,7 +88267,7 @@ DCStask.id=AUFTRAG.SpecialTask.PATROLZONE local param={} param.zone=self:GetObjective() param.altitude=self.missionAltitude -param.speed=self.missionSpeed +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.CAPTUREZONE then @@ -78781,7 +88282,7 @@ DCStask.id=AUFTRAG.SpecialTask.PATROLZONE local param={} param.zone=self:GetObjective() param.altitude=self.missionAltitude -param.speed=self.missionSpeed +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.GROUNDATTACK then @@ -78790,7 +88291,16 @@ DCStask.id=AUFTRAG.SpecialTask.GROUNDATTACK local param={} param.target=self:GetTargetData() param.action="Wedge" -param.speed=self.missionSpeed +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +DCStask.params=param +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 DCStask.params=param table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then @@ -78852,7 +88362,7 @@ 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) +local DCStask=CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime,self.combatLand,self.directionAfter) table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.ONGUARD or self.type==AUFTRAG.Type.ARMOREDGUARD then local DCStask={} @@ -79053,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.64", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -79150,6 +88660,7 @@ TacticalIncrFreq=0.5, TacticalModulation=radio.modulation.AM, TacticalInterval=120, DetectionSet=nil, +MaxMissionRange=125, } AWACS.CallSignClear={ [1]="Overlord", @@ -79424,7 +88935,7 @@ self.HasEscorts=false self.EscortTemplate="" self.EscortMission={} self.EscortMissionReplacement={} -self.PathToSRS="C:\\Program Files\\DCS-SimpleRadio-Standalone" +self.PathToSRS="C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.Gender="female" self.Culture="en-GB" self.Voice=nil @@ -79513,6 +89024,8 @@ self:AddTransition("*","FlightOnMission","*") self:AddTransition("*","Intercept","*") self:AddTransition("*","InterceptSuccess","*") self:AddTransition("*","InterceptFailure","*") +self:AddTransition("*","VIDSuccess","*") +self:AddTransition("*","VIDFailure","*") self:AddTransition("*","Stop","Stopped") local text=string.format("%sAWACS Version %s Initiated",self.lid,self.version) self:I(text) @@ -79667,6 +89180,15 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end +function AWACS:SetMaxMissionRange(NM) +self.MaxMissionRange=NM or 125 +return self +end function AWACS:AddFrequencyAndModulation(Frequency,Modulation) self:T(self.lid.."AddFrequencyAndModulation") table.insert(self.MultiFrequency,Frequency) @@ -79697,7 +89219,7 @@ 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.Duration=MSRS.getSpeechTime(TextTTS,0.95,false)or 8 RadioEntry.FromAI=FromAI RadioEntry.IsGroup=IsGroup if Tactical then @@ -79775,7 +89297,7 @@ if Event.IniCoalition==self.coalition then self:_SetClientMenus() end end -if Event.id==EVENTS.PlayerLeaveUnit then +if Event.id==EVENTS.PlayerLeaveUnit and Event.IniGroupName then self:T("Player group left unit: "..Event.IniGroupName) self:T("Player name left: "..Event.IniPlayerName) self:T("Coalition = "..UTILS.GetCoalitionName(Event.IniCoalition)) @@ -79922,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -79935,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -79971,7 +89524,7 @@ 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.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" 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 @@ -80015,7 +89568,7 @@ self.MaxAIonCAP=MaxAICap or 4 self.AICAPCAllName=Callsign or CALLSIGN.Aircraft.Colt return self end -function AWACS:SetEscort(EscortNumber) +function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance) self:T(self.lid.."SetEscort") if EscortNumber and EscortNumber>0 then self.HasEscorts=true @@ -80024,6 +89577,9 @@ else self.HasEscorts=false self.EscortNumber=0 end +self.EscortFormation=Formation +self.OffsetVec=OffsetVector or{x=500,y=100,z=500} +self.EscortEngageMaxDistance=EscortEngageMaxDistance or 45 return self end function AWACS:_MessageVector(GID,Tag,Coordinate,Angels) @@ -80053,10 +89609,21 @@ self:T(self.lid.."_StartEscorts") local AwacsFG=self.AwacsFG local group=AwacsFG:GetGroup() local timeonstation=(self.EscortsTimeOnStation+self.ShiftChangeTime)*3600 +local OffsetX=500 +local OffsetY=500 +local OffsetZ=500 +if self.OffsetVec then +OffsetX=self.OffsetVec.x or 500 +OffsetY=self.OffsetVec.y or 500 +OffsetZ=self.OffsetVec.z or 500 +end 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) +local escort=AUFTRAG:NewESCORT(group,{x=OffsetX*((i+(i%2))/2),y=OffsetY*((i+(i%2))/2),z=(OffsetZ+OffsetZ*((i+(i%2))/2))*(-1)^i},self.EscortEngageMaxDistance,{"Air"}) escort:SetTime(nil,timeonstation) +if self.Escortformation then +escort:SetFormation(self.Escortformation) +end +escort:SetMissionRange(self.MaxMissionRange) self.AirWing:AddMission(escort) self.CatchAllMissions[#self.CatchAllMissions+1]=escort if Shiftchange then @@ -80193,11 +89760,11 @@ return managedgroup.CallSign end local callsign="Ghost 1" if Group and Group:IsAlive()then -callsign=Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations) +callsign=Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations,self.callsignCustomFunc,self.callsignCustomArgs) end return callsign end -function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign==false then self.callsignshort=false else @@ -80205,6 +89772,8 @@ self.callsignshort=true end self.keepnumber=Keepnumber or false self.callsignTranslations=CallsignTranslations +self.callsignCustomFunc=CallsignCustomFunc +self.callsignCustomArgs=arg or{} return self end function AWACS:_UpdateContactFromCluster(CID) @@ -80765,11 +90334,13 @@ self.Contacts:Push(cluster,CID) local vidpos=self.gettext:GetEntry("VIDPOS",self.locale) text=string.format(vidpos,Callsign,self.callsigntxt,Declaration) self:T(text) +self:__VIDSuccess(3,GID,group,cluster) 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) +self:__VIDFailure(3,GID,group,cluster) end self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) end @@ -81281,6 +90852,10 @@ basemenu=basemenu, checkin=checkin, } self.clientmenus:Push(menus,cgrpname) +local GID,hasentry=self:_GetManagedGrpID(cgrp) +if hasentry then +self:_CheckOut(cgrp,GID,true) +end end end else @@ -81391,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -81424,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -81449,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -81541,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -82084,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -82515,6 +92112,11 @@ 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 string.find(text,"BRAA",1,true)then +text=string.gsub(text,"BRAA","brah") +elseif string.find(text,"BRA",1,true)then +text=string.gsub(text,"BRA","brah") +end if IsSub==false then self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) end @@ -82626,6 +92228,7 @@ local RejectZoneSet=self.RejectZoneSet local intercept=AUFTRAG:NewINTERCEPT(Target.Target) intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) +intercept:SetMissionRange(self.MaxMissionRange) intercept:AddConditionSuccess( function(target,zoneset,rzoneset) local success=true @@ -82667,6 +92270,7 @@ 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:SetMissionRange(self.MaxMissionRange) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) Pilot.FlightGroup:AddMission(capauftrag) if currmission then @@ -82720,27 +92324,41 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) +mission:SetMissionRange(self.MaxMissionRange) +mission:SetRequiredAttribute({GROUP.Attribute.AIR_AWACS}) local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600 mission:SetTime(nil,timeonstation) self.CatchAllMissions[#self.CatchAllMissions+1]=mission @@ -82965,17 +92583,18 @@ else report:Add("***** Cannot obtain (yet) this missions OpsGroup!") end report:Add("====================") +local RESMission 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) +RESMission=self.EscortMissionReplacement[i] +local esstatus=RESMission:GetState() +local RESMissiontime=(timer.getTime()-self.EscortsTimeStamp) +local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-RESMissiontime),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 OpsGroups=RESMission:GetOpsGroups() local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups) if OpsGroup then local OpsName=OpsGroup:GetName()or"Unknown" @@ -82986,11 +92605,11 @@ report:Add(string.format("Mission FG State %s",OpsGroup:GetState())) else report:Add("***** Cannot obtain (yet) this missions OpsGroup!") end -if ESmission:IsExecuting()then +if RESMission and RESMission:IsExecuting()then self.ShiftChangeEscortsFlag=false self.ShiftChangeEscortsRequested=false if ESmission and ESmission:IsNotOver()then -ESmission:Cancel() +ESmission:__Cancel(1) end self.EscortMission[i]=self.EscortMissionReplacement[i] self.EscortMissionReplacement[i]=nil @@ -83146,6 +92765,7 @@ 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:SetMissionRange(self.MaxMissionRange) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) capauftrag:AddAsset(managedgroup.FlightGroup) self.CatchAllMissions[#self.CatchAllMissions+1]=capauftrag @@ -83390,6 +93010,7 @@ local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.Awa self.CatchAllMissions[#self.CatchAllMissions+1]=mission local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600 mission:SetTime(nil,timeonstation) +mission:SetMissionRange(self.MaxMissionRange) AwacsAW:AddMission(mission) self.AwacsMissionReplacement=mission end @@ -83683,6 +93304,7 @@ supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone) self:AddMission(supplyzone.mission) end end +self:_TacticalOverview() if self.verbose>=1 then local Nmissions=self:CountMissionsInQueue() local Npq,Np,Nq=self:CountAssetsOnMission() @@ -83746,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -83775,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.0" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -84004,6 +93685,9 @@ end self.strategy=Strategy return self end +function CHIEF:GetStrategy() +return self.strategy +end function CHIEF:GetDefcon(Defcon) return self.Defcon end @@ -84802,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -84861,6 +94547,7 @@ end if MissionType==AUFTRAG.Type.ARMOREDGUARD then RangeMax=UTILS.NMToMeters(50) end +self:T(self.lid..string.format("Recruiting assets for zone %s",StratZone.opszone:GetName())) self:T(self.lid.."Missiontype="..MissionType) self:T({categories=Categories}) self:T({attributes=Attributes}) @@ -84872,11 +94559,15 @@ self:T2(self.lid..string.format("Recruited %d assets for %s mission STRATEGIC zo local TargetZone=StratZone.opszone.zone local TargetCoord=TargetZone:GetCoordinate() local transport=nil +local Ntransports=0 if Resource.carrierNmin and Resource.carrierNmax and Resource.carrierNmax>0 then +self:T(self.lid..string.format("Recruiting carrier assets: Nmin=%s, Nmax=%s",tostring(Resource.carrierNmin),tostring(Resource.carrierNmax))) 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) +Ntransports=transport~=nil and#transport.assets or 0 +self:T(self.lid..string.format("Recruited %d transport carrier assets success=%s",Ntransports,tostring(recruited))) end end if not recruited then @@ -84935,8 +94626,9 @@ if mission then mission:_AddAssets(assets) self:MissionAssign(mission,legions) StratZone.opszone:_AddMission(self.coalition,MissionType,mission) +mission:SetName(string.format("Stratzone %s-%d",StratZone.opszone:GetName(),mission.auftragsnummer)) Resource.mission=mission -if transport then +if transport and Ntransports>0 then mission.opstransport=transport transport.opszone=StratZone.opszone transport.chief=self @@ -84944,7 +94636,7 @@ transport.commander=self.commander end return true else -self:E(self.lid..string.format("ERROR: Mission type not supported for OPSZONE! Unrecruiting assets...")) +self:E(self.lid..string.format("ERROR: Mission type %s not supported for OPSZONE! Unrecruiting assets...",tostring(MissionType))) LEGION.UnRecruitAssets(assets) return false end @@ -85020,11 +94712,19 @@ descriptors={}, properties={}, operations={}, } -COHORT.version="0.3.5" +COHORT.version="0.3.7" +_COHORTNAMES={} function COHORT:New(TemplateGroupName,Ngroups,CohortName) +local name=tostring(CohortName or TemplateGroupName) +if UTILS.IsAnyInTable(_COHORTNAMES,name)then +env.error(string.format('ERROR: cannot create cohort "%s" because another cohort with that name already exists. Names must be unique!',name)) +return nil +else +table.insert(_COHORTNAMES,name) +end local self=BASE:Inherit(self,FSM:New()) self.templatename=TemplateGroupName -self.name=tostring(CohortName or TemplateGroupName) +self.name=name self.lid=string.format("COHORT %s | ",self.name) self.templategroup=GROUP:FindByName(self.templatename) if not self.templategroup then @@ -85163,9 +94863,10 @@ function COHORT:SetMissionRange(Range) self.engageRange=UTILS.NMToMeters(Range or 150) return self end -function COHORT:SetCallsign(Callsign,Index) +function COHORT:SetCallsign(Callsign,Index,CallsignString) self.callsignName=Callsign self.callsignIndex=Index +self.callsignClearName=CallsignString self.callsign={} self.callsign.NumberSquad=Callsign self.callsign.NumberGroup=Index @@ -85224,6 +94925,32 @@ end end return self end +function COHORT:RemoveAssets(N,Delay) +self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else +N=N or 1 +local n=0 +for i=#self.assets,1,-1 do +local asset=self.assets[i] +self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) +if not(asset.requested or asset.spawned or asset.isReserved)then +self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) +table.remove(self.assets,i) +n=n+1 +else +self:T2(self.lid..string.format("Could NOT Remove asset %s",asset.spawngroupname)) +end +if n>=N then +break +end +end +self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end +return self +end function COHORT:GetName() return self.name end @@ -85244,6 +94971,9 @@ self.callsigncounter=self.callsigncounter+2 else self.callsigncounter=self.callsigncounter+1 end +callsign["name"]=self.callsignClearName or UTILS.GetCallsignName(self.callsignName)or"None" +callsign["name"]=string.format("%s%d%d",callsign["name"],callsign[2],callsign[3]) +callsign[4]=callsign["name"] Asset.callsign[i]=callsign self:T3({callsign=callsign}) end @@ -85412,6 +95142,7 @@ return false end if Mission.type==AUFTRAG.Type.TANKER then if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then +self:T(self.lid..string.format("INFO: Correct refueling system requested=%s != %s=available",tostring(Mission.refuelSystem),tostring(self.tankerSystem))) else self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available",tostring(Mission.refuelSystem),tostring(self.tankerSystem))) return false @@ -85765,6 +95496,7 @@ gcicapZones={}, awacsZones={}, tankerZones={}, limitMission={}, +maxMissionsAssignPerCycle=1, } COMMANDER.version="0.1.4" function COMMANDER:New(Coalition,Alias) @@ -86389,6 +96121,7 @@ if mission.importance and mission.importance=(self.maxMissionsAssignPerCycle or 1)then return end +end else end end end +function COMMANDER:SetMaxMissionsAssignPerCycle(MaxMissionsAssignPerCycle) +self.maxMissionsAssignPerCycle=MaxMissionsAssignPerCycle or 1 +return self +end function COMMANDER:_GetCohorts(Legions,Cohorts,Operation) local function CheckOperation(LegionOrCohort) if#self.opsqueue==0 then @@ -86447,7 +96187,10 @@ 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 +local Runway=true +if legion:IsAirwing()then +Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition() +end if legion:IsRunning()and Runway then for _,_cohort in pairs(legion.cohorts)do local cohort=_cohort @@ -86466,7 +96209,10 @@ end else for _,_legion in pairs(self.legions)do local legion=_legion -local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true +local Runway=true +if legion:IsAirwing()then +Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition() +end if legion:IsRunning()and Runway then for _,_cohort in pairs(legion.cohorts)do local cohort=_cohort @@ -86501,8 +96247,10 @@ if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then MaxWeight=cohort.cargobayLimit end end +if MaxWeight then self:T(self.lid..string.format("Largest cargo bay available=%.1f",MaxWeight)) end +end local legions=self.legions local cohorts=nil if Mission.specialLegions or Mission.specialCohorts then @@ -86762,6 +96510,7 @@ self:GetParent(self).onafterStatus(self,From,Event,To) local fsmstate=self:GetState() self:CheckTransportQueue() self:CheckMissionQueue() +self:_TacticalOverview() if self.verbose>=1 then local Nmissions=self:CountMissionsInQueue() local Npq,Np,Nq=self:CountAssetsOnMission() @@ -86825,6 +96574,7 @@ Nparkingspots=nil, holdingpatterns={}, hpcounter=0, nosubs=false, +Nplayers=0, } FLIGHTCONTROL.FlightStatus={ UNKNOWN="Unknown", @@ -86839,7 +96589,7 @@ LANDING="Landing", TAXIINB="Taxi To Parking", ARRIVED="Arrived", } -FLIGHTCONTROL.version="0.7.5" +FLIGHTCONTROL.version="0.7.7" function FLIGHTCONTROL:New(AirbaseName,Frequency,Modulation,PathToSRS,Port,GoogleKey) local self=BASE:Inherit(self,FSM:New()) self.airbase=AIRBASE:FindByName(AirbaseName) @@ -86867,6 +96617,7 @@ self:SetRunwayRepairtime() self.nosubs=false self:SetCallSignOptions(true,true) self.msrsqueue=MSRSQUEUE:New(self.alias) +self:SetTransmitOnlyWithPlayers(true) local path=PathToSRS or MSRS.path local port=Port or MSRS.port or 5002 self:SetSRSPort(port) @@ -86902,6 +96653,18 @@ function FLIGHTCONTROL:SetVerbosity(VerbosityLevel) self.verbose=VerbosityLevel or 0 return self end +function FLIGHTCONTROL:SetRadioOnlyIfPlayers(Switch) +if Switch==nil or Switch==true then +self.radioOnlyIfPlayers=true +else +self.radioOnlyIfPlayers=false +end +return self +end +function FLIGHTCONTROL:SetTransmitOnlyWithPlayers(Switch) +self.msrsqueue:SetTransmitOnlyWithPlayers(Switch) +return self +end function FLIGHTCONTROL:SwitchSubtitlesOn() self.nosubs=false return self @@ -88557,6 +98320,13 @@ self:T(self.lid..string.format("Removing DEAD flight %s",tostring(flight.groupna self:_RemoveFlight(flight) end end +self.Nplayers=0 +for _,_flight in pairs(self.flights)do +local flight=_flight +if not flight.isAI then +self.Nplayers=self.Nplayers+1 +end +end if self.speedLimitTaxi then for _,_flight in pairs(self.flights)do local flight=_flight @@ -88662,6 +98432,10 @@ self:TransmissionPilot(text,flight) return self end function FLIGHTCONTROL:TransmissionTower(Text,Flight,Delay) +if self.radioOnlyIfPlayers==true and self.Nplayers==0 then +self:T(self.lid.."No players ==> skipping TOWER radio transmission") +return +end local text=self:_GetTextForSpeech(Text) local subgroups=nil if Flight and not Flight.isAI then @@ -88676,6 +98450,10 @@ self.Tlastmessage=timer.getAbsTime()+(Delay or 0) self:T(self.lid..string.format("Radio Tower: %s",Text)) end function FLIGHTCONTROL:TransmissionPilot(Text,Flight,Delay) +if self.radioOnlyIfPlayers==true and self.Nplayers==0 then +self:T(self.lid.."No players ==> skipping PILOT radio transmission") +return +end local playerData=Flight:_GetPlayerData() if playerData==nil or playerData.myvoice then local text=self:_GetTextForSpeech(Text) @@ -88892,7 +98670,7 @@ GRADUATE="Graduate", INSTRUCTOR="Instructor", } FLIGHTGROUP.Players={} -FLIGHTGROUP.version="1.0.2" +FLIGHTGROUP.version="1.0.3" function FLIGHTGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -88900,7 +98678,7 @@ 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.lid=string.format("FLIGHTGROUP %s | ",self.groupname or"N/A") self:SetDefaultROE() self:SetDefaultROT() self:SetDefaultEPLRS(self.isEPLRS) @@ -89010,6 +98788,41 @@ self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,not Switch) end return self end +function FLIGHTGROUP:SetOptionLandingStraightIn() +self.OptionLandingStraightIn=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingStraightIn() +end +return self +end +function FLIGHTGROUP:SetOptionLandingForcePair() +self.OptionLandingForcePair=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingForcePair() +end +return self +end +function FLIGHTGROUP:SetOptionLandingRestrictPair() +self.OptionLandingRestrictPair=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingRestrictPair() +end +return self +end +function FLIGHTGROUP:SetOptionLandingOverheadBreak() +self.OptionLandingOverheadBreak=true +if self:GetGroup():IsAlive()then +self:GetGroup():SetOptionLandingOverheadBreak() +end +return self +end +function FLIGHTGROUP:SetOptionPreferVertical() +self.OptionPreferVertical=true +if self:GetGroup():IsAlive()then +self:GetGroup():OptionPreferVerticalLanding() +end +return self +end function FLIGHTGROUP:SetReadyForTakeoff(ReadyTO,Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay,FLIGHTGROUP.SetReadyForTakeoff,self,ReadyTO,0) @@ -89274,6 +99087,15 @@ if timer.getAbsTime()>self.Twaiting+self.dTwait then end end end +if mission and mission.missionHoldingCoord and self.isHoldingAtHoldingPoint==true then +self:T(self.lid.."...yes") +if mission:IsReadyToPush()then +self.flaghold:Set(1) +self.Twaiting=nil +self.dTwait=nil +self.isHoldingAtHoldingPoint=false +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() @@ -89587,6 +99409,7 @@ 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:_SetElementParkingFree(Element) self:_UpdateStatus(Element,OPSGROUP.ElementStatus.AIRBORNE) end function FLIGHTGROUP:onafterElementLanded(From,Event,To,Element,airbase) @@ -89987,6 +99810,10 @@ if self:IsEngaging()then self:T(self.lid.."Engaging! Group NOT done...") return end +if self:IsGoing4Fuel()then +self:T(self.lid.."Going for FUEL! Group NOT done...") +return +end local nTasks=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() local nTransports=self:CountRemainingTransports() @@ -90126,7 +99953,7 @@ 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!") +self:T(self.lid.."ERROR: Airbase is nil in LandAtAirbase() call!") allowed=false end if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then @@ -90297,10 +100124,12 @@ Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5),self.group:Get 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) +self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO,true) end function FLIGHTGROUP:onafterRefueled(From,Event,To) local text=string.format("Flight group finished refuelling") self:T(self.lid..text) +self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO,false) self:_CheckGroupDone(1) end function FLIGHTGROUP:onafterHolding(From,Event,To) @@ -90448,16 +100277,16 @@ flightgroup.Twaiting=nil flightgroup.dTwait=nil flightgroup:_CheckGroupDone(0.1) end -function FLIGHTGROUP:_InitGroup(Template) +function FLIGHTGROUP:_InitGroup(Template,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,FLIGHTGROUP._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 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 @@ -90468,6 +100297,10 @@ 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() +local template=Template or self:_GetTemplate() +self.isUncontrolled=template~=nil and template.uncontrolled or false +self.isLateActivated=template~=nil and template.lateActivation or false +if template then self.radio.Freq=tonumber(template.frequency) self.radio.Modu=tonumber(template.modulation) self.radio.On=template.communication @@ -90482,13 +100315,18 @@ end self.callsign.NumberSquad=tonumber(callsign[1]) self.callsign.NumberGroup=tonumber(callsign[2]) self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) +end if self.isHelo then self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 else self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group end +if not self.tacanDefault then self:SetDefaultTACAN(nil,nil,nil,nil,true) +end +if not self.tacan then self.tacan=UTILS.DeepCopy(self.tacanDefault) +end self.isAI=not self:_IsHuman(group) if not self.isAI then self.menu=self.menu or{} @@ -90506,6 +100344,7 @@ for _,unit in pairs(units)do self:_AddElementByName(unit:GetName()) end self.groupinitialized=true +end return self end function FLIGHTGROUP:GetHomebaseFromWaypoints() @@ -90646,29 +100485,6 @@ end end return nil end -function FLIGHTGROUP:InitWaypoints() -self.waypoints0=self.group:GetTemplateRoutePoints() -self.waypoints={} -for index,wp in pairs(self.waypoints0)do -local waypoint=self:_CreateWaypoint(wp) -self:_AddWaypoint(waypoint) -end -self.homebase=self.homebase or self:GetHomebaseFromWaypoints() -self.destbase=self.destbase or self:GetDestinationFromWaypoints() -self.currbase=self:GetHomebaseFromWaypoints() -if self.destbase and#self.waypoints>1 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) @@ -90792,6 +100608,10 @@ end function FLIGHTGROUP:GetParkingSpot(element,maxdist,airbase) local coord=element.unit:GetCoordinate() airbase=airbase or self:GetClosestAirbase() +if airbase==nil then +self:T(self.lid.."No airbase found for element "..element.name) +return nil +end local parking=airbase.parking if airbase and airbase:IsShip()then if#parking>1 then @@ -90862,12 +100682,14 @@ local clients=_DATABASE.CLIENTS local coords={} for clientname,client in pairs(clients)do local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) +if template then 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 airbasecategory=airbase:GetAirbaseCategory() @@ -91061,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -91285,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -91292,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -91345,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -91352,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -91384,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -91550,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -91591,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -91805,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -92343,13 +102260,13 @@ ClassName="INTEL_DLINK", verbose=0, lid=nil, alias=nil, -cachetime=300, +cachetime=120, interval=20, contacts={}, clusters={}, contactcoords={}, } -INTEL_DLINK.version="0.0.1" +INTEL_DLINK.version="0.0.2" function INTEL_DLINK:New(Intels,Alias,Interval,Cachetime) local self=BASE:Inherit(self,FSM:New()) self.intels=Intels or{} @@ -92361,9 +102278,9 @@ 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:SetDLinkCacheTime(Cachetime or 120) self:SetStartState("Stopped") self:AddTransition("Stopped","Start","Running") self:AddTransition("*","Collect","*") @@ -92385,6 +102302,11 @@ self:I(self.lid..text) self:__Collect(-math.random(1,10)) return self end +function INTEL_DLINK:SetDLinkCacheTime(seconds) +self.cachetime=math.abs(seconds or 120) +self:I(self.lid.."Caching for "..self.cachetime.." seconds.") +return self +end function INTEL_DLINK:onbeforeCollect(From,Event,To) self:T({From,Event,To}) self:T("Contacts Data Gathering") @@ -92468,7 +102390,7 @@ transportqueue={}, cohorts={}, } LEGION.RandomAssetScore=1 -LEGION.version="0.5.0" +LEGION.version="0.5.1" function LEGION:New(WarehouseName,LegionName) local self=BASE:Inherit(self,WAREHOUSE:New(WarehouseName,LegionName)) if not self then @@ -92493,6 +102415,10 @@ function LEGION:SetVerbosity(VerbosityLevel) self.verbose=VerbosityLevel or 0 return self end +function LEGION:SetTacticalOverviewOn() +self.tacview=true +return self +end function LEGION:AddMission(Mission) Mission:Queued() Mission:SetLegionStatus(self,AUFTRAG.Status.QUEUED) @@ -92545,6 +102471,14 @@ end end return self end +function LEGION:DelAsset(Asset) +if Asset.cohort then +Asset.cohort:DelAsset(Asset) +else +self:E(self.lid..string.format("ERROR: Asset has not cohort attached. Cannot remove it from legion!")) +end +return self +end function LEGION:RelocateCohort(Cohort,Legion,Delay,NcarriersMin,NcarriersMax,TransportLegions) if Delay and Delay>0 then self:ScheduleOnce(Delay,LEGION.RelocateCohort,self,Cohort,Legion,0,NcarriersMin,NcarriersMax,TransportLegions) @@ -92637,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -92692,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -93090,6 +103031,7 @@ 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 +self:DelAsset(asset) end function LEGION:onafterDestroyed(From,Event,To) self:T(self.lid.."Legion warehouse destroyed!") @@ -93145,6 +103087,7 @@ if self:IsAirwing()then opsgroup=FLIGHTGROUP:New(asset.spawngroupname) elseif self:IsBrigade()then opsgroup=ARMYGROUP:New(asset.spawngroupname) +opsgroup:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) elseif self:IsFleet()then opsgroup=NAVYGROUP:New(asset.spawngroupname) else @@ -93153,6 +103096,7 @@ end opsgroup:_SetLegion(self) opsgroup.cohort=self:_GetCohortOfAsset(asset) opsgroup.homebase=self.airbase +opsgroup.destbase=self.airbase opsgroup.homezone=self.spawnzone if opsgroup.cohort.weaponData then local text="Weapon data for group:" @@ -93166,6 +103110,35 @@ self:T3(self.lid..text) end return opsgroup end +function LEGION:_TacticalOverview() +if self.tacview then +local NassetsTotal=self:CountAssets(nil) +local NassetsStock=self:CountAssets(true) +local NassetsActiv=self:CountAssets(false) +local NmissionsTotal=#self.missionqueue +local NmissionsRunni=self:CountMissionsInQueue() +local text=string.format("Tactical Overview %s\n",self.alias) +text=text..string.format("===================================\n") +text=text..string.format("Assets: %d [Active=%d, Stock=%d]\n",NassetsTotal,NassetsActiv,NassetsStock) +text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsRunni) +for _,mtype in pairs(AUFTRAG.Type)do +local n=self:CountMissionsInQueue(mtype) +if n>0 then +local N=self:CountMissionsInQueue(mtype,true) +text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) +end +end +local Ntransports=#self.transportqueue +if Ntransports>0 then +text=text..string.format("Transports: %d\n",Ntransports) +for _,_transport in pairs(self.transportqueue)do +local transport=_transport +text=text..string.format(" - %s",transport:GetState()) +end +end +MESSAGE:New(text,60,nil,true):ToCoalition(self:GetCoalition()) +end +end function LEGION:IsAssetOnMission(asset,MissionTypes) if MissionTypes then if type(MissionTypes)~="table"then @@ -93246,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -93473,7 +103448,10 @@ 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 +local Runway=true +if legion:IsAirwing()then +Runway=legion:IsRunwayOperational()and legion.airbase and legion.airbase:GetCoalition()==legion:GetCoalition() +end if legion:IsRunning()and Runway then for _,_cohort in pairs(legion.cohorts)do local cohort=_cohort @@ -93671,6 +103649,9 @@ table.insert(Assets,asset) end end end +if#Assets==0 then +return false,{},{} +end LEGION._OptimizeAssetSelection(Assets,MissionTypeOpt,TargetVec2,false,TotalWeight) for _,_asset in pairs(Assets)do local asset=_asset @@ -94002,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.2" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -94047,6 +104028,7 @@ self:_InitGroup() self:HandleEvent(EVENTS.Birth,self.OnEventBirth) self:HandleEvent(EVENTS.Dead,self.OnEventDead) self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit) +self:HandleEvent(EVENTS.UnitLost,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) @@ -94074,6 +104056,13 @@ function NAVYGROUP:SetPathfindingOff() self:SetPathfinding(false,self.pathCorridor) return self end +function NAVYGROUP:SetIntoWindLegacy(SwitchOn) +if SwitchOn==nil then +SwitchOn=true +end +self.intowindold=SwitchOn +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) @@ -94133,6 +104122,31 @@ local recovery=self:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset) table.insert(self.Qintowind,recovery) return recovery end +function NAVYGROUP:GetTurnIntoWind(TID) +if TID then +for _,_turn in pairs(self.Qintowind)do +local turn=_turn +if turn.Id==TID then +return turn +end +end +else +return self.intowind +end +return nil +end +function NAVYGROUP:ExtendTurnIntoWind(Duration,TurnIntoWind) +Duration=Duration or 300 +local TID=TurnIntoWind and TurnIntoWind.Id or nil +local turn=self:GetTurnIntoWind(TID) +if turn then +turn.Tstop=turn.Tstop+Duration +self:T(self.lid..string.format("Extending turn into wind by %d seconds. New stop time is %s",Duration,UTILS.SecondsToClock(turn.Tstop))) +else +self:E(self.lid.."Could not get turn into wind to extend!") +end +return self +end function NAVYGROUP:RemoveTurnIntoWind(IntoWindData) if self.intowind and self.intowind.Id==IntoWindData.Id then self:TurnIntoWindStop() @@ -94191,7 +104205,7 @@ else return false end end -function NAVYGROUP:Status(From,Event,To) +function NAVYGROUP:Status() local fsmstate=self:GetState() local alive=self:IsAlive() local freepath=0 @@ -94290,6 +104304,24 @@ text=text..string.format("\n[%d] ID=%d Start=%s Stop=%s Open=%s Over=%s",i,recov end 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 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:I(self.lid..text) +end if self:IsCruising()and self.detectionOn and self.engagedetectedOn then local targetgroup,targetdist=self:_GetDetectedTarget() if targetgroup then @@ -94307,7 +104339,7 @@ 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) +local text=string.format("Initialized Navy Group %s [GID=%d]:\n",self.groupname,self.group:GetID()) 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)) @@ -94318,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -94432,7 +104464,7 @@ 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) +local text=string.format("%s Waypoint [%d] UID=%d speed=%d m/s",self.groupname,i-1,wp.uid or-1,wp.speed) self:I(self.lid..text) COORDINATE:NewFromWaypoint(wp):MarkToAll(text) end @@ -94542,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -94550,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -94566,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -94584,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -94650,7 +104684,10 @@ self:__UpdateRoute(-0.01) end return waypoint end -function NAVYGROUP:_InitGroup(Template) +function NAVYGROUP:_InitGroup(Template,Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,NAVYGROUP._InitGroup,self,Template,0) +else if self.groupinitialized then self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return @@ -94673,10 +104710,18 @@ 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 +if not self.tacanDefault then self:SetDefaultTACAN(nil,nil,nil,nil,true) +end +if not self.tacan then self.tacan=UTILS.DeepCopy(self.tacanDefault) +end +if not self.iclsDefault then self:SetDefaultICLS(nil,nil,nil,true) +end +if not self.icls then self.icls=UTILS.DeepCopy(self.iclsDefault) +end local units=self.group:GetUnits() local dcsgroup=Group.getByName(self.groupname) local size0=dcsgroup:getInitialSize() @@ -94687,6 +104732,7 @@ for _,unit in pairs(units)do self:_AddElementByName(unit:GetName()) end self.groupinitialized=true +end return self end function NAVYGROUP:_CheckFreePath(DistanceMax,dx) @@ -94786,19 +104832,35 @@ local coord=self:GetCoordinate() local Wdir,Wspeed=coord:GetWind(Altitude or 18) return Wdir,Wspeed end -function NAVYGROUP:GetHeadingIntoWind_old(Offset) +function NAVYGROUP:GetHeadingIntoWind_old(Offset,vdeck) +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 Offset=Offset or 0 local windfrom,vwind=self:GetWind() -local intowind=windfrom-Offset +local intowind=windfrom-Offset+adjustDegreesForWindSpeed(vwind) if vwind<0.1 then intowind=self:GetHeading() end if intowind<0 then intowind=intowind+360 end -return intowind +local vtot=math.max(vdeck-UTILS.MpsToKnots(vwind),4) +return intowind,vtot end -function NAVYGROUP:GetHeadingIntoWind(Offset,vdeck) +function NAVYGROUP:GetHeadingIntoWind_new(Offset,vdeck) Offset=Offset or 0 local windfrom,vwind=self:GetWind(18) vwind=UTILS.MpsToKnots(vwind) @@ -94828,6 +104890,13 @@ 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:GetHeadingIntoWind(Offset,vdeck) +if self.intowindold then +return self:GetHeadingIntoWind_old(Offset,vdeck) +else +return self:GetHeadingIntoWind_new(Offset,vdeck) +end +end function NAVYGROUP:_FindPathToNextWaypoint() self:T3(self.lid.."Path finding") local astar=ASTAR:New() @@ -95495,7 +105564,7 @@ ASSIGNED="assigned to carrier", BOARDING="boarding", LOADED="loaded", } -OPSGROUP.version="1.0.1" +OPSGROUP.version="1.0.4" function OPSGROUP:New(group) local self=BASE:Inherit(self,FSM:New()) if type(group)=="string"then @@ -95683,7 +105752,7 @@ self.legionReturn=false else self.legionReturn=true end -self:T(self.lid..string.format("Setting ReturnToLetion=%s",tostring(self.legionReturn))) +self:T(self.lid..string.format("Setting ReturnToLegion=%s",tostring(self.legionReturn))) return self end function OPSGROUP:SetDefaultSpeed(Speed) @@ -95844,21 +105913,43 @@ return false end return nil end -function OPSGROUP:GetCoordinateInRange(TargetCoord,WeaponBitType,RefCoord) +function OPSGROUP:GetCoordinateInRange(TargetCoord,WeaponBitType,RefCoord,SurfaceTypes) local coordInRange=nil RefCoord=RefCoord or self:GetCoordinate() local weapondata=self:GetWeaponData(WeaponBitType) +local dh={0,-5,5,-10,10,-15,15,-20,20,-25,25,-30,30,-35,35,-40,40,-45,45,-50,50,-55,55,-60,60,-65,65,-70,70,-75,75,-80,80} +local function _checkSurface(point) +if SurfaceTypes then +local stype=point:GetSurfaceType() +for _,sf in pairs(SurfaceTypes)do +if sf==stype then +return true +end +end +return false +else +return true +end +end if weapondata then -local heading=RefCoord:HeadingTo(TargetCoord) +local heading=TargetCoord:HeadingTo(RefCoord) local dist=RefCoord:Get2DDistance(TargetCoord) +local range=nil 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))) +range=weapondata.RangeMax +self:T(self.lid..string.format("Out of max range = %.1f km by %.1f km for weapon %s",weapondata.RangeMax/1000,(weapondata.RangeMax-dist)/1000,tostring(WeaponBitType))) elseif dist Task DONE!",zoneCurr:GetName())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -97533,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -97609,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -97948,6 +108078,7 @@ 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())) +mission.unpaused=true self:MissionStart(mission) for i,mid in pairs(self.pausedmissions)do if mid==mission.auftragsnummer then @@ -97988,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -97997,7 +108128,7 @@ end self:_RemoveMissionWaypoints(Mission) if Mission.patroldata then Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 -AIRWING.UpdatePatrolPointMarker(Mission.patroldata) +AIRWING.UpdatePatrolPointMarker(self,Mission.patroldata) end if Mission.engagedetectedOn then self:SetEngageDetectedOff() @@ -98194,11 +108325,10 @@ end end elseif mission.type==AUFTRAG.Type.ARTY then local targetcoord=mission:GetTargetCoordinate() -local inRange=self:InWeaponRange(targetcoord,mission.engageWeaponType) +local inRange=self:InWeaponRange(targetcoord,mission.engageWeaponType,waypointcoord) if inRange then -waypointcoord=self:GetCoordinate(true) else -local coordInRange=self:GetCoordinateInRange(targetcoord,mission.engageWeaponType,waypointcoord) +local coordInRange=self:GetCoordinateInRange(targetcoord,mission.engageWeaponType,waypointcoord,surfacetypes) if coordInRange then local waypoint=nil if self:IsFlightgroup()then @@ -98218,6 +108348,25 @@ 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 +local ingresscoord=mission:GetMissionIngressCoord() +local holdingcoord=mission:GetMissionHoldingCoord() +if holdingcoord then +waypoint=FLIGHTGROUP.AddWaypoint(self,holdingcoord,mission.missionHoldingCoordSpeed or SpeedToMission,uid,UTILS.MetersToFeet(mission.missionHoldingCoordAlt or self.altitudeCruise),false) +uid=waypoint.uid +self.flaghold:Set(0) +local TaskOrbit=self.group:TaskOrbit(holdingcoord,mission.missionHoldingCoordAlt,UTILS.KnotsToMps(mission.missionHoldingCoordSpeed or SpeedToMission)) +local TaskStop=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,mission.missionHoldingDuration or 900) +local TaskCntr=self.group:TaskControlled(TaskOrbit,TaskStop) +local TaskOver=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting",self) +local DCSTasks=self.group:TaskCombo({TaskCntr,TaskOver}) +local waypointtask=self:AddTaskWaypoint(DCSTasks,waypoint,"Holding") +waypointtask.ismission=false +self.isHoldingAtHoldingPoint=true +end +if ingresscoord then +waypoint=FLIGHTGROUP.AddWaypoint(self,ingresscoord,mission.missionIngressCoordSpeed or SpeedToMission,uid,UTILS.MetersToFeet(mission.missionIngressCoordAlt or self.altitudeCruise),false) +uid=waypoint.uid +end waypoint=FLIGHTGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false) elseif self:IsArmygroup()then local formation=mission.optionFormation @@ -98238,7 +108387,7 @@ 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) +Ewaypoint=FLIGHTGROUP.AddWaypoint(self,egresscoord,mission.missionEgressCoordSpeed or SpeedToMission,waypoint.uid,UTILS.MetersToFeet(mission.missionEgressCoordAlt or self.altitudeCruise),false) elseif self:IsArmygroup()then Ewaypoint=ARMYGROUP.AddWaypoint(self,egresscoord,SpeedToMission,waypoint.uid,mission.optionFormation,false) elseif self:IsNavygroup()then @@ -98250,13 +108399,15 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end -if self.speedMax<=3.6 or mission.teleport then +if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then self:Teleport(waypointcoord,nil,true) self:__TaskExecute(-1,waypointtask) else @@ -98833,7 +108984,7 @@ 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:T(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() @@ -98965,14 +109116,26 @@ self.Ndestroyed=self.Ndestroyed+1 end end self:Despawn(0,true) -else +end for _,_element in pairs(self.elements)do local element=_element +if element and element.status~=OPSGROUP.ElementStatus.DEAD then self:ElementInUtero(element) end end -self:T({Template=Template}) +self:_Spawn(0.01,Template) +end +return self +end +function OPSGROUP:_Spawn(Delay,Template) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) +else +if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end self.group=_DATABASE:Spawn(Template) +self.group:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) self.dcsgroup=self:GetDCSGroup() self.controller=self.dcsgroup:getController() self.isLateActivated=Template.lateActivation @@ -98983,9 +109146,8 @@ self.groupinitialized=false self.wpcounter=1 self.currentwp=1 self:_InitWaypoints() -self:_InitGroup(Template) +self:_InitGroup(Template,0.001) end -return self end function OPSGROUP:onafterInUtero(From,Event,To) self:T(self.lid..string.format("Group inutero at t=%.3f",timer.getTime())) @@ -99036,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -99094,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -99600,7 +109764,7 @@ 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 +if self.isFlightgroup and element.unit and element.unit:IsAlive()then trigger.action.setUnitInternalCargo(element.name,element.weightCargo) end end @@ -100609,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -100743,8 +110907,13 @@ self:_PassedFinalWaypoint(false,string.format("_AddWaypoint: wpnumber/index %d>% end end function OPSGROUP:_InitWaypoints(WpIndexMin,WpIndexMax) -self.waypoints0=UTILS.DeepCopy(_DATABASE:GetGroupTemplate(self.groupname).route.points) self.waypoints={} +self.waypoints0={} +local template=_DATABASE:GetGroupTemplate(self.groupname) +if template==nil then +return self +end +self.waypoints0=UTILS.DeepCopy(template.route.points) WpIndexMin=WpIndexMin or 1 WpIndexMax=WpIndexMax or#self.waypoints0 WpIndexMax=math.min(WpIndexMax,#self.waypoints0) @@ -101372,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -101737,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -101752,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -101772,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -101802,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -101847,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -101937,7 +112122,6 @@ 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 @@ -101953,7 +112137,14 @@ element.uid=element.DCSunit:getID() element.controller=element.DCSunit:getController() element.Nhit=0 element.opsgroup=self -element.skill=unittemplate.skill or"Unknown" +local unittemplate=unit:GetTemplate() +if unittemplate==nil then +if element.DCSunit:getPlayerName()then +element.skill="Client" +end +else +element.skill=unittemplate~=nil and unittemplate.skill or"Unknown" +end if element.skill=="Client"or element.skill=="Player"then element.ai=false element.client=CLIENT:FindByName(unitname) @@ -101986,22 +112177,19 @@ element.cargoBay={} element.weightCargo=0 end element.weight=element.weightEmpty+element.weightCargo -if self.isFlightgroup then element.callsign=element.unit:GetCallsign() +element.fuelmass=element.fuelmass0 or 99999 +element.fuelrel=element.unit:GetFuel()or 1 +if self.isFlightgroup and unittemplate then 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, @@ -102091,6 +112279,9 @@ end end return targetgroup,targetdist end +function OPSGROUP:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end OPSTRANSPORT={ ClassName="OPSTRANSPORT", verbose=0, @@ -103135,12 +113326,13 @@ Tblu=0, Tnut=0, chiefs={}, Missions={}, +UpdateSeconds=120, } OPSZONE.ZoneType={ Circular="Circular", Polygon="Polygon", } -OPSZONE.version="0.6.1" +OPSZONE.version="0.6.2" function OPSZONE:New(Zone,CoalitionOwner) local self=BASE:Inherit(self,FSM:New()) if Zone then @@ -103258,6 +113450,14 @@ self.drawZone=true end return self end +function OPSZONE:SetDrawZoneForCoalition(Switch) +if Switch==true then +self.drawZoneForCoalition=true +else +self.drawZoneForCoalition=false +end +return self +end function OPSZONE:SetMarkZone(Switch,ReadOnly) if Switch then self.markZone=true @@ -103316,6 +113516,10 @@ return dT end return nil end +function OPSZONE:FindByName(ZoneName) +local Found=_DATABASE:FindOpsZone(ZoneName) +return Found +end function OPSZONE:IsRed() local is=self.ownerCurrent==coalition.side.RED return is @@ -103362,10 +113566,12 @@ 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) +local EveryUpdateIn=self.UpdateSeconds or 120 +self.timerStatus:Start(1,EveryUpdateIn) if self.airbase then self:HandleEvent(EVENTS.BaseCaptured) end +return self end function OPSZONE:onafterStop(From,Event,To) self:I(self.lid..string.format("Stopping OPSZONE")) @@ -103407,7 +113613,11 @@ self.ownerCurrent=NewOwnerCoalition if self.drawZone then self.zone:UndrawZone() local color=self:_GetZoneColor() -self.zone:DrawZone(nil,color,1.0,color,0.5) +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.5) end for _,_chief in pairs(self.chiefs)do local chief=_chief @@ -103435,7 +113645,11 @@ self.Tattacked=nil if self.drawZone then self.zone:UndrawZone() local color=self:_GetZoneColor() -self.zone:DrawZone(nil,color,1.0,color,0.5) +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.5) end end end @@ -103454,7 +113668,11 @@ 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) +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.5) end self:_CleanMissionTable() end @@ -103469,7 +113687,11 @@ end if self.drawZone then self.zone:UndrawZone() local color=self:_GetZoneColor() -self.zone:DrawZone(nil,color,1.0,color,0.2) +local coalition=nil +if self.drawZoneForCoalition then +coalition=self.ownerCurrent +end +self.zone:DrawZone(coalition,color,1.0,color,0.2) end end end @@ -103617,7 +113839,7 @@ captured(coalition.side.NEUTRAL) end else if Nblu>0 then -if not self:IsAttacked()and self.Tnut>=self.threatlevelCapture then +if not self:IsAttacked()and self.Tblu>=self.threatlevelCapture then self:Attacked(coalition.side.BLUE) end elseif Nblu==0 then @@ -103642,7 +113864,7 @@ captured(coalition.side.NEUTRAL) end else if Nred>0 then -if not self:IsAttacked()and self.Tnut>=self.threatlevelCapture then +if not self:IsAttacked()and self.Tred>=self.threatlevelCapture then self:Attacked(coalition.side.RED) end elseif Nred==0 then @@ -103859,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.24" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -103876,7 +114101,7 @@ self.TaskController=nil self.timestamp=timer.getAbsTime() self.TTSType=TTSType or"close air support" self.lastsmoketime=0 -if Repeat then +if type(Repeat)=="boolean"and Repeat==true and type(Times)=="number"and Times>1 then self.Repeat=true self.RepeatNo=Times or 1 end @@ -103910,6 +114135,89 @@ self:AddTransition("*","Stop","Stopped") self:__Status(-5) return self end +function PLAYERTASK:NewFromTarget(Target,Repeat,Times,TTSType) +return PLAYERTASK:New(self:_GetTaskTypeForTarget(Target),Target,Repeat,Times,TTSType) +end +function PLAYERTASK:_GetTaskTypeForTarget(Target) +local group=nil +local auftrag=nil +if Target:IsInstanceOf("GROUP")then +group=Target +elseif Target:IsInstanceOf("SET_GROUP")then +group=Target:GetFirst() +elseif Target:IsInstanceOf("UNIT")then +group=Target:GetGroup() +elseif Target:IsInstanceOf("SET_UNIT")then +group=Target:GetFirst():GetGroup() +elseif Target:IsInstanceOf("AIRBASE")then +auftrag=AUFTRAG.Type.BOMBRUNWAY +elseif Target:IsInstanceOf("STATIC") +or Target:IsInstanceOf("SET_STATIC") +or Target:IsInstanceOf("SCENERY") +or Target:IsInstanceOf("SET_SCENERY")then +auftrag=AUFTRAG.Type.BOMBING +elseif Target:IsInstanceOf("OPSZONE") +or Target:IsInstanceOf("SET_OPSZONE")then +auftrag=AUFTRAG.Type.CAPTUREZONE +end +if group then +local category=group:GetCategory() +local attribute=group:GetAttribute() +if(category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER) +and group:InAir()then +auftrag=AUFTRAG.Type.INTERCEPT +elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then +if attribute==GROUP.Attribute.GROUND_SAM +or attribute==GROUP.Attribute.GROUND_EWR then +auftrag=AUFTRAG.Type.SEAD +elseif attribute==GROUP.Attribute.GROUND_AAA +or attribute==GROUP.Attribute.GROUND_APC +or attribute==GROUP.Attribute.GROUND_IFV +or attribute==GROUP.Attribute.GROUND_TRUCK +or attribute==GROUP.Attribute.GROUND_TRAIN then +auftrag=AUFTRAG.Type.BAI +elseif attribute==GROUP.Attribute.GROUND_INFANTRY +or attribute==GROUP.Attribute.GROUND_ARTILLERY +or attribute==GROUP.Attribute.GROUND_TANK then +auftrag=AUFTRAG.Type.CAS +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 +end +return auftrag +end +function PLAYERTASK:_CheckCaptureOpsZoneSuccess(OpsZone,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone) +local isClientInZone=true +if CheckClientInZone then +isClientInZone=false +for _,client in ipairs(self:GetClientObjects())do +local clientCoord=client:GetCoordinate() +if OpsZone.zone:IsCoordinateInZone(clientCoord)then +isClientInZone=true +break +end +end +end +local isCaptureGroupInZone=false +OpsZone:GetScannedGroupSet():ForEachGroup(function(group) +if string.find(group:GetName(),CaptureSquadGroupNamePrefix)then +isCaptureGroupInZone=true +end +end) +return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone +end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -103946,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -103968,6 +114286,91 @@ self:T(self.lid.."SetMenuName") self.Target.name=Text return self end +function PLAYERTASK:AddStaticObjectSuccessCondition() +local task=self +task:AddConditionSuccess( +function(target) +if target==nil then return false end +local isDead=false +if target:IsInstanceOf("STATIC") +or target:IsInstanceOf("SCENERY") +or target:IsInstanceOf("SET_SCENERY")then +isDead=(not target)or target:GetLife()<1 or target:GetLife()<0.2*target:GetLife0() +elseif target:IsInstanceOf("SET_STATIC")then +local deadCount=0 +target:ForEachStatic(function(static) +if static:GetLife()<1 or static:GetLife()<0.2*static:GetLife0()then +deadCount=deadCount+1 +end +end) +if deadCount==target:Count()then +isDead=true +end +end +return isDead +end,task:GetTarget() +) +return self +end +function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone) +local task=self +task:AddConditionSuccess( +function(target) +if target:IsInstanceOf("OPSZONE")then +return task:_CheckCaptureOpsZoneSuccess(target,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone or true) +elseif target:IsInstanceOf("SET_OPSZONE")then +local successes=0 +local isClientInZone=false +target:ForEachZone(function(opszone) +if task:_CheckCaptureOpsZoneSuccess(opszone,CaptureSquadGroupNamePrefix,Coalition,CheckClientInZone or true)then +successes=successes+1 +end +for _,client in ipairs(task:GetClientObjects())do +local clientCoord=client:GetCoordinate() +if opszone.zone:IsCoordinateInZone(clientCoord)then +isClientInZone=true +break +end +end +end) +return successes==target:Count()and isClientInZone +end +return false +end,task:GetTarget() +) +return self +end +function PLAYERTASK:AddReconSuccessCondition(MinDistance) +local task=self +task:AddConditionSuccess( +function(target) +local targetLocation=target:GetCoordinate() +local minD=MinDistance or UTILS.NMToMeters(5) +for _,client in ipairs(task:GetClientObjects())do +local clientCoord=client:GetCoordinate() +local distance=clientCoord:Get2DDistance(targetLocation) +local isLos=land.isVisible(clientCoord:GetVec3(),targetLocation:GetVec3()) +if distance0 and timer.getTime()-task.StartTime>TimeLimit +end) +return self +end function PLAYERTASK:AddNextTaskAfterSuccess(Task) self:T(self.lid.."AddNextTaskAfterSuccess") table.insert(self.NextTaskSuccess,Task) @@ -103987,6 +114390,11 @@ IsDone=true end return IsDone end +function PLAYERTASK:IsNotDone() +self:T(self.lid.."IsNotDone?") +local IsNotDone=not self:IsDone() +return IsNotDone +end function PLAYERTASK:HasClients() self:T(self.lid.."HasClients?") local hasclients=self:CountClients()>0 and true or false @@ -104147,6 +114555,10 @@ self:T({From,Event,To}) self:T(self.lid.."onafterStatus") local status=self:GetState() if status=="Stopped"then return self end +if self.TargetMarker then +local coordinate=self.Target:GetCoordinate() +self.TargetMarker:UpdateCoordinate(coordinate,0.5) +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 @@ -104249,13 +114661,13 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() -self.FinalState="Cancel" +self.FinalState="Cancelled" self:__Done(-1) return self end @@ -104267,7 +114679,7 @@ end if self.TargetMarker then self.TargetMarker:Remove() end -if self.TaskController.Scoring then +if self.TaskController and self.TaskController.Scoring then local clients,count=self:GetClientObjects() if count>0 then for _,_client in pairs(clients)do @@ -104295,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -104353,6 +114768,14 @@ InfoHasCoordinate=false, UseTypeNames=false, Scoring=nil, MenuNoTask=nil, +InformationMenu=false, +TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -104376,6 +114799,16 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.BOMBING]=100, [AUFTRAG.Type.BOMBRUNWAY]=100, [AUFTRAG.Type.CONQUER]=100, +[AUFTRAG.Type.RECON]=100, +[AUFTRAG.Type.ESCORT]=100, +[AUFTRAG.Type.CAP]=100, +[AUFTRAG.Type.CAPTUREZONE]=100, +} +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, } PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, @@ -104405,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -104465,6 +114901,7 @@ FRIGATE="Frigate", CRUISER="Cruiser", DESTROYER="Destroyer", CARRIER="Aircraft Carrier", +RADIOS="Radios", }, DE={ TASKABORT="Auftrag abgebrochen!", @@ -104488,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -104548,9 +114988,10 @@ FRIGATE="Fregatte", CRUISER="Kreuzer", DESTROYER="Zerstörer", CARRIER="Flugzeugträger", +RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.66" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -104568,13 +115009,14 @@ self.TargetQueue=FIFO:New() self.TaskQueue=FIFO:New() self.TasksPerPlayer=FIFO:New() self.PrecisionTasks=FIFO:New() +self.LasingDroneSet=SET_OPSGROUP:New() self.FlashPlayer={} self.AllowFlash=false self.lasttaskcount=0 self.taskinfomenu=false self.activehasinfomenu=false self.MenuName=nil -self.menuitemlimit=5 +self.menuitemlimit=6 self.holdmenutime=30 self.MarkerReadOnly=false self.repeatonfailed=true @@ -104588,6 +115030,8 @@ self.noflaresmokemenu=false self.illumenu=false self.ShowMagnetic=true self.UseTypeNames=false +self.InformationMenu=false +self.TaskInfoDuration=30 self.IsClientSet=false if ClientFilter and type(ClientFilter)=="table"and ClientFilter.ClassName and ClientFilter.ClassName=="SET_CLIENT"then self.ClientSet=ClientFilter @@ -104618,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -104660,6 +115117,11 @@ self:T(self.lid.."SetAllowFlashDirection") self.AllowFlash=OnOff return self end +function PLAYERTASKCONTROLLER:SetShowRadioInfoMenu(OnOff) +self:T(self.lid.."SetAllowRadioInfoMenu") +self.InformationMenu=OnOff +return self +end function PLAYERTASKCONTROLLER:SetDisableSmokeFlareTask() self:T(self.lid.."SetDisableSmokeFlareTask") self.noflaresmokemenu=true @@ -104693,7 +115155,7 @@ self.InfoHasCoordinate=OnOff self.InfoHasLLDDM=LLDDM return self end -function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign==false then self.ShortCallsign=false else @@ -104701,6 +115163,8 @@ self.ShortCallsign=true end self.Keepnumber=Keepnumber or false self.CallsignTranslations=CallsignTranslations +self.CallsignCustomFunc=CallsignCustomFunc +self.CallsignCustomArgs=arg or{} return self end function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) @@ -104722,45 +115186,63 @@ self.repeattimes=Repeats or 5 end return self end +function PLAYERTASKCONTROLLER:SetBriefingDuration(Seconds) +self:T(self.lid.."SetBriefingDuration") +self.TaskInfoDuration=Seconds or 30 +return self +end function PLAYERTASKCONTROLLER:_SendMessageToClients(Text,Seconds) self:T(self.lid.."_SendMessageToClients") local seconds=Seconds or 10 self.ClientSet:ForEachClient( function(Client) +if Client~=nil and Client:IsActive()then local m=MESSAGE:New(Text,seconds,"Tasking"):ToClient(Client) end +end ) return self end -function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) +function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) self:T(self.lid.."EnablePrecisionBombing") +if not self.LasingDroneSet then +self.LasingDroneSet=SET_OPSGROUP:New() +end +local LasingDrone 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 +LasingDrone=FlightGroup 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 +LasingDrone.playertask={} +LasingDrone.playertask.id=0 +LasingDrone.playertask.busy=false +LasingDrone.playertask.lasercode=LaserCode or 1688 +LasingDrone:SetLaser(LasingDrone.playertask.lasercode) +LasingDrone.playertask.template=LasingDrone:_GetTemplate(true) +LasingDrone.playertask.alt=Alt or 10000 +LasingDrone.playertask.speed=Speed or 120 +LasingDrone.playertask.maxtravel=UTILS.NMToMeters(MaxTravelDist or 50) +if LasingDrone:IsFlightgroup()then 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 Orbit=AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,Alt,Speed) +Orbit:SetMissionAltitude(Alt) +LasingDrone:AddMission(Orbit) +elseif LasingDrone:IsArmygroup()then local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.Coalition)) if HoldingPoint then BullsCoordinate=HoldingPoint end local Orbit=AUFTRAG:NewONGUARD(BullsCoordinate) -self.LasingDrone:AddMission(Orbit) +LasingDrone:AddMission(Orbit) end +self.LasingDroneSet:AddObject(FlightGroup) +elseif FlightGroup.ClassName and(FlightGroup.ClassName=="SET_OPSGROUP")then +FlightGroup:ForEachGroup( +function(group) +self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) +end +) else -self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!") +self:E(self.lid.."No OPSGROUP/SET_OPSGROUP object passed or object is not alive!") end else self.autolase=nil @@ -104768,6 +115250,10 @@ self.precisionbombing=false end return self end +function PLAYERTASKCONTROLLER:AddPrecisionBombingOpsGroup(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) +self:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) +return self +end function PLAYERTASKCONTROLLER:EnableBuddyLasing(Recce) self:T(self.lid.."EnableBuddyLasing") self.buddylasing=true @@ -104813,10 +115299,12 @@ 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) +if playergroup~=nil then +ttsplayername=playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local newplayername=self:_GetTextForSpeech(ttsplayername) self.customcallsigns[playername]=newplayername ttsplayername=newplayername +end else ttsplayername=self.customcallsigns[playername] end @@ -104844,7 +115332,7 @@ self.activehasinfomenu=InfoMenu or false if self.activehasinfomenu then self:EnableTaskInfoMenu() end -self.menuitemlimit=ItemLimit or 5 +self.menuitemlimit=ItemLimit+1 or 6 self.holdmenutime=HoldTime or 30 return self end @@ -104860,7 +115348,7 @@ 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.id==EVENTS.UnitLost or 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="" @@ -104907,7 +115395,7 @@ if EventData.IniGroup then if self.customcallsigns[playername]then self.customcallsigns[playername]=nil end -playername=EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber) +playername=EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) end playername=self:_GetTextForSpeech(playername) local text=string.format(switchtext,playername,self.MenuName or self.Name,freqtext) @@ -104942,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -105105,37 +115593,62 @@ return self end function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() self:T(self.lid.."_CheckPrecisionTasks") +self:T({count=self.PrecisionTasks:Count(),enabled=self.precisionbombing}) if self.PrecisionTasks:Count()>0 and self.precisionbombing then -if not self.LasingDrone or self.LasingDrone:IsDead()then +self.LasingDroneSet:ForEachGroup( +function(LasingDrone) +if not LasingDrone or 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) +if LasingDrone then +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 function SelectDrone(coord) +local selected=nil +local mindist=math.huge +local dist=math.huge +self.LasingDroneSet:ForEachGroup( +function(grp) +if grp.playertask and(not grp.playertask.busy)then +local gc=grp:GetCoordinate() +if coord and gc then +dist=coord:Get2DDistance(gc) +end +if dist%s",CoordText) end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then -if self.LasingDrone.playertask.inreach and self.LasingDrone:IsLasing()then +if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) ttstext=ttstext..lasingtext end @@ -105708,7 +116280,7 @@ else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then -local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) +local m=MESSAGE:New(text,self.TaskInfoDuration or 30,"Tasking"):ToClient(Client) end return self end @@ -105740,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -105749,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -105978,6 +116559,10 @@ if self.TaskQueue:Count()>0 and self.MenuNoTask~=nil then JoinTaskMenuTemplate:DeleteGenericEntry(self.MenuNoTask) self.MenuNoTask=nil end +if self.InformationMenu then +local radioinfo=self.gettext:GetEntry("RADIOS",self.locale) +JoinTaskMenuTemplate:NewEntry(radioinfo,self.JoinTopMenu,self._ShowRadioInfo,self) +end self.JoinTaskMenuTemplate=JoinTaskMenuTemplate return self end @@ -106108,6 +116693,24 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddConflictZone(ConflictZone) +self:T(self.lid.."AddConflictZone") +if self.Intel then +self.Intel:AddConflictZone(ConflictZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddConflictZoneSet(ConflictZoneSet) +self:T(self.lid.."AddConflictZoneSet") +if self.Intel then +self.Intel.conflictzoneset:AddSet(ConflictZoneSet) +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 @@ -106117,7 +116720,7 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end -function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone) +function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone) self:T(self.lid.."RemoveRejectZone") if self.Intel then self.Intel:RemoveRejectZone(RejectZone) @@ -106126,6 +116729,58 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone) +self:T(self.lid.."RemoveConflictZone") +if self.Intel then +self.Intel:RemoveConflictZone(ConflictZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -106210,9 +116865,9 @@ NewContact(Contact) end return self end -function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate) +function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) self:T(self.lid.."SetSRS") -self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" +self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" 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 @@ -106225,7 +116880,7 @@ 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=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend) self.SRS:SetCoalition(self.Coalition) self.SRS:SetLabel(self.MenuName or self.Name) self.SRS:SetGender(self.Gender) @@ -106257,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0.7 then camera=false end +elseif string.find(typename,"OH58")then +local dcsunit=Unit.getByName(client:GetName()) +local vivihorizontal=dcsunit:getDrawArgumentValue(528)or 0 +if vivihorizontal<-0.527 or vivihorizontal>0.527 then +camera=false +end elseif string.find(typename,"Ka-50")then camera=true end end return camera end +function PLAYERRECCE:_GetKiowaMMSSight(Kiowa) +self:T(self.lid.."_GetKiowaMMSSight") +local unit=Kiowa +if unit and unit:IsAlive()then +local dcsunit=Unit.getByName(Kiowa:GetName()) +local mmshorizontal=dcsunit:getDrawArgumentValue(528)or 0 +local mmsvertical=dcsunit:getDrawArgumentValue(527)or 0 +self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical)) +local mmson=true +if mmshorizontal<-0.527 or mmshorizontal>0.527 then mmson=false end +local horizontalview=mmshorizontal/0.527*190 +local heading=unit:GetHeading() +local mmsheading=(heading+horizontalview)%360 +local mmsyaw=math.atan(mmsvertical)*40 +local maxview=self:_GetActualMaxLOSight(unit,mmsheading,mmsyaw,not mmson) +if maxview>8000 then maxview=8000 end +self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson))) +return mmsheading,mmsyaw,maxview,mmson +end +return 0,0,0,false +end function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) self:T(self.lid.."GetGazelleVivianneSight") local unit=Gazelle @@ -106647,33 +117450,11 @@ vivihorizontal=0.67 vivioff=true return 0,0,0,false end -vivivertical=vivivertical/1.10731 local horizontalview=vivihorizontal*-180 -local verticalview=vivivertical*30 +local verticalview=math.atan(vivivertical) 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 @@ -106686,20 +117467,20 @@ 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 CamHeight=self.Cameraheight[typename]or 1 +if vnod<-2 then local beta=90 -local gamma=math.floor(90-vnod) -local alpha=math.floor(180-beta-gamma) +local gamma=90-math.abs(vnod) +local alpha=90-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) +return math.ceil(math.abs(maxview)) end -function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign==false then self.ShortCallsign=false else @@ -106707,6 +117488,8 @@ self.ShortCallsign=true end self.Keepnumber=Keepnumber or false self.CallsignTranslations=CallsignTranslations +self.CallsignCustomFunc=CallsignCustomFunc +self.CallsignCustomArgs=arg or{} return self end function PLAYERRECCE:_GetViewZone(unit,vheading,minview,maxview,angle,camon,laser) @@ -106794,22 +117577,30 @@ local finalcount=0 local minview=0 local typename=unit:GetTypeName() local playername=unit:GetPlayerName() -local maxview=self.MaxViewDistance[typename]or 5000 +local maxview=self.MaxViewDistance[typename]or 8000 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 +maxview=self.MaxViewDistance[typename]or 8000 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 +maxview=self.MaxViewDistance[typename]or 8000 +elseif string.find(typename,"OH58")and camera then +nod,maxview,camon=0,8000,true +heading,nod,maxview,camon=self:_GetKiowaMMSSight(unit) +angle=8 +if maxview==0 then +maxview=self.MaxViewDistance[typename]or 8000 +end else heading=unit:GetHeading() -nod,maxview,camon=10,1000,true +nod,maxview,camon=10,3000,true +maxview=self.MaxViewDistance[typename]or 3000 angle=45 end if laser then @@ -106898,7 +117689,8 @@ 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:T(self.lid.."Target Life Points: "..target:GetLife()or"none") +if target:IsDead()or target:IsDestroyed()or target:GetDamage()>79 or target:GetLife()<=1 then self:__Shack(-1,client,oldtarget) else self:__TargetLOSLost(-1,client,oldtarget) @@ -107139,6 +117931,9 @@ 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)) +if self.reporttostringbullsonly~=true then +report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) +end else report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) end @@ -107175,8 +117970,14 @@ 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)) +if self.reporttostringbullsonly~=true then +report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) +end else report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) +if self.reporttostringbullsonly~=true then +report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) +end end report:Add(string.rep("-",15)) local text=report:Text() @@ -107200,6 +118001,7 @@ for _,_client in pairs(clientset)do local client=_client if client and client:IsAlive()then local playername=client:GetPlayerName() +self:T("Menu for "..playername) if not self.UnitLaserCodes[playername]then self:_SetClientLaserCode(nil,nil,playername,1688) end @@ -107208,6 +118010,7 @@ self.SmokeOwn[playername]=self.smokeownposition end local group=client:GetGroup() if not self.ClientMenus[playername]then +self:T("Start Menubuild for "..playername) 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" @@ -107323,9 +118126,9 @@ self:TargetDetected(targetsbyclock,client,playername) end return self end -function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) +function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) self:T(self.lid.."SetSRS") -self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" +self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" 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 @@ -107344,6 +118147,9 @@ self.SRS:SetGender(self.Gender) self.SRS:SetCulture(self.Culture) self.SRS:SetPort(self.Port) self.SRS:SetVolume(self.Volume) +if Backend then +self.SRS:SetBackend(Backend) +end if self.PathToGoogleKey then self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey) self.SRS:SetProvider(MSRS.Provider.GOOGLE) @@ -107369,11 +118175,21 @@ self:T(self.lid.."SetMenuName: "..Name) self.MenuName=Name return self end +function PLAYERRECCE:SetReportBullsOnly(OnOff) +self:T(self.lid.."SetReportBullsOnly: "..tostring(OnOff)) +self.reporttostringbullsonly=OnOff +return self +end function PLAYERRECCE:EnableSmokeOwnPosition() self:T(self.lid.."EnableSmokeOwnPosition") self.smokeownposition=true return self end +function PLAYERRECCE:EnableKiowaAutolase() +self:T(self.lid.."EnableKiowaAutolase") +self.CanLase.OH58D=true +return self +end function PLAYERRECCE:DisableSmokeOwnPosition() self:T(self.lid.."DisableSmokeOwnPosition") self.smokeownposition=false @@ -107481,7 +118297,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -107490,7 +118306,7 @@ coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Setti 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) +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) @@ -107512,7 +118328,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -107628,7 +118444,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -107661,7 +118477,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -107694,7 +118510,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -107727,7 +118543,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition,Settings) @@ -107765,7 +118581,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition,Settings) @@ -107803,7 +118619,7 @@ 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 callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings=(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS local coord=Client:GetCoordinate() local coordtext=coord:ToStringBULLS(self.Coalition,Settings) @@ -108036,7 +118852,7 @@ DEAD="Dead", DAMAGED="Damaged", } _TARGETID=0 -TARGET.version="0.6.0" +TARGET.version="0.7.1" function TARGET:New(TargetObject) local self=BASE:Inherit(self,FSM:New()) _TARGETID=_TARGETID+1 @@ -108058,7 +118874,7 @@ self:AddTransition("*","ObjectDead","*") self:AddTransition("*","Damaged","Damaged") self:AddTransition("*","Destroyed","Dead") self:AddTransition("*","Dead","Dead") -self:__Start(-1) +self:__Start(-0.1) return self end function TARGET:AddObject(Object) @@ -108165,6 +118981,11 @@ function TARGET:IsAlive() for _,_target in pairs(self.targets)do local target=_target if target.Status~=TARGET.ObjectStatus.DEAD then +if self.isDestroyed then +self:E(self.lid..string.format("ERROR: target is DESTROYED but target object status is not DEAD but %s for object %s",target.Status,target.Name)) +elseif self:IsDead()then +self:E(self.lid..string.format("ERROR: target is DEAD but target object status is not DEAD but %s for object %s",target.Status,target.Name)) +end return true end end @@ -108177,6 +118998,14 @@ function TARGET:IsDead() local is=self:Is("Dead") return is end +function TARGET:IsTargetDead(TargetObject) +local isDead=TargetObject.Status==TARGET.ObjectStatus.DEAD +return isDead +end +function TARGET:IsTargetAlive(TargetObject) +local isAlive=TargetObject.Status==TARGET.ObjectStatus.ALIVE +return isAlive +end function TARGET:onafterStart(From,Event,To) self:T({From,Event,To}) local text=string.format("Starting Target") @@ -108188,7 +119017,6 @@ 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 @@ -108202,11 +119030,10 @@ 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 @@ -108216,11 +119043,12 @@ 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()) +local text=string.format("%s: Targets=%d/%d [%d, %d], Life=%.1f/%.1f, Damage=%.1f", +fsmstate,self:CountTargets(),self.N0,self.Ndestroyed,self.Ndead,self:GetLife(),self:GetLife0(),self:GetDamage()) if self:CountTargets()==0 or self:GetDamage()>=100 then -text=text.." Dead!" +text=text.." - Dead!" elseif damaged then -text=text.." Damaged!" +text=text.." - Damaged!" end self:I(self.lid..text) end @@ -108229,15 +119057,27 @@ 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) +text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f, N0=%d, Ndestroyed=%d, Ndead=%d", +i,target.Type,target.Name,target.Status,target.Life,target.Life0,damage,target.N0,target.Ndestroyed,target.Ndead) end self:I(self.lid..text) end -if self:CountTargets()==0 or self:GetDamage()>=100 then +if self:IsAlive()and(self:CountTargets()==0 or self:GetDamage()>=100)then self:Dead() end +for i,_target in pairs(self.targets)do +local target=_target +if target.Ndestroyed>target.N0 then +self:E(self.lid..string.format("ERROR: Number of destroyed target objects greater than number of initial target objects: %d>%d!",target.Ndestroyed,target.N0)) +end +if target.Ndestroyed>target.N0 then +self:E(self.lid..string.format("ERROR: Number of dead target objects greater than number of initial target objects: %d>%d!",target.Ndead,target.N0)) +end +end if self:IsAlive()then self:__Status(-self.TStatus) +else +self:I(self.lid..string.format("Target is not alive any more ==> no further status updates are carried out")) end return self end @@ -108250,6 +119090,8 @@ 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 +Target.Ndestroyed=Target.Ndestroyed+1 +Target.Life=0 self:ObjectDead(Target) return self end @@ -108257,12 +119099,15 @@ 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 +Target.Ndead=Target.Ndead+1 +Target.Life=0 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 +break end end if dead then @@ -108303,14 +119148,16 @@ if not target then target=self:GetTargetByName(EventData.IniUnitName) end if target then +local Ndead=target.Ndead +local Ndestroyed=target.Ndestroyed if EventData.id==EVENTS.RemoveUnit then -target.Ndead=target.Ndead+1 +Ndead=Ndead+1 else -target.Ndestroyed=target.Ndestroyed+1 -target.Ndead=target.Ndead+1 +Ndestroyed=Ndestroyed+1 +Ndead=Ndead+1 end -if target.Ndead==target.N0 then -if target.Ndestroyed>=target.N0 then +if Ndead==target.N0 then +if 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) @@ -108394,6 +119241,7 @@ target.Name=coord:ToStringMGRS() target.Coordinate=coord target.Life0=1 target.Life=1 +target.N0=target.N0+1 elseif Object:IsInstanceOf("ZONE_BASE")then local zone=Object Object=zone @@ -108402,6 +119250,7 @@ target.Name=zone:GetName() target.Coordinate=zone:GetCoordinate() target.Life0=1 target.Life=1 +target.N0=target.N0+1 elseif Object:IsInstanceOf("OPSZONE")then local zone=Object Object=zone @@ -108524,9 +119373,23 @@ return 0 elseif Target.Type==TARGET.ObjectType.COORDINATE then return 0 elseif Target.Type==TARGET.ObjectType.ZONE then -return 0 +local zone=Target.Object +local foundunits={} +if zone:IsInstanceOf("ZONE_RADIUS")or zone:IsInstanceOf("ZONE_POLYGON")then +zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}) +foundunits=zone:GetScannedSetUnit() +else +foundunits=SET_UNIT:New():FilterZones({zone}):FilterOnce() +end +local ThreatMax=foundunits:GetThreatLevelMax()or 0 +return ThreatMax +elseif Target.Type==TARGET.ObjectType.OPSZONE then +local unitset=Target.Object:GetScannedUnitSet() +local ThreatMax=unitset:GetThreatLevelMax() +return ThreatMax else self:E("ERROR: unknown target object type in GetTargetThreatLevel!") +return 0 end return self end @@ -108747,6 +119610,17 @@ end self:E(self.lid..string.format("ERROR: Cannot get average coordinate of target %s",tostring(self.name))) return nil end +function TARGET:GetCoordinates() +local coordinates={} +for _,_target in pairs(self.targets)do +local target=_target +local coordinate=self:GetTargetCoordinate(target) +if coordinate then +table.insert(coordinates,coordinate) +end +end +return coordinates +end function TARGET:GetHeading() for _,_target in pairs(self.targets)do local Target=_target @@ -108876,7 +119750,15 @@ return target.Object end return nil end -function TARGET:CountObjectives(Target,Coalitions) +function TARGET:GetObjects() +local objects={} +for _,_target in pairs(self.targets)do +local target=_target +table.insert(objects,target.Object) +end +return objects +end +function TARGET:CountObjectives(Target,Coalitions,OnlyReallyAlive) local N=0 if Target.Type==TARGET.ObjectType.GROUP then local target=Target.Object @@ -108915,7 +119797,13 @@ N=N+1 end end elseif Target.Type==TARGET.ObjectType.COORDINATE then +if not OnlyReallyAlive then +N=N+1 +end elseif Target.Type==TARGET.ObjectType.ZONE then +if not OnlyReallyAlive then +N=N+1 +end elseif Target.Type==TARGET.ObjectType.OPSZONE then local target=Target.Object if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetOwner())then @@ -108926,11 +119814,11 @@ self:E(self.lid.."ERROR: Unknown target type! Cannot count targets") end return N end -function TARGET:CountTargets(Coalitions) +function TARGET:CountTargets(Coalitions,OnlyReallyAlive) local N=0 for _,_target in pairs(self.targets)do local Target=_target -N=N+self:CountObjectives(Target,Coalitions) +N=N+self:CountObjectives(Target,Coalitions,OnlyReallyAlive) end return N end @@ -108973,7 +119861,7 @@ capdir=45, capleg=15, maxinterceptsize=2, missionrange=100, -noaltert5=4, +noalert5=4, ManagedAW={}, ManagedSQ={}, ManagedCP={}, @@ -108986,23 +119874,34 @@ engagerange=50, repeatsonfailure=3, GoZoneSet=nil, NoGoZoneSet=nil, +ConflictZoneSet=nil, Monitor=false, TankerInvisible=true, CapFormation=nil, ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Air"}, } -EASYGCICAP.version="0.1.10" +EASYGCICAP.version="0.1.33" 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.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED self.wings={} -self.EWRName=EWRName or self.coalitionname.." EWR" +if type(EWRName)=="string"then EWRName={EWRName}end +self.EWRName=EWRName self.airbasename=AirbaseName self.airbase=AIRBASE:FindByName(self.airbasename) self.GoZoneSet=SET_ZONE:New() self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() self.resurrection=900 self.capspeed=300 self.capalt=25000 @@ -109010,13 +119909,21 @@ self.capdir=90 self.capleg=15 self.capgrouping=2 self.missionrange=100 -self.noaltert5=2 +self.noalert5=2 self.MaxAliveMissions=8 self.engagerange=50 self.repeatsonfailure=3 self.Monitor=false self.TankerInvisible=true self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Air"} self.lid=string.format("EASYGCICAP %s | ",self.alias) self:SetStartState("Stopped") self:AddTransition("Stopped","Start","Running") @@ -109027,6 +119934,55 @@ self:I(self.lid.."Created new instance (v"..self.version..")") self:__Start(math.random(6,12)) return self end +function EASYGCICAP:GetAirwing(AirbaseName) +self:T(self.lid.."GetAirwing") +if self.wings[AirbaseName]then +return self.wings[AirbaseName][1] +end +return nil +end +function EASYGCICAP:AddAgent(Group) +self:T(self.lid.."AddAgent") +if Group:IsInstanceOf("GROUP")and self.Intel~=nil then +self.Intel:AddAgent(Group) +if self.TankerInvisible==true then +Group:SetCommandInvisible(true) +Group:OptionROEHoldFire() +if Group:IsAir()then +Group:OptionROTEvadeFire() +else +Group:OptionDisperseOnAttack(30) +end +end +end +return self +end +function EASYGCICAP:GetAirwingTable() +self:T(self.lid.."GetAirwingTable") +local Wingtable={} +for _,_object in pairs(self.wings or{})do +table.insert(Wingtable,_object[1]) +end +return Wingtable +end +function EASYGCICAP:SetFuelLow(Percent) +self:T(self.lid.."SetFuelLow") +self.FuelLowThreshold=Percent or 25 +return self +end +function EASYGCICAP:ShowPatrolPointMarkers(onoff) +if onoff then +self.showpatrolpointmarks=true +else +self.showpatrolpointmarks=false +end +return self +end +function EASYGCICAP:SetFuelCritical(Percent) +self:T(self.lid.."SetFuelCritical") +self.FuelCriticalThreshold=Percent or 10 +return self +end function EASYGCICAP:SetCAPFormation(Formation) self:T(self.lid.."SetCAPFormation") self.CapFormation=Formation @@ -109037,8 +119993,18 @@ self:T(self.lid.."SetTankerAndAWACSInvisible") self.TankerInvisible=Switch return self end +function EASYGCICAP:_CountAliveAuftrags() +local alive=0 +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +alive=alive+1 +end +end +return alive +end function EASYGCICAP:SetMaxAliveMissions(Maxiumum) -self:T(self.lid.."SetDefaultResurrection") +self:T(self.lid.."SetMaxAliveMissions") self.MaxAliveMissions=Maxiumum or 8 return self end @@ -109052,6 +120018,11 @@ self:T(self.lid.."SetDefaultRepeatOnFailure") self.repeatsonfailure=Retries or 3 return self end +function EASYGCICAP:SetDefaultTakeOffType(Takeoff) +self:T(self.lid.."SetDefaultTakeOffType") +self.defaulttakeofftype=Takeoff or"hot" +return self +end function EASYGCICAP:SetDefaultCAPSpeed(Speed) self:T(self.lid.."SetDefaultSpeed") self.capspeed=Speed or 300 @@ -109082,13 +120053,13 @@ 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 +function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes) +self:T(self.lid.."SetDefaultNumberAlert5Standby") +self.noalert5=math.abs(Airframes)or 2 return self end function EASYGCICAP:SetDefaultEngageRange(Range) -self:T(self.lid.."SetDefaultNumberAlter5Standby") +self:T(self.lid.."SetDefaultEngageRange") self.engagerange=Range or 50 return self end @@ -109097,11 +120068,27 @@ self:T(self.lid.."SetDefaultOverhead") self.overhead=Overhead or 0.75 return self end +function EASYGCICAP:SetDefaultDespawnAfterLanding() +self:T(self.lid.."SetDefaultDespawnAfterLanding") +self.DespawnAfterLanding=true +self.DespawnAfterHolding=false +return self +end +function EASYGCICAP:SetDefaultDespawnAfterHolding() +self:T(self.lid.."SetDefaultDespawnAfterLanding") +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +return self +end function EASYGCICAP:SetCapStartTimeVariation(Start,End) self.capOptionVaryStartTime=Start or 5 self.capOptionVaryEndTime=End or 60 return self end +function EASYGCICAP:SetCAPEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Air"} +return self +end function EASYGCICAP:AddAirwing(Airbasename,Alias) self:T(self.lid.."AddAirwing "..Airbasename) local AWEntry={} @@ -109123,6 +120110,13 @@ end function EASYGCICAP:_AddAirwing(Airbasename,Alias) self:T(self.lid.."_AddAirwing "..Airbasename) local CapFormation=self.CapFormation +local DespawnAfterLanding=self.DespawnAfterLanding +local DespawnAfterHolding=self.DespawnAfterHolding +local check=STATIC:FindByName(Airbasename,false)or UNIT:FindByName(Airbasename) +if check==nil then +MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end local CAP_Wing=AIRWING:New(Airbasename,Alias) CAP_Wing:SetVerbosityLevel(0) CAP_Wing:SetReportOff() @@ -109131,6 +120125,9 @@ CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename)) CAP_Wing:SetRespawnAfterDestroyed() CAP_Wing:SetNumberCAP(self.capgrouping) CAP_Wing:SetCapCloseRaceTrack(true) +if self.showpatrolpointmarks then +CAP_Wing:ShowPatrolPointMarkers(true) +end if self.capOptionVaryStartTime then CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime) end @@ -109147,22 +120144,37 @@ end if#self.ManagedREC>0 then CAP_Wing:SetNumberRecon(1) end -CAP_Wing:SetTakeoffHot() +CAP_Wing:SetTakeoffType(self.defaulttakeofftype) 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 engagerange=self.engagerange +local GoZoneSet=self.GoZoneSet +local NoGoZoneSet=self.NoGoZoneSet +local FuelLow=self.FuelLowThreshold or 25 +local FuelCritical=self.FuelCriticalThreshold or 10 +local EngageTypes=self.EngageTargetTypes or{"Air"} +function CAP_Wing:onbeforeFlightOnMission(From,Event,To,Flightgroup,Mission) local flightgroup=Flightgroup +if DespawnAfterLanding then +flightgroup:SetDespawnAfterLanding() +elseif DespawnAfterHolding then flightgroup:SetDespawnAfterHolding() +end flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename)) flightgroup:GetGroup():CommandEPLRS(true,5) flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch() +flightgroup:GetGroup():SetOptionLandingOverheadBreak() 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:SetEngageDetectedOn(engagerange,EngageTypes,GoZoneSet,NoGoZoneSet) flightgroup:SetOutOfAAMRTB() +flightgroup:SetFuelLowRTB(true) +flightgroup:SetFuelLowThreshold(FuelLow) +flightgroup:SetFuelCriticalRTB(true) +flightgroup:SetFuelCriticalThreshold(FuelCritical) if CapFormation then flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation) end @@ -109178,31 +120190,44 @@ end flightgroup:GetGroup():OptionROTEvadeFire() flightgroup:SetFuelLowRTB(true) Intel:AddAgent(flightgroup) -function flightgroup:OnAfterHolding(From,Event,To) +if DespawnAfterHolding then +function flightgroup:onbeforeHolding(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) +end +if self.noalert5>0 then +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end +alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) +table.insert(self.ListOfAuftrag,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()) +self:T(self.lid.."AddPatrolPointCAP") +local coordinate=Coordinate local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end EntryCAP.AirbaseName=AirbaseName -EntryCAP.Coordinate=Coordinate +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() +local mark=MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll() end return self end @@ -109255,6 +120280,11 @@ function EASYGCICAP:_SetTankerPatrolPoints() self:T(self.lid.."_SetTankerPatrolPoints") for _,_data in pairs(self.ManagedTK)do local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create a TANKER point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end local Wing=self.wings[data.AirbaseName][1] local Coordinate=data.Coordinate local Altitude=data.Altitude @@ -109269,6 +120299,11 @@ function EASYGCICAP:_SetAwacsPatrolPoints() self:T(self.lid.."_SetAwacsPatrolPoints") for _,_data in pairs(self.ManagedEWR)do local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create an AWACS point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end local Wing=self.wings[data.AirbaseName][1] local Coordinate=data.Coordinate local Altitude=data.Altitude @@ -109283,20 +120318,35 @@ function EASYGCICAP:_SetCAPPatrolPoints() self:T(self.lid.."_SetCAPPatrolPoints") for _,_data in pairs(self.ManagedCP)do local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end 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 +local Zone=_data.Zone +if Zone then +Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength) +else Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) end +end return self end function EASYGCICAP:_SetReconPatrolPoints() self:T(self.lid.."_SetReconPatrolPoints") for _,_data in pairs(self.ManagedREC)do local data=_data +self:T("Airbasename = "..data.AirbaseName) +if not self.wings[data.AirbaseName]then +MESSAGE:New(self.lid.."You are trying to create a RECON point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() +return +end local Wing=self.wings[data.AirbaseName][1] local Coordinate=data.Coordinate local Altitude=data.Altitude @@ -109434,7 +120484,9 @@ Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetMissionRange(self.missionrange) Squadron_One:SetRadio(Frequency,Modulation) +if TACAN then Squadron_One:AddTacanChannel(TACAN,TACAN) +end local wing=self.wings[AirbaseName][1] wing:AddSquadron(Squadron_One) wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75) @@ -109457,7 +120509,7 @@ wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.AWACS},75) return self end function EASYGCICAP:AddAcceptZone(Zone) -self:T(self.lid.."AddAcceptZone0") +self:T(self.lid.."AddAcceptZone") self.GoZoneSet:AddZone(Zone) return self end @@ -109466,20 +120518,53 @@ self:T(self.lid.."AddRejectZone") self.NoGoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:AddConflictZone(Zone) +self:T(self.lid.."AddConflictZone") +self.ConflictZoneSet:AddZone(Zone) +self.GoZoneSet:AddZone(Zone) +return self +end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) -self:I("_TryAssignIntercept for size "..WingSize or 1) +self:T("_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())) +self:T(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)) +self:T(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 @@ -109493,7 +120578,7 @@ FG:AddMission(InterceptAuftrag) local cm=FG:GetMissionCurrent() if cm then cm:Cancel()end wingsize=wingsize-1 -self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) +self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) if wingsize==0 then assigned=true break @@ -109510,8 +120595,9 @@ local maxsize=self.maxinterceptsize local repeatsonfailure=self.repeatsonfailure local wings=self.wings local ctlpts=self.ManagedCP -local MaxAliveMissions=self.MaxAliveMissions*self.capgrouping +local MaxAliveMissions=self.MaxAliveMissions local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet local ReadyFlightGroups=self.ReadyFlightGroups if Cluster.ctype~=INTEL.Ctype.AIRCRAFT then return end local contact=self.Intel:GetHighestThreatContact(Cluster) @@ -109535,9 +120621,11 @@ local airwing=_data[1] local zone=_data[2] local zonecoord=zone:GetCoordinate() local name=_data[3] +local coa=AIRBASE:FindByName(name):GetCoalition() local distance=position:DistanceFromPointVec2(zonecoord) local airframes=airwing:CountAssets(true) -if distance=wingsize then +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true then bestdistance=distance targetairwing=airwing targetawname=name @@ -109547,10 +120635,15 @@ for _,_data in pairs(ctlpts)do local data=_data local name=data.AirbaseName local zonecoord=data.Coordinate +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false local distance=position:DistanceFromPointVec2(zonecoord) local airframes=airwing:CountAssets(true) -if distance=wingsize then +if distance=wingsize and samecoalitionab==true then bestdistance=distance targetairwing=airwing targetawname=name @@ -109560,8 +120653,9 @@ 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) +local missioncount=self:_CountAliveAuftrags() self:T(self.lid.." Assets on Mission "..AssetCount) -if AssetCount<=MaxAliveMissions then +if missioncount0 then InterceptAuftrag:AddConditionSuccess( -function(group,zoneset) +function(group,zoneset,conflictset) local success=false if group and group:IsAlive()then local coord=group:GetCoordinate() -if coord and zoneset:IsCoordinateInZone(coord)then +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then success=true end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true end return success end, contact.group, -nogozoneset +nogozoneset, +conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then InterceptAuftrag:SetRequiredAssets(rest) @@ -109600,14 +120708,21 @@ end function EASYGCICAP:_StartIntel() self:T(self.lid.."_StartIntel") local BlueAir_DetectionSetGroup=SET_GROUP:New() -BlueAir_DetectionSetGroup:FilterPrefixes({self.EWRName}) +BlueAir_DetectionSetGroup:FilterPrefixes(self.EWRName) BlueAir_DetectionSetGroup:FilterStart() -local BlueIntel=INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname,self.EWRName) +local BlueIntel=INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname,self.alias) BlueIntel:SetClusterAnalysis(true,false,false) BlueIntel:SetForgetTime(300) BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -109615,7 +120730,7 @@ end local function AssignCluster(Cluster) self:_AssignIntercept(Cluster) end -function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster) +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) AssignCluster(Cluster) end self.Intel=BlueIntel @@ -109640,6 +120755,19 @@ return self end function EASYGCICAP:onafterStatus(From,Event,To) self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end local function counttable(tbl) local count=0 for _,_data in pairs(tbl)do @@ -109667,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -109677,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -109689,12 +120818,14 @@ 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.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission 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 +text=text.."\nSelf managed Missions:" +text=text.."\n - Mission Limit: "..self.MaxAliveMissions +text=text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags() MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug) end self:__Status(30) @@ -109703,6 +120834,480 @@ end function EASYGCICAP:onafterStop(From,Event,To) self:T({From,Event,To}) self.Intel:Stop() +for _,_wing in pairs(self.wings or{})do +_wing:Stop() +end +return self +end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) return self end AI_BALANCER={ @@ -109767,7 +121372,7 @@ 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 PointVec2=COORDINATE:New(AIGroup:GetVec2().x,0,AIGroup:GetVec2().y) local ClosestAirbase=self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2(PointVec2) self:T(ClosestAirbase.AirbaseName) AIGroup:RouteRTB(ClosestAirbase) @@ -110052,15 +121657,15 @@ return end local FromRTBRoutePoint=FromCoord:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, RTBSpeed, true ) local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, RTBSpeed, true ) @@ -110109,8 +121714,8 @@ 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) +local FromRefuelRoutePoint=FromRefuelCoord:WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToRefuelSpeed,true) +local ToRefuelRoutePoint=Tanker:GetCoordinate():WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToRefuelSpeed,true) self:F({ToRefuelSpeed=ToRefuelSpeed}) RefuelRoute[#RefuelRoute+1]=FromRefuelRoutePoint RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint @@ -110220,7 +121825,7 @@ 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) +local FromWP=CurrentCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToTargetSpeed,true) PatrolRoute[#PatrolRoute+1]=FromWP if self.racetrack then local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax) @@ -110243,7 +121848,7 @@ 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) +local ToWP=ToTargetCoord:WaypointAir(self.PatrolAltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,ToTargetSpeed,true) PatrolRoute[#PatrolRoute+1]=ToWP local Tasks={} Tasks[#Tasks+1]=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self) @@ -110346,12 +121951,12 @@ 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) +local FromWP=DefenderCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.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) +local ToWP=ToCoord:WaypointAir(self.PatrolAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true) EngageRoute[#EngageRoute+1]=ToWP AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___EngageRoute",self,AttackSetUnit) EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks) @@ -110394,12 +121999,12 @@ 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) +local FromWP=DefenderCoord:WaypointAir(self.EngageAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.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) +local ToWP=ToCoord:WaypointAir(self.EngageAltType or"RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,EngageSpeed,true) EngageRoute[#EngageRoute+1]=ToWP if TargetDistance<=EngageDistance*9 then local AttackUnitTasks=self:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) @@ -113179,10 +124784,14 @@ local Dispatcher=AI_A2G_Fsm:GetDispatcher() local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) if Squadron then local FirstUnit=AttackSetUnit:GetRandomSurely() +if FirstUnit then local Coordinate=FirstUnit:GetCoordinate() if self.SetSendPlayerMessages then Dispatcher:MessageToPlayers(Squadron,DefenderName..", on route to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) end +else +return +end end self:GetParent(self).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit) end @@ -113942,12 +125551,12 @@ 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 CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) local ToPatrolZoneSpeed=self.PatrolMaxSpeed local CurrentRoutePoint=CurrentPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TakeOffParking, -POINT_VEC3.RoutePointAction.FromParkingArea, +COORDINATE.WaypointType.TakeOffParking, +COORDINATE.WaypointAction.FromParkingArea, ToPatrolZoneSpeed, true ) @@ -113957,12 +125566,12 @@ 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 CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) local ToPatrolZoneSpeed=self.PatrolMaxSpeed local CurrentRoutePoint=CurrentPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, ToPatrolZoneSpeed, true ) @@ -113973,11 +125582,11 @@ 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 ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true ) @@ -114003,13 +125612,21 @@ local OrbitTask=OldAIControllable:TaskOrbitCircle(math.random(self.PatrolFloorAl local TimedOrbitTask=OldAIControllable:TaskControlled(OrbitTask,OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil)) OldAIControllable:SetTask(TimedOrbitTask,10) RTB=true -else end local Damage=self.Controllable:GetLife() if Damage<=self.PatrolDamageThreshold then self:T(self.Controllable:GetName().." is damaged:"..Damage..", RTB!") RTB=true end +if self:IsInstanceOf("AI_CAS")or self:IsInstanceOf("AI_BAI")then +local atotal,shells,rockets,bombs,missiles=self.Controllable:GetAmmunition() +local arelevant=rockets+bombs +if arelevant==0 or missiles==0 then +RTB=true +self:T({total=atotal,shells=shells,rockets=rockets,bombs=bombs,missiles=missiles}) +self:T(self.Controllable:GetName().." is out of ammo, RTB!") +end +end if RTB==true then self:RTB() else @@ -114026,12 +125643,12 @@ local PatrolRoute={} 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 CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) local ToPatrolZoneSpeed=self.PatrolMaxSpeed local CurrentRoutePoint=CurrentPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, ToPatrolZoneSpeed, true ) @@ -114133,12 +125750,12 @@ 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 CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) local ToEngageZoneSpeed=self.PatrolMaxSpeed local CurrentRoutePoint=CurrentPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, ToEngageZoneSpeed, true ) @@ -114148,11 +125765,11 @@ 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 ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) local ToPatrolRoutePoint=ToTargetPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true ) @@ -114296,12 +125913,12 @@ 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 CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) local ToEngageZoneSpeed=self.PatrolMaxSpeed local CurrentRoutePoint=CurrentPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -114328,11 +125945,11 @@ AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAS_ZONE.EngageRoute", 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 ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -114467,12 +126084,12 @@ 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 CurrentPointVec3=COORDINATE:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) local ToEngageZoneSpeed=self.PatrolMaxSpeed local CurrentRoutePoint=CurrentPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -114512,11 +126129,11 @@ 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 ToTargetPointVec3=COORDINATE:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( self.PatrolAltType, -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -114585,7 +126202,7 @@ Attack="A", Reconnaissance="R", } AI_FORMATION.__Enum.ReportType={ -Airborne="*", +All="*", Airborne="A", GroundRadar="R", Ground="G", @@ -114644,7 +126261,7 @@ FollowGroupSet:Flush(self) local FollowSet=FollowGroupSet:GetSet() local i=1 for FollowID,FollowGroup in pairs(FollowSet)do -local PointVec3=POINT_VEC3:New() +local PointVec3=COORDINATE:New() PointVec3:SetX(XStart+i*XSpace) PointVec3:SetY(YStart+i*YSpace) PointVec3:SetZ(ZStart+i*ZSpace) @@ -114683,7 +126300,7 @@ function AI_FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XS local FollowSet=FollowGroupSet:GetSet() local i=0 for FollowID,FollowGroup in pairs(FollowSet)do -local PointVec3=POINT_VEC3:New() +local PointVec3=COORDINATE:New() local Side=(i%2==0)and 1 or-1 local Row=i/2+1 PointVec3:SetX(XStart+Row*XSpace) @@ -114704,7 +126321,7 @@ function AI_FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XS local FollowSet=FollowGroupSet:GetSet() local i=0 for FollowID,FollowGroup in pairs(FollowSet)do -local PointVec3=POINT_VEC3:New() +local PointVec3=COORDINATE:New() local ZIndex=i%ZLevels local XIndex=math.floor(i/ZLevels) local YIndex=math.floor(i/ZLevels) @@ -114855,7 +126472,6 @@ 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} @@ -116897,8 +128513,8 @@ local landheight=CoordinateTo:GetLandHeight() CoordinateTo.y=landheight+50 local WaypointTo=CoordinateTo:WaypointAir( "RADIO", -POINT_VEC3.RoutePointType.TurningPoint, -POINT_VEC3.RoutePointAction.TurningPoint, +COORDINATE.WaypointType.TurningPoint, +COORDINATE.WaypointAction.TurningPoint, 50, true ) @@ -116924,7 +128540,7 @@ 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) +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,50,true) Route[#Route+1]=WaypointTo local Tasks={} Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,80),150,CoordinateTo:GetRandomCoordinateInRadius(800,500)) @@ -116952,11 +128568,11 @@ 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 WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.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) +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true) Route[#Route+1]=WaypointFrom Route[#Route+1]=WaypointTo Helicopter:WayPointInitialize(Route) @@ -116980,13 +128596,13 @@ 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) +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.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) +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,_speed,true) Route[#Route+1]=WaypointTo Route[#Route+1]=WaypointTo Helicopter:WayPointInitialize(Route) @@ -117007,12 +128623,12 @@ 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) +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.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) +local WaypointTo=CoordinateTo:WaypointAir("RADIO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true) Route[#Route+1]=WaypointTo Helicopter:WayPointInitialize(Route) local Tasks={} @@ -117165,7 +128781,7 @@ 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) +local ToWaypoint=AirbasePointVec2:WaypointAir(COORDINATE.WaypointAltType.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) @@ -118037,7 +129653,7 @@ self.DisplayCount=self.DisplayCount+1 end return true end -function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To,Event) +function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To) self:__NoMore(1) end end @@ -118430,12 +130046,18 @@ end return true end function CIRCLE:GetRandomVec2() +math.random() +math.random() +math.random() 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() +math.random() +math.random() +math.random() 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 @@ -119118,6 +130740,9 @@ local has_pos=(d1>0)or(d2>0)or(d3>0) return not(has_neg and has_pos) end function TRIANGLE:GetRandomVec2(points) +math.random() +math.random() +math.random() points=points or self.Points local pt={math.random(),math.random()} table.sort(pt) @@ -119274,6 +130899,9 @@ function SOUNDFILE:GetFileName() return self.filename end function SOUNDFILE:SetDuration(Duration) +if Duration and type(Duration)=="string"then +Duration=tonumber(Duration) +end self.duration=Duration or 3 return self end @@ -119304,7 +130932,7 @@ 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:SetDuration(Duration or MSRS.getSpeechTime(Text)) self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec",self.text,self.duration)) return self end @@ -119339,6 +130967,7 @@ SubtitleDuration=0, Power=100, Loop=false, alias=nil, +moduhasbeenset=false, } function RADIO:New(Positionable) local self=BASE:Inherit(self,BASE:New()) @@ -119374,12 +131003,13 @@ end function RADIO:SetFrequency(Frequency) self:F2(Frequency) if type(Frequency)=="number"then -self.Frequency=Frequency*1000000 +self.Frequency=Frequency +self.HertzFrequency=Frequency*1000000 if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then local commandSetFrequency={ id="SetFrequency", params={ -frequency=self.Frequency, +frequency=self.HertzFrequency, modulation=self.Modulation, } } @@ -119396,6 +131026,10 @@ self:F2(Modulation) if type(Modulation)=="number"then if Modulation==radio.modulation.AM or Modulation==radio.modulation.FM then self.Modulation=Modulation +if self.moduhasbeenset==false and Modulation==radio.modulation.FM then +self:SetFrequency(self.Frequency) +end +self.moduhasbeenset=true return self end end @@ -119601,7 +131235,7 @@ 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.") +self:E(self.lid..string.format("ERROR: Duration specified is NOT a number but type=%s. Filename=%s, duration=%s",type(duration),tostring(filename),tostring(duration))) return nil end local transmission={} @@ -119645,6 +131279,7 @@ end return wait end function RADIOQUEUE:Broadcast(transmission) +self:T("Broadcast") if((transmission.soundfile and transmission.soundfile.useSRS)or transmission.soundtext)and self.msrs then self:_BroadcastSRS(transmission) return @@ -119653,7 +131288,6 @@ 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={ @@ -119662,7 +131296,6 @@ 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 @@ -119683,7 +131316,7 @@ local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s MESSAGE:New(text,2,"RADIOQUEUE "..self.alias):ToAll() end else -self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission().")) +self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission()")) local vec3=nil if self.sendername then vec3=self:_GetRadioSenderCoord() @@ -119699,6 +131332,8 @@ 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 +else +self:E("ERROR: Could not get vec3 to determine transmission origin! Did you specify a sender and is it still alive?") end end end @@ -120075,20 +131710,150 @@ ConfigFileName="Moose_MSRS.lua", ConfigFilePath="Config\\", ConfigLoaded=false, poptions={}, +UsePowerShell=false, } -MSRS.version="0.3.0" +MSRS.version="0.3.3" MSRS.Voices={ +Amazon={ +Generative={ +en_AU={ +Olivia="Olivia", +}, +en_GB={ +Amy="Amy", +}, +en_US={ +Danielle="Danielle", +Joanna="Joanna", +Ruth="Ruth", +Stephen="Stephen", +}, +fr_FR={ +["Léa"]="Léa", +["Rémi"]="Rémi", +}, +de_DE={ +Vicki="Vicki", +Daniel="Daniel", +}, +it_IT={ +Bianca="Bianca", +Adriano="Adriano", +}, +es_ES={ +Lucia="Lucia", +Sergio="Sergio", +}, +}, +LongForm={ +en_US={ +Danielle="Danielle", +Gregory="Gregory", +Ivy="Ivy", +Ruth="Ruth", +Patrick="Patrick", +}, +es_ES={ +Alba="Alba", +["Raúl"]="Raúl", +}, +}, +Neural={ +en_AU={ +Olivia="Olivia", +}, +en_GB={ +Amy="Amy", +Emma="Emma", +Brian="Brian", +Arthur="Arthur", +}, +en_US={ +Danielle="Danielle", +Gregory="Gregory", +Ivy="Ivy", +Joanna="Joanna", +Kendra="Kendra", +Kimberly="Kimberly", +Salli="Salli", +Joey="Joey", +Kevin="Kevin", +Ruth="Ruth", +Stephen="Stephen", +}, +fr_FR={ +["Léa"]="Léa", +["Rémi"]="Rémi", +}, +de_DE={ +Vicki="Vicki", +Daniel="Daniel", +}, +it_IT={ +Bianca="Bianca", +Adriano="Adriano", +}, +es_ES={ +Lucia="Lucia", +Sergio="Sergio", +}, +}, +Standard={ +en_AU={ +Nicole="Nicole", +Russel="Russel", +}, +en_GB={ +Amy="Amy", +Emma="Emma", +Brian="Brian", +}, +en_IN={ +Aditi="Aditi", +Raveena="Raveena", +}, +en_US={ +Ivy="Ivy", +Joanna="Joanna", +Kendra="Kendra", +Kimberly="Kimberly", +Salli="Salli", +Joey="Joey", +Kevin="Kevin", +}, +fr_FR={ +Celine="Celine", +["Léa"]="Léa", +Mathieu="Mathieu", +}, +de_DE={ +Marlene="Marlene", +Vicki="Vicki", +Hans="Hans", +}, +it_IT={ +Carla="Carla", +Bianca="Bianca", +Giorgio="Giorgio", +}, +es_ES={ +Conchita="Conchita", +Lucia="Lucia", +Enrique="Enrique", +}, +}, +}, 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", +["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", @@ -120128,11 +131893,15 @@ Standard={ ["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_IN_Standard_E"]='en-IN-Standard-E', +["en_IN_Standard_F"]='en-IN-Standard-F', ["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_GB_Standard_N"]='en-GB-Standard-N', +["en_GB_Standard_O"]='en-GB-Standard-O', ["en_US_Standard_A"]='en-US-Standard-A', ["en_US_Standard_B"]='en-US-Standard-B', ["en_US_Standard_C"]='en-US-Standard-C', @@ -120143,25 +131912,33 @@ Standard={ ["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", +["fr_FR_Standard_A"]="fr-FR-Standard-F", +["fr_FR_Standard_B"]="fr-FR-Standard-G", +["fr_FR_Standard_C"]="fr-FR-Standard-F", +["fr_FR_Standard_D"]="fr-FR-Standard-G", +["fr_FR_Standard_E"]="fr-FR-Standard-F", +["fr_FR_Standard_G"]="fr-FR-Standard-G", +["fr_FR_Standard_F"]="fr-FR-Standard-F", +["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', +["de_DE_Standard_G"]='de-DE-Standard-G', +["de_DE_Standard_H"]='de-DE-Standard-H', +["es_ES_Standard_A"]="es-ES-Standard-E", +["es_ES_Standard_B"]="es-ES-Standard-F", +["es_ES_Standard_C"]="es-ES-Standard-E", +["es_ES_Standard_D"]="es-ES-Standard-F", +["es_ES_Standard_E"]="es-ES-Standard-E", +["es_ES_Standard_F"]="es-ES-Standard-F", +["it_IT_Standard_A"]="it-IT-Standard-E", +["it_IT_Standard_B"]="it-IT-Standard-E", +["it_IT_Standard_C"]="it-IT-Standard-F", +["it_IT_Standard_D"]="it-IT-Standard-F", +["it_IT_Standard_E"]="it-IT-Standard-E", +["it_IT_Standard_F"]="it-IT-Standard-F", }, Wavenet={ ["en_AU_Wavenet_A"]='en-AU-Wavenet-A', @@ -120172,11 +131949,15 @@ Wavenet={ ["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_IN_Wavenet_E"]='en-IN-Wavenet-E', +["en_IN_Wavenet_F"]='en-IN-Wavenet-F', ["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_GB_Wavenet_O"]='en-GB-Wavenet-O', +["en_GB_Wavenet_N"]='en-GB-Wavenet-N', ["en_US_Wavenet_A"]='en-US-Wavenet-A', ["en_US_Wavenet_B"]='en-US-Wavenet-B', ["en_US_Wavenet_C"]='en-US-Wavenet-C', @@ -120187,25 +131968,144 @@ Wavenet={ ["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", +["fr_FR_Wavenet_A"]="fr-FR-Wavenet-F", +["fr_FR_Wavenet_B"]="fr-FR-Wavenet-G", +["fr_FR_Wavenet_C"]="fr-FR-Wavenet-F", +["fr_FR_Wavenet_D"]="fr-FR-Wavenet-G", +["fr_FR_Wavenet_E"]="fr-FR-Wavenet-F", +["fr_FR_Wavenet_G"]="fr-FR-Wavenet-G", +["fr_FR_Wavenet_F"]="fr-FR-Wavenet-F", +["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', +["de_DE_Wavenet_G"]='de-DE-Wavenet-G', +["de_DE_Wavenet_H"]='de-DE-Wavenet-H', +["es_ES_Wavenet_B"]="es-ES-Wavenet-E", +["es_ES_Wavenet_C"]="es-ES-Wavenet-F", +["es_ES_Wavenet_D"]="es-ES-Wavenet-E", +["es_ES_Wavenet_E"]="es-ES-Wavenet-E", +["es_ES_Wavenet_F"]="es-ES-Wavenet-F", +["it_IT_Wavenet_A"]="it-IT-Wavenet-E", +["it_IT_Wavenet_B"]="it-IT-Wavenet-E", +["it_IT_Wavenet_C"]="it-IT-Wavenet-F", +["it_IT_Wavenet_D"]="it-IT-Wavenet-F", +["it_IT_Wavenet_E"]="it-IT-Wavenet-E", +["it_IT_Wavenet_F"]="it-IT-Wavenet-F", }, +Chirp3HD={ +["en_GB_Chirp3_HD_Aoede"]='en-GB-Chirp3-HD-Aoede', +["en_GB_Chirp3_HD_Charon"]='en-GB-Chirp3-HD-Charon', +["en_GB_Chirp3_HD_Fenrir"]='en-GB-Chirp3-HD-Fenrir', +["en_GB_Chirp3_HD_Kore"]='en-GB-Chirp3-HD-Kore', +["en_GB_Chirp3_HD_Leda"]='en-GB-Chirp3-HD-Leda', +["en_GB_Chirp3_HD_Orus"]='en-GB-Chirp3-HD-Orus', +["en_GB_Chirp3_HD_Puck"]='en-GB-Chirp3-HD-Puck', +["en_GB_Chirp3_HD_Zephyr"]='en-GB-Chirp3-HD-Zephyr', +["en_US_Chirp3_HD_Charon"]='en-US-Chirp3-HD-Charon', +["en_US_Chirp3_HD_Fenrir"]='en-US-Chirp3-HD-Fenrir', +["en_US_Chirp3_HD_Kore"]='en-US-Chirp3-HD-Kore', +["en_US_Chirp3_HD_Leda"]='en-US-Chirp3-HD-Leda', +["en_US_Chirp3_HD_Orus"]='en-US-Chirp3-HD-Orus', +["en_US_Chirp3_HD_Puck"]='en-US-Chirp3-HD-Puck', +["de_DE_Chirp3_HD_Aoede"]='de-DE-Chirp3-HD-Aoede', +["de_DE_Chirp3_HD_Charon"]='de-DE-Chirp3-HD-Charon', +["de_DE_Chirp3_HD_Fenrir"]='de-DE-Chirp3-HD-Fenrir', +["de_DE_Chirp3_HD_Kore"]='de-DE-Chirp3-HD-Kore', +["de_DE_Chirp3_HD_Leda"]='de-DE-Chirp3-HD-Leda', +["de_DE_Chirp3_HD_Orus"]='de-DE-Chirp3-HD-Orus', +["de_DE_Chirp3_HD_Puck"]='de-DE-Chirp3-HD-Puck', +["de_DE_Chirp3_HD_Zephyr"]='de-DE-Chirp3-HD-Zephyr', +["en_AU_Chirp3_HD_Aoede"]='en-AU-Chirp3-HD-Aoede', +["en_AU_Chirp3_HD_Charon"]='en-AU-Chirp3-HD-Charon', +["en_AU_Chirp3_HD_Fenrir"]='en-AU-Chirp3-HD-Fenrir', +["en_AU_Chirp3_HD_Kore"]='en-AU-Chirp3-HD-Kore', +["en_AU_Chirp3_HD_Leda"]='en-AU-Chirp3-HD-Leda', +["en_AU_Chirp3_HD_Orus"]='en-AU-Chirp3-HD-Orus', +["en_AU_Chirp3_HD_Puck"]='en-AU-Chirp3-HD-Puck', +["en_AU_Chirp3_HD_Zephyr"]='en-AU-Chirp3-HD-Zephyr', +["en_IN_Chirp3_HD_Aoede"]='en-IN-Chirp3-HD-Aoede', +["en_IN_Chirp3_HD_Charon"]='en-IN-Chirp3-HD-Charon', +["en_IN_Chirp3_HD_Fenrir"]='en-IN-Chirp3-HD-Fenrir', +["en_IN_Chirp3_HD_Kore"]='en-IN-Chirp3-HD-Kore', +["en_IN_Chirp3_HD_Leda"]='en-IN-Chirp3-HD-Leda', +["en_IN_Chirp3_HD_Orus"]='en-IN-Chirp3-HD-Orus', +}, +ChirpHD={ +["en_US_Chirp_HD_D"]='en-US-Chirp-HD-D', +["en_US_Chirp_HD_F"]='en-US-Chirp-HD-F', +["en_US_Chirp_HD_O"]='en-US-Chirp-HD-O', +["de_DE_Chirp_HD_D"]='de-DE-Chirp-HD-D', +["de_DE_Chirp_HD_F"]='de-DE-Chirp-HD-F', +["de_DE_Chirp_HD_O"]='de-DE-Chirp-HD-O', +["en_AU_Chirp_HD_D"]='en-AU-Chirp-HD-D', +["en_AU_Chirp_HD_F"]='en-AU-Chirp-HD-F', +["en_AU_Chirp_HD_O"]='en-AU-Chirp-HD-O', +["en_IN_Chirp_HD_D"]='en-IN-Chirp-HD-D', +["en_IN_Chirp_HD_F"]='en-IN-Chirp-HD-F', +["en_IN_Chirp_HD_O"]='en-IN-Chirp-HD-O', +}, +}, +Neural2={ +["en_GB_Neural2_A"]='en-GB-Neural2-A', +["en_GB_Neural2_B"]='en-GB-Neural2-B', +["en_GB_Neural2_C"]='en-GB-Neural2-C', +["en_GB_Neural2_D"]='en-GB-Neural2-D', +["en_GB_Neural2_F"]='en-GB-Neural2-F', +["en_GB_Neural2_N"]='en-GB-Neural2-N', +["en_GB_Neural2_O"]='en-GB-Neural2-O', +["en_US_Neural2_A"]='en-US-Neural2-A', +["en_US_Neural2_C"]='en-US-Neural2-C', +["en_US_Neural2_D"]='en-US-Neural2-D', +["en_US_Neural2_E"]='en-US-Neural2-E', +["en_US_Neural2_F"]='en-US-Neural2-F', +["en_US_Neural2_G"]='en-US-Neural2-G', +["en_US_Neural2_H"]='en-US-Neural2-H', +["en_US_Neural2_I"]='en-US-Neural2-I', +["en_US_Neural2_J"]='en-US-Neural2-J', +["de_DE_Neural2_G"]='de-DE-Neural2-G', +["de_DE_Neural2_H"]='de-DE-Neural2-H', +["en_AU_Neural2_A"]='en-AU-Neural2-A', +["en_AU_Neural2_B"]='en-AU-Neural2-B', +["en_AU_Neural2_C"]='en-AU-Neural2-C', +["en_AU_Neural2_D"]='en-AU-Neural2-D', +["en_IN_Neural2_A"]='en-IN-Neural2-A', +["en_IN_Neural2_B"]='en-IN-Neural2-B', +["en_IN_Neural2_C"]='en-IN-Neural2-C', +["en_IN_Neural2_D"]='en-IN-Neural2-D', +}, +News={ +["en_GB_News_G"]='en-GB-News-G', +["en_GB_News_H"]='en-GB-News-H', +["en_GB_News_I"]='en-GB-News-I', +["en_GB_News_J"]='en-GB-News-J', +["en_GB_News_K"]='en-GB-News-K', +["en_GB_News_L"]='en-GB-News-L', +["en_GB_News_M"]='en-GB-News-M', +["en_US_News_K"]='en-US-News-K', +["en_US_News_L"]='en-US-News-L', +["en_US_News_N"]='en-US-News-N', +["en_AU_News_E"]='en-AU-News-E', +["en_AU_News_F"]='en-AU-News-F', +["en_AU_News_G"]='en-AU-News-G', +}, +Casual={ +["en_US_Casual_K"]='en-US-Casual-K', +}, +Polyglot={ +["en_US_Polyglot_1"]='en-US-Polyglot-1', +["de_DE_Polyglot_1"]='de-DE-Polyglot-1', +["en_AU_Polyglot_1"]='en-AU-Polyglot-1', +}, +Studio={ +["en_GB_Studio_B"]='en-GB-Studio-B', +["en_GB_Studio_C"]='en-GB-Studio-C', +["en_US_Studio_O"]='en-US-Studio-O', +["en_US_Studio_Q"]='en-US-Studio-Q', +["de_DE_Studio_B"]='de-DE-Studio-B', +["de_DE_Studio_C"]='de-DE-Studio-C', }, } MSRS.Backend={ @@ -120300,7 +132200,7 @@ return self.backend end function MSRS:SetPath(Path) self:F({Path=Path}) -self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone" +self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" 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) @@ -120558,6 +132458,7 @@ self:ScheduleOnce(Delay,MSRS.PlaySoundFile,self,Soundfile,0) else local command=self:_GetCommand() command=command..' --file="'..tostring(soundfile)..'"' +command=string.gsub(command,"--ssml","-h") self:_ExecCommand(command) end return self @@ -120594,7 +132495,7 @@ 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) +self:ScheduleOnce(Delay,self.PlayTextExt,self,Text,0,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate) else Frequencies=Frequencies or self:GetFrequencies() Modulations=Modulations or self:GetModulations() @@ -120655,47 +132556,62 @@ label=label or self.Label coordinate=coordinate or self.coordinate modus=modus:gsub("0","AM") modus=modus:gsub("1","FM") +local pwsh=string.format('Start-Process -WindowStyle Hidden -WorkingDirectory \"%s\" -FilePath \"%s\" -ArgumentList \'-f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"',path,exe,freqs,modus,coal,port,label,volume) 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 +if voice and self.UsePowerShell~=true then command=command..string.format(" --voice=\"%s\"",tostring(voice)) +pwsh=pwsh..string.format(" --voice=\"%s\"",tostring(voice)) else if gender and gender~="female"then command=command..string.format(" -g %s",tostring(gender)) +pwsh=pwsh..string.format(" -g %s",tostring(gender)) end if culture and culture~="en-GB"then command=command..string.format(" -l %s",tostring(culture)) +pwsh=pwsh..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) +pwsh=pwsh..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) +pwsh=pwsh..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 ") +self:E("ERROR: SRS only supports WINDOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as AWS and Azure.") 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) +if self.UsePowerShell==true then +return pwsh +else return command end +end function MSRS:_ExecCommand(command) -self:F({command=command}) +self:T2({command=command}) if string.find(command,"CommandNotFound")then return 0 end local batContent=command.." && exit" local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat" +if self.UsePowerShell==true then +filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1" +batContent=command.."\'" +self:T({batContent=batContent}) +end 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 +if self.UsePowerShell~=true then local filenvbs=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".vbs" local script=io.open(filenvbs,"w+") script:write(string.format('Dim WinScriptHost\n')) @@ -120710,16 +132626,13 @@ 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) +elseif self.UsePowerShell==true then +local pwsh=string.format('start /min "" powershell.exe -ExecutionPolicy Unrestricted -WindowStyle Hidden -Command "%s"',filename) +if string.len(pwsh)>255 then +self:E("[MSRS] - pwsh string too long") +end +res=os.execute(pwsh) +timer.scheduleFunction(os.remove,filename,timer.getTime()+1) else command=string.format('start /b "" "%s"',filename) self:T("MSRS execute command="..command) @@ -120729,8 +132642,8 @@ 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}) +self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()") +self:T({Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate}) local options={} local ssml=Text or'' Frequencies=UTILS.EnsureTable(Frequencies,true)or self:GetFrequencies() @@ -120742,7 +132655,6 @@ options.position.lat,options.position.lon,options.position.alt=self:_GetLatLongA 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 @@ -120763,9 +132675,9 @@ 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]) +self:T("Calling GRPC.tts with the following parameter:") +self:T({ssml=ssml,freq=freq,options=options}) +self:T(options.provider[provider]) GRPC.tts(ssml,freq*1e6,options) end end @@ -120783,7 +132695,7 @@ 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.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" Self.port=MSRS_Config.Port or 5002 Self.backend=MSRS_Config.Backend or MSRS.Backend.SRSEXE Self.frequencies=MSRS_Config.Frequency or{127,243} @@ -120878,6 +132790,7 @@ end return self end function MSRSQUEUE:NewTransmission(text,duration,msrs,tstart,interval,subgroups,subtitle,subduration,frequency,modulation,gender,culture,voice,volume,label,coordinate) +self:T({Text=text,Dur=duration,start=tstart,int=interval,sub=subgroups,subt=subtitle,sudb=subduration,F=frequency,M=modulation,G=gender,C=culture,V=voice,Vol=volume,L=label}) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive()==0 then return self @@ -125071,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -3303,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3860,7 +4407,57 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +4475,61 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4558,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -4288,6 +4916,314 @@ qx=qx+shift_factor*norm_dx qy=qy+shift_factor*norm_dy return{x=qx,y=qy} end +function UTILS.ValidateAndRepositionGroundUnits(Positions,Anchor,MaxRadius,Spacing) +local units=Positions +Anchor=Anchor or UTILS.GetCenterPoint(units) +local gPos={x=Anchor.x,y=Anchor.z or Anchor.y} +local maxRadius=0 +local unitCount=0 +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local dist=UTILS.VecDist2D(pos,gPos) +if dist>maxRadius then +maxRadius=dist +end +unitCount=unitCount+1 +end +maxRadius=MaxRadius or math.max(maxRadius*2,10) +local spacing=Spacing or math.max(maxRadius*0.05,5) +if unitCount>0 and maxRadius>5 then +local spots=UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos),maxRadius,spacing,1000) +if spots and#spots>0 then +local validSpots={} +for _,spot in pairs(spots)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +table.insert(validSpots,spot) +end +end +spots=validSpots +end +local step=spacing +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local isOnLand=land.getSurfaceType(pos)==land.SurfaceType.LAND +local isValid=false +if spots and#spots>0 then +local si=1 +local sid=0 +local closestDist=100000000 +local closestSpot +for _,spot in pairs(spots)do +local dist=UTILS.VecDist2D(pos,spot) +if dist=spacing then +pos=closestSpot +end +isValid=true +table.remove(spots,sid) +end +end +if not isValid and not isOnLand then +local h=UTILS.HdgTo(pos,gPos) +local retries=0 +while not isValid and retries<500 do +local dist=UTILS.VecDist2D(pos,gPos) +pos=UTILS.Vec2Translate(pos,step,h) +local skip=false +for _,unit2 in pairs(units)do +if unit~=unit2 then +local pos2={x=unit2.x,y=unit2.z or unit2.y} +local dist2=UTILS.VecDist2D(pos,pos2) +if dist2<12 then +isValid=false +skip=true +break +end +end +end +if not skip and dist>step and land.getSurfaceType(pos)==land.SurfaceType.LAND then +isValid=true +break +elseif dist<=step then +break +end +retries=retries+1 +end +end +if isValid then +unit.x=pos.x +if unit.z then +unit.z=pos.y +else +unit.y=pos.y +end +end +end +end +end +function UTILS.ValidateAndRepositionStatic(Country,Category,Type,Position,ShapeName,MaxRadius) +local coord=COORDINATE:NewFromVec2(Position) +local st=SPAWNSTATIC:NewFromType(Type,Category,Country) +if ShapeName then +st:InitShape(ShapeName) +end +local sName="s-"..timer.getTime().."-"..math.random(1,10000) +local tempStatic=st:SpawnFromCoordinate(coord,0,sName) +if tempStatic then +local sRadius=tempStatic:GetBoundingRadius(2)or 3 +tempStatic:Destroy() +sRadius=sRadius*0.5 +MaxRadius=MaxRadius or math.max(sRadius*10,100) +local positions=UTILS.GetSimpleZones(coord:GetVec3(),MaxRadius,sRadius,20) +if positions and#positions>0 then +local closestSpot +local closestDist=math.huge +for _,spot in pairs(positions)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +local dist=UTILS.VecDist2D(Position,spot) +if dist=sRadius then +return closestSpot +else +return Position +end +end +end +end +return nil +end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -7574,7 +8510,7 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName=Event.TgtDCSUnitName Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) @@ -7597,10 +8533,12 @@ 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -9414,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -9424,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9471,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9789,6 +10737,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9850,6 +10808,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -10478,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10500,7 +11468,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true self.ScanData.Units[ZoneObject]=ZoneObject end end -if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then +if ObjectCategory==Object.Category.SCENERY then local SceneryType=ZoneObject:getTypeName() local SceneryName=ZoneObject:getName() self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} @@ -11263,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -11942,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -12256,6 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -12297,6 +13283,20 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +end +return self +end function SET_BASE:_Find(ObjectName) local ObjectFound=self.Set[ObjectName] return ObjectFound @@ -12609,7 +13609,8 @@ return true end function SET_BASE:IsInSet(Object) local outcome=false -local name=Object:GetName() +if Object==nil then return false end +local name=(Object~=nil and Object.GetName)and Object:GetName()or"none" self:ForEach( function(object) if object:GetName()==name then @@ -12780,16 +13781,6 @@ self.Filter.Zones[zonename]=Zone end return self end -function SET_GROUP:FilterCoalitions(Coalitions,Clear) -if Clear or(not self.Filter.Coalitions)then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_GROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -13244,16 +14235,6 @@ function SET_UNIT:FindUnit(UnitName) local UnitFound=self.Set[UnitName] return UnitFound end -function SET_UNIT:FilterCoalitions(Coalitions) -self.Filter.Coalitions={} -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_UNIT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -13898,18 +14879,6 @@ 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={} @@ -14336,18 +15305,6 @@ self.Filter.Playernames[playername]=playername end return self end -function SET_CLIENT: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_CLIENT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14521,8 +15478,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -14737,18 +15693,6 @@ function SET_PLAYER:FindClient(PlayerName) local ClientFound=self.Set[PlayerName] return ClientFound end -function SET_PLAYER: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_PLAYER:FilterZones(Zones) if not self.Filter.Zones then self.Filter.Zones={} @@ -15002,18 +15946,6 @@ function SET_AIRBASE:GetRandomAirbase() local RandomAirbase=self:GetRandom() return RandomAirbase end -function SET_AIRBASE: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_AIRBASE:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -15174,18 +16106,6 @@ function SET_CARGO:FindCargo(CargoName) local CargoFound=self.Set[CargoName] return CargoFound end -function SET_CARGO: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_CARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -15805,16 +16725,6 @@ self.Filter.Prefixes[Prefix]=Prefix end return self end -function SET_OPSZONE:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_OPSZONE:FilterOnce() for ObjectName,Object in pairs(self.Database)do self:Remove(ObjectName,true) @@ -16031,6 +16941,18 @@ self:Remove(RemoveGroupName) end return self end +function SET_OPSGROUP:CountAlive() +local CountG=0 +local CountU=0 +local Set=self:GetSet() +for GroupID,GroupData in pairs(Set)do +if GroupData and GroupData:IsAlive()then +CountG=CountG+1 +CountU=CountU+GroupData:GetGroup():CountAliveUnits() +end +end +return CountG,CountU +end function SET_OPSGROUP:FindGroup(GroupName) local GroupFound=self.Set[GroupName] return GroupFound @@ -16047,18 +16969,6 @@ function SET_OPSGROUP:FindNavyGroup(GroupName) local GroupFound=self:FindGroup(GroupName) return GroupFound end -function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear) -if Clear or 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_OPSGROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -16540,18 +17450,6 @@ DCargoInclude=DCargoInclude and MClientFunc end return DCargoInclude end -function SET_DYNAMICCARGO: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_DYNAMICCARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -17477,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -17685,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -18564,41 +19480,6 @@ return nil 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 -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 -end -do VELOCITY={ ClassName="VELOCITY", } @@ -19737,6 +20618,12 @@ self.SpawnUnitsWithAbsolutePositions=true self.UnitsAbsolutePositions=Positions return self end +function SPAWN:InitValidateAndRepositionGroundUnits(OnOff,MaxRadius,Spacing) +self.SpawnValidateAndRepositionGroundUnits=OnOff +self.SpawnValidateAndRepositionGroundUnitsRadius=MaxRadius +self.SpawnValidateAndRepositionGroundUnitsSpacing=Spacing +return self +end function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable) local temptable={} for _,_temp in pairs(SpawnTemplatePrefixTable)do @@ -19972,6 +20859,9 @@ SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-Curren end end if self.SpawnRandomizeUnits then +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -20106,6 +20996,11 @@ end if self.SpawnHiddenOnMap then SpawnTemplate.hidden=self.SpawnHiddenOnMap end +if self.SpawnValidateAndRepositionGroundUnits then +local units=SpawnTemplate.units +local gPos={x=SpawnTemplate.x,y=SpawnTemplate.y} +UTILS.ValidateAndRepositionGroundUnits(units,gPos,self.SpawnValidateAndRepositionGroundUnitsRadius,self.SpawnValidateAndRepositionGroundUnitsSpacing) +end SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID @@ -20892,6 +21787,11 @@ min=9 max=18 ctable=CALLSIGN.F15E end +if SpawnTemplate.units[1].type=="A6E"then +min=4 +max=18 +ctable=CALLSIGN.Intruder +end local callsignnr=math.random(min,max) local callsignname="Enfield" for name,value in pairs(ctable)do @@ -21407,6 +22307,18 @@ function SPAWNSTATIC:InitCargo(IsCargo) self.InitStaticCargo=IsCargo return self end +function SPAWNSTATIC:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWNSTATIC:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWNSTATIC:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead return self @@ -21426,6 +22338,11 @@ self.InitOffsetY=OffsetY or 0 self.InitOffsetAngle=OffsetAngle or 0 return self end +function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff,MaxRadius) +self.ValidateAndRepositionStatic=OnOff +self.ValidateAndRepositionStaticMaxRadius=MaxRadius +return self +end function SPAWNSTATIC:OnSpawnStatic(SpawnCallBackFunction,...) self:F("OnSpawnStatic") self.SpawnFunctionHook=SpawnCallBackFunction @@ -21495,6 +22412,15 @@ end if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end +if self.SpawnHiddenOnPlanner then +Template.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +Template.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +Template.hidden=self.SpawnHiddenOnMap +end if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -21512,6 +22438,13 @@ end self.SpawnIndex=self.SpawnIndex+1 Template.name=self.InitStaticName or string.format("%s#%05d",self.SpawnTemplatePrefix,self.SpawnIndex) local Static=nil +if self.ValidateAndRepositionStatic then +local validPos=UTILS.ValidateAndRepositionStatic(CountryID,Template.category,Template.type,Template,Template.shape_name,self.ValidateAndRepositionStaticMaxRadius) +if validPos then +Template.x=validPos.x +Template.y=validPos.y +end +end if self.InitFarp then local TemplateGroup={} TemplateGroup.units={} @@ -21553,7 +22486,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21561,8 +22494,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -21810,20 +22741,17 @@ MARKEROPS_BASE={ ClassName="MARKEROPS", Tag="mytag", Keywords={}, -version="0.1.4", +version="0.1.5", debug=false, Casesensitive=true, } -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) 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","*") @@ -21886,9 +22814,9 @@ end end function MARKEROPS_BASE:_MatchTag(Eventtext) local matches=false -if not self.Casesensitive then +if self.Casesensitive==false then local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then +if string.find(string.lower(Eventtext),type,1,true)then matches=true end else @@ -21924,6 +22852,14 @@ self:UnHandleEvent(EVENTS.MarkAdded) self:UnHandleEvent(EVENTS.MarkChange) self:UnHandleEvent(EVENTS.MarkRemoved) end +function MARKEROPS_BASE:SwitchCaseSensitiveOff() +self.Casesensitive=false +return self +end +function MARKEROPS_BASE:SwitchCaseSensitiveOn() +self.Casesensitive=true +return self +end TEXTANDSOUND={ ClassName="TEXTANDSOUND", version="0.0.1", @@ -22028,7 +22964,7 @@ ClassName="PATHLINE", lid=nil, points={}, } -PATHLINE.version="0.1.1" +PATHLINE.version="0.2.0" function PATHLINE:New(Name) local self=BASE:Inherit(self,BASE:New()) self.name=Name or"Unknown Path" @@ -22127,12 +23063,26 @@ return point.vec2 end return nil end +function PATHLINE:GetLength(Project2D) +local l=0 +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +if Project2D then +l=l+UTILS.VecDist2D(p1.vec2,p2.vec2) +else +l=l+UTILS.VecDist3D(p1.vec3,p2.vec3) +end +end +return l +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) +UTILS.RemoveMark(point.markerID) end else if point.markerID then @@ -22143,6 +23093,31 @@ local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m trigger.action.markToAll(point.markerID,text,point.vec3,"") end end +return self +end +function PATHLINE:DrawLine(Recipient,Color,LineType) +Recipient=Recipient or-1 +Color=Color or{1,0,0,1.0} +LineType=LineType or 1 +local ReadOnly=false +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +p1.lineID=UTILS.GetMarkID() +trigger.action.lineToAll(Recipient,p1.lineID,p1.vec3,p2.vec3,Color,LineType,ReadOnly,"") +end +return self +end +function PATHLINE:UnDrawLine(Delay) +local np=#self.points +for _,_point in pairs(self.points)do +local p=_point +if p.lineID then +UTILS.RemoveMark(p.lineID,Delay) +end +end +return self end function PATHLINE:_CreatePoint(Vec) local point={} @@ -22326,7 +23301,7 @@ end CLIENTMENUMANAGER={ ClassName="CLIENTMENUMANAGER", lid="", -version="0.1.6", +version="0.1.7", name=nil, clientset=nil, menutree={}, @@ -22600,6 +23575,10 @@ self.menutree=nil self.menutree={} return self end +function CLIENTMENUMANAGER:DeleteEntry(Entry,Client) +self:T(self.lid.."DeleteEntry") +return self:DeleteF10Entry(Entry,Client) +end function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client) self:T(self.lid.."DeleteF10Entry") local Set=self.clientset.Set @@ -22677,6 +23656,529 @@ end end return self end +VECTOR={ +ClassName="VECTOR", +verbose=0, +} +VECTOR.version="0.1.0" +_VECTORID=0 +VECTOR.__index=VECTOR +function VECTOR:New(x,y,z) +if z==nil then +self=setmetatable({x=x or 0,y=0,z=y or 0},VECTOR) +else +self=setmetatable({x=x or 0,y=y or 0,z=z or 0},VECTOR) +end +_VECTORID=_VECTORID+1 +self.uid=_VECTORID +return self +end +function VECTOR:NewFromVec(Vec) +local vector=VECTOR:New(Vec.x,Vec.y,Vec.z) +return vector +end +function VECTOR:NewFromPolar(r,phi) +local Phi=math.rad(phi) +local x=r*math.cos(phi) +local y=r*math.sin(phi) +local v=VECTOR:New(x,y) +return self +end +function VECTOR:NewFromSpherical(r,theta,phi) +local sinPhi=math.sin(math.rad(phi)) +local cosPhi=math.cos(math.rad(phi)) +local sinTheta=math.sin(math.rad(theta)) +local cosTheta=math.cos(math.rad(theta)) +local x=r*sinTheta*cosPhi +local y=r*sinTheta*sinPhi +local z=r*cosTheta +local v=VECTOR:New(x,y,z) +return self +end +function VECTOR:NewDirectionalVector(a,b) +local x=b.x-a.x +local y +local z +if a.z and b.z then +y=b.y-a.y +z=b.z-a.z +elseif b.z then +y=b.y-0 +z=b.z-a.y +elseif a.z then +y=0-a.y +z=b.y-a.z +else +y=b.y-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:NewFromLLDD(Latitude,Longitude,Altitude) +local vec3=coord.LLtoLO(Latitude,Longitude) +self=VECTOR:NewFromVec(vec3) +if Altitude then +self.y=Altitude +end +return self +end +function VECTOR:NewFromLLDMS(Latitude,Longitude,Altitude) +local lat=UTILS.LLDMSstringToDD(Latitude) +local lon=UTILS.LLDMSstringToDD(Longitude) +self=VECTOR:NewFromLLDD(lat,lon,Altitude) +return self +end +function VECTOR:GetVec2() +local vec={x=self.x,y=self.z} +return vec +end +function VECTOR:GetVec3(OnSurface) +local x=self.x +local y=OnSurface and land.getHeight({x=self.x,y=self.z})or self.y +local z=self.z +local vec={x=x,y=y,z=z} +return vec +end +function VECTOR:GetCoordinate(OnSurface) +local vec3=self:GetVec3(OnSurface) +local coordinate=COORDINATE:NewFromVec3(vec3) +return coordinate +end +function VECTOR:GetDistance(Vector,Only2D) +local dx=self.x-Vector.x +local dy=0 +local dz=0 +if Vector.z then +if not Only2D then +dy=self.y-Vector.y +end +dz=self.z-Vector.z +else +dy=0 +dz=self.z-Vector.y +end +local dist=math.sqrt(dx*dx+dy*dy+dz*dz) +return dist +end +function VECTOR:GetDirectionalVectorTo(a) +local x=a.x-self.x +local y=0 +local z=nil +if a.z then +y=a.y-self.y +z=a.z-self.z +else +y=a.y-self.z +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetDirectionalVectorFrom(a) +local x=self.x-a.x +local y +local z +if a.z then +y=self.y-a.y +z=self.z-a.z +else +y=self.z-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetLength() +local l=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) +return l +end +function VECTOR:GetHeading(To360) +local heading=math.atan2(self.z,self.x) +heading=math.deg(heading) +if To360==nil or To360==true then +heading=UTILS.AdjustHeading360(heading) +else +if heading==360.0 then +heading=0.0 +end +end +return heading +end +function VECTOR:GetHeadingTo(Vector) +local a=self:GetDirectionalVectorTo(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetHeadingFrom(Vector) +local a=self:GetDirectionalVectorFrom(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetLatitudeLongitude() +local vec3=self:GetVec3() +local latitude,longitude,altitude=coord.LOtoLL(vec3) +return latitude,longitude +end +function VECTOR:GetMGRS() +local lat,long=self:GetLatitudeLongitude() +local mgrs=coord.LLtoMGRS(lat,long) +return mgrs +end +function VECTOR:GetHeadingDelta(Vector) +local h1=self:GetHeading(false) +local h2=Vector:GetHeading(false) +local delta=h2-h1 +return delta +end +function VECTOR:GetIntermediateVector(Vector,Fraction) +local f=Fraction or 0.5 +local vec=self:GetDirectionalVectorTo(Vector) +local length=vec:GetLength() +vec:SetLength(f*length) +vec=self+vec +return vec +end +function VECTOR:SetLength(Length) +self:Normalize() +local v=self*Length +self:Replace(v) +return self +end +function VECTOR:SetX(x) +self.x=x or 0 +return self +end +function VECTOR:SetY(y) +if y==nil then +y=self:GetSurfaceHeight() +end +self.y=y +return self +end +function VECTOR:SetZ(z) +self.z=z or 0 +return self +end +function VECTOR:AddVec(Vec) +self.x=self.x+Vec.x +if Vec.z then +self.y=self.y+Vec.y +self.z=self.z+Vec.z +else +self.z=self.z+Vec.y +end +return self +end +function VECTOR:SubVec(Vec) +self.x=self.x-Vec.x +if Vec.z then +self.y=self.y-Vec.y +self.z=self.z-Vec.z +else +self.z=self.z-Vec.y +end +return self +end +function VECTOR:Dot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Rot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Copy() +local c=VECTOR:New(self.x,self.y,self.z) +return c +end +function VECTOR:Replace(Vector,Project2D) +self.x=Vector.x +if Vector.z then +self.y=Vector.y +self.z=Vector.z +else +if Project2D then +self.y=0 +end +self.z=Vector.y +end +return self +end +function VECTOR:Normalize() +local l=self:GetLength() +if l~=0 then +self:Replace(self/l) +end +return self +end +function VECTOR:Translate(Distance,Heading,Copy) +Distance=Distance or 1000 +local alpha=math.rad(Heading or 0) +local vector=Copy and self:Copy()or self +vector.x=Distance*math.cos(alpha)+vector.x +vector.z=Distance*math.sin(alpha)+vector.z +return vector +end +function VECTOR:Rotate2D(Angle,Copy) +local phi=-math.rad(Angle or 0) +local sinPhi=math.sin(phi) +local cosPhi=math.cos(phi) +local X=self.z +local Y=self.x +local z=X*cosPhi-Y*sinPhi +local x=X*sinPhi+Y*cosPhi +if Copy then +local vector=VECTOR:New(x,self.y,z) +return vector +else +self:SetX(x) +self:SetZ(z) +return self +end +end +function VECTOR: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) +local text="MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +return text +end +function VECTOR:GetSurfaceType() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +return s +end +function VECTOR:GetSurfaceTypeName() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +for name,id in land.SurfaceType()do +if id==s then +return name +end +end +return"unknown" +end +function VECTOR:IsVisible(Vec) +local vec1=self:GetVec3() +local vec2={x=Vec.x,Vec.y,Vec.z} +local los=land.isVisible(vec1,vec2) +return los +end +function VECTOR:GetClosestRoad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('roads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetClosestRailroad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('railroads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetPathOnRoad(Vec) +local vec1=self:GetVec2() +local vec2=Vec:GetVec2() +local vec2points=land.findPathOnRoads("roads",vec1.x,vec1.y,vec2.x,vec2.y) +local path=nil +if vec2points then +path=PATHLINE:NewFromVec2Array("Road",vec2points) +end +return path +end +function VECTOR:GetProfile(Vec3) +local vec3=self:GetVec3() +local vec3s=land.profile(vec3,Vec3) +local profile=nil +if vec3s then +profile=PATHLINE:NewFromVec3Array("Profile",vec3s) +end +return profile +end +function VECTOR:GetInterceptPoint(DirectionVector,Distance) +local vec3=self:GetVec3() +local ip3=land.getIP(vec3,DirectionVector,Distance or 1000) +local ipvector=nil +if ip3 then +ipvector=VECTOR:New(ip3.x,ip3.y,ip3.z) +end +return ipvector +end +function VECTOR:GetSurfaceHeight() +local vec2=self:GetVec2() +local h=land.getHeight(vec2) +return h +end +function VECTOR:GetSurfaceHeightAndDepth() +local vec2=self:GetVec2() +local h,d=land.getSurfaceHeightWithSeabed(vec2) +return h,d +end +function VECTOR:GetWindVector(WithTurbulence) +local vec3=self:GetVec3() +local wind=nil +if WithTurbulence then +wind=atmosphere.getWindWithTurbulence(vec3) +else +wind=atmosphere.getWind(vec3) +end +local vector=VECTOR:New(wind) +return vector +end +function VECTOR:GetTemperaturAndPressure() +local vec3=self:GetVec3() +local t,p=atmosphere.getTemperatureAndPressure(vec3) +return t,p +end +function VECTOR:Smoke(Color,Duration) +local vec3=self:GetVec3() +Color=Color or 0 +local name=string.format("Vector-Smoke-%d",self.uid) +trigger.action.smoke(vec3,Color,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -22776,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -22923,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -22960,6 +24483,16 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetPointVec2() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -22991,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -22998,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -23158,12 +24693,18 @@ end BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -23283,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -23778,6 +25329,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -26040,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -27263,6 +28855,7 @@ GROUND_EWR="Ground_EWR", GROUND_AAA="Ground_AAA", GROUND_SAM="Ground_SAM", GROUND_SHORAD="Ground_SHORAD", +GROUND_BALLISTICMISSILE="Ground_BallisticMissile", GROUND_OTHER="Ground_OtherGround", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_WARSHIP="Naval_WarShip", @@ -27695,33 +29288,45 @@ end function GROUP:GetTypeName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +end return nil end function GROUP:GetNatoReportingName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +end return"Bogey" end function GROUP:GetPlayerName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +end return nil end function GROUP:GetCallsign() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -27741,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -28313,6 +29927,9 @@ if self.InitRespawnModu then Template.modulation=self.InitRespawnModu end self:Destroy(false) +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) self:ResetEvents() return self @@ -28389,7 +30006,7 @@ function GROUP:GetTaskMission() return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) end function GROUP:GetTaskRoute() -if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) else return{} @@ -28403,7 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -28425,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -28517,6 +30134,8 @@ 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 ballisticMissile=artillery and self:HasAttribute("SS_missile") +local shorad=self:HasAttribute("SR SAM") local ewr=self:HasAttribute("EWR") local ifv=self:HasAttribute("IFV") local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker") @@ -28547,6 +30166,8 @@ elseif sam then attribute=GROUP.Attribute.GROUND_SAM elseif aaa then attribute=GROUP.Attribute.GROUND_AAA +elseif artillery and ballisticMissile then +attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE elseif artillery then attribute=GROUP.Attribute.GROUND_ARTILLERY elseif tank then @@ -28664,6 +30285,10 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +return self +end function GROUP:SetCommandInvisible(switch) return self:CommandSetInvisible(switch) end @@ -28826,6 +30451,44 @@ end end return isAAA end +function GROUP:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function GROUP:GetBoundingBox() +local bbox={min={x=math.huge,y=math.huge,z=math.huge}, +max={x=-math.huge,y=-math.huge,z=-math.huge} +} +local Units=self:GetUnits()or{} +if#Units==0 then +return nil +end +for _,unit in pairs(Units)do +if unit and unit:IsAlive()then +local ubox=unit:GetBoundingBox() +if ubox then +if ubox.min.xbbox.max.x then +bbox.max.x=ubox.max.x +end +if ubox.max.y>bbox.max.y then +bbox.max.y=ubox.max.y +end +if ubox.max.z>bbox.max.z then +bbox.max.z=ubox.max.z +end +end +end +end +return bbox +end UNIT={ ClassName="UNIT", UnitName=nil, @@ -28963,6 +30626,9 @@ i=i+1 end end SpawnGroupTemplate.groupId=nil +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units) +end _DATABASE:Spawn(SpawnGroupTemplate) end function UNIT:IsActive() @@ -29746,6 +31412,23 @@ end function UNIT:SetCarrierIlluminationMode(Mode) UTILS.SetCarrierIlluminationMode(self:GetID(),Mode) end +function UNIT:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -30053,6 +31736,7 @@ self.Life0=Life0 else self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -30065,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -30130,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30140,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30149,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30238,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -30246,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -30268,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -30278,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -30296,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -30303,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -30316,18 +32028,15 @@ AIRBASE.Normandy={ ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -["Hawkinge"]="Hawkinge", } 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -30370,13 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -30392,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -30404,16 +32115,17 @@ AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", -["Marka"]="Marka", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -30430,17 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Ben_Gurion"]="Ben Gurion", -["Hatzor"]="Hatzor", -["Palmachim"]="Palmachim", -["Tel_Nof"]="Tel Nof", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -30448,8 +32157,7 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } -AIRBASE.MarianaIslandsWWII= -{ +AIRBASE.MarianaIslandsWWII={ ["Agana"]="Agana", ["Airfield_3"]="Airfield 3", ["Charon_Kanoa"]="Charon Kanoa", @@ -30510,7 +32218,9 @@ AIRBASE.Sinai={ ["Borg_El_Arab_International_Airport"]="Borg El Arab International Airport", ["Cairo_International_Airport"]="Cairo International Airport", ["Cairo_West"]="Cairo West", +["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["El_Minya"]="El Minya", @@ -30522,8 +32232,11 @@ AIRBASE.Sinai={ ["Inshas_Airbase"]="Inshas Airbase", ["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", ["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", ["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", @@ -30537,45 +32250,50 @@ AIRBASE.Sinai={ ["Sde_Dov"]="Sde Dov", ["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", ["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", ["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", ["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", ["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", ["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", ["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", ["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", ["Severomorsk_1"]="Severomorsk-1", ["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", ["Vidsel"]="Vidsel", ["Vuojarvi"]="Vuojarvi", -["Andoya"]="Andoya", -["Alakurtti"]="Alakurtti", -["Kittila"]="Kittila", -["Bardufoss"]="Bardufoss", -["Alta"]="Alta", -["Sodankyla"]="Sodankyla", -["Enontekio"]="Enontekio", -["Evenes"]="Evenes", -["Hosio"]="Hosio", -["Kilpyavr"]="Kilpyavr", -["Afrikanda"]="Afrikanda", -["Kalevala"]="Kalevala", -["Koshka_Yavr"]="Koshka Yavr", -["Poduzhemye"]="Poduzhemye", -["Luostari_Pechenga"]="Luostari Pechenga", } AIRBASE.Afghanistan={ ["Bagram"]="Bagram", @@ -30585,6 +32303,10 @@ AIRBASE.Afghanistan={ ["Camp_Bastion_Heliport"]="Camp Bastion Heliport", ["Chaghcharan"]="Chaghcharan", ["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", ["Farah"]="Farah", ["Gardez"]="Gardez", ["Ghazni_Heliport"]="Ghazni Heliport", @@ -30594,7 +32316,6 @@ AIRBASE.Afghanistan={ ["Kandahar"]="Kandahar", ["Kandahar_Heliport"]="Kandahar Heliport", ["Khost"]="Khost", -["Khost_Heliport"]="Khost Heliport", ["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", ["Nimroz"]="Nimroz", ["Qala_i_Naw"]="Qala i Naw", @@ -30605,21 +32326,28 @@ AIRBASE.Afghanistan={ ["Urgoon_Heliport"]="Urgoon Heliport", } AIRBASE.Iraq={ -["Baghdad_International_Airport"]="Baghdad International Airport", -["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", -["Al_Sahra_Airport"]="Al-Sahra Airport", -["Erbil_International_Airport"]="Erbil International Airport", -["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", ["Al_Salam_Airbase"]="Al-Salam Airbase", -["Balad_Airbase"]="Balad Airbase", -["Kirkuk_International_Airport"]="Kirkuk International Airport", -["Bashur_Airport"]="Bashur Airport", +["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Taquddum_Airport"]="Al-Taquddum Airport", -["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", ["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", } AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", ["Airracing_Frankfurt"]="Airracing Frankfurt", ["Airracing_Koblenz"]="Airracing Koblenz", ["Airracing_Luebeck"]="Airracing Lubeck", @@ -30630,16 +32358,23 @@ AIRBASE.GermanyCW={ ["Bienenfarm"]="Bienenfarm", ["Bindersleben"]="Bindersleben", ["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", ["Braunschweig"]="Braunschweig", ["Bremen"]="Bremen", ["Briest"]="Briest", ["Buechel"]="Buchel", ["Bueckeburg"]="Buckeburg", ["Celle"]="Celle", +["Chojna"]="Chojna", ["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", ["Damgarten"]="Damgarten", ["Dedelow"]="Dedelow", ["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", ["Fassberg"]="Fassberg", ["Finow"]="Finow", ["Frankfurt"]="Frankfurt", @@ -30650,7 +32385,7 @@ AIRBASE.GermanyCW={ ["Gatow"]="Gatow", ["Gelnhausen"]="Gelnhausen", ["Giebelstadt"]="Giebelstadt", -["Glindbruchkippe"]="Glindbruchkippe ", +["Glindbruchkippe"]="Glindbruchkippe", ["Gross_Mohrdorf"]="Gross Mohrdorf", ["Grosse_Wiese"]="Grosse Wiese", ["Guetersloh"]="Gutersloh", @@ -30768,43 +32503,60 @@ AIRBASE.GermanyCW={ ["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", ["Hannover"]="Hannover", ["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", ["Herrenteich"]="Herrenteich", ["Hildesheim"]="Hildesheim", ["Hockenheim"]="Hockenheim", ["Holzdorf"]="Holzdorf", ["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", ["Koethen"]="Kothen", ["Laage"]="Laage", +["Landstuhl"]="Landstuhl", ["Langenselbold"]="Langenselbold", ["Laerz"]="Larz", -["Leipzig_Halle"]="Leipzig Halle", ["Leipzig_Mockau"]="Leipzig Mockau", ["Luebeck"]="Lubeck", ["Lueneburg"]="Luneburg", ["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", ["Mendig"]="Mendig", ["Merseburg"]="Merseburg", ["Neubrandenburg"]="Neubrandenburg", ["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", ["Northeim"]="Northeim", +["Noervenich"]="Norvenich", ["Ober_Moerlen"]="Ober-Morlen", ["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", ["Parchim"]="Parchim", ["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", ["Pferdsfeld"]="Pferdsfeld", ["Pinnow"]="Pinnow", ["Pottschutthoehe"]="Pottschutthohe", ["Ramstein"]="Ramstein", +["Revinge"]="Revinge", ["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", ["Schoenefeld"]="Schonefeld", ["Schweinfurt"]="Schweinfurt", ["Sembach"]="Sembach", +["Sittensen"]="Sittensen", ["Spangdahlem"]="Spangdahlem", ["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", ["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", ["Tegel"]="Tegel", ["Tempelhof"]="Tempelhof", ["Templin"]="Templin", +["Thurland"]="Thurland", ["Tutow"]="Tutow", ["Uelzen"]="Uelzen", ["Uetersen"]="Uetersen", @@ -30820,6 +32572,7 @@ AIRBASE.GermanyCW={ ["Worms"]="Worms", ["Wunstorf"]="Wunstorf", ["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", ["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ @@ -30887,9 +32640,19 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -31136,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -31827,16 +33617,55 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end if self.SceneryObject and self.SceneryObject.getLife then -self.Life0=self.SceneryObject:getLife()or 0 +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) @@ -31855,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 +local life=1 if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -31895,14 +33746,16 @@ end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -31921,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -31945,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -31974,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -32827,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -33299,7 +35167,7 @@ self:SetLiquid(lqno,lqam) end end else -self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end if self:IsLimitedAircraft()then @@ -33317,7 +35185,7 @@ self:SetAmount(acname,acnumber) end end else -self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end if self:IsLimitedWeapons()then @@ -33346,7 +35214,7 @@ end end end else -self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end return self @@ -33417,6 +35285,9 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33425,8 +35296,26 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.1.0" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -33649,13 +35538,29 @@ end end self.LastPosition=pos else -if self.timer and self.timer:IsRunning()then self.timer:Stop()end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") self.CargoState=DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end return self end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end function DYNAMICCARGO._FilterHeloTypes(client) if not client then return false end local typename=client:GetTypeName() @@ -34991,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -35004,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -35026,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -35038,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -35065,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -35097,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -35104,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -35243,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -35353,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -35379,6 +37412,7 @@ local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -35392,7 +37426,6 @@ 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 @@ -35404,14 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName -if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then TargetCategory=Unit.Category.STRUCTURE TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") end 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) @@ -35420,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -35431,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -35503,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -35521,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -35610,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -35709,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -35726,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -35964,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -35987,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -35995,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -36063,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -36084,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -43640,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -43651,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -43677,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -43707,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -43730,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -43745,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -43754,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -44366,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -44398,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -44417,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -46611,7 +48690,7 @@ maxrange=7000, reloadtime=nil, }, } -ARTY.version="1.3.3" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -47291,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -50820,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -51117,7 +53220,6 @@ 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) @@ -51221,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -51315,6 +53418,9 @@ self:I(self.lid..text) self:T({DCSdesc=asset.DCSdesc}) self:T3({Template=asset.template}) end +function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +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 @@ -51420,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -52130,6 +54235,9 @@ template.route.points[1].y=coord.z template.x=coord.x template.y=coord.z template.alt=coord.y +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(template.units) +end local group=_DATABASE:Spawn(template) return group end @@ -52226,7 +54334,6 @@ 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 @@ -54716,7 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", -version="0.9.33", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -54767,6 +56874,9 @@ SmokeDecoyColor=SMOKECOLOR.White, checkcounter=1, DLinkCacheTime=120, logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -54807,16 +56917,23 @@ MANTIS.SamData={ ["Chaparral"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"}, ["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Linebacker",Point="true"}, ["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"}, -["HEMTT_C-RAM_Phalanx"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="HEMTT_C-RAM_Phalanx",Point="true"}, +["C-RAM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="HEMTT_C-RAM_Phalanx",Point="true"}, ["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"}, ["SA-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, ["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"}, -["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, ["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, +["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, +["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["IRIS-T SLM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"}, } MANTIS.SamDataHDS={ ["SA-2 HDS"]={Range=56,Blindspot=7,Height=30,Type="Medium",Radar="V759"}, @@ -54847,15 +56964,15 @@ MANTIS.SamDataSMA={ } MANTIS.SamDataCH={ ["2S38 CHM"]={Range=6,Blindspot=0.1,Height=4.5,Type="Short",Radar="2S38"}, -["PantsirS1 CHM"]={Range=20,Blindspot=1.2,Height=15,Type="Short",Radar="PantsirS1"}, +["PantsirS1 CHM"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, ["PantsirS2 CHM"]={Range=30,Blindspot=1.2,Height=18,Type="Medium",Radar="PantsirS2"}, ["PGL-625 CHM"]={Range=10,Blindspot=1,Height=5,Type="Short",Radar="PGL_625"}, ["HQ-17A CHM"]={Range=15,Blindspot=1.5,Height=10,Type="Short",Radar="HQ17A"}, ["M903PAC2 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="MIM104_M903_PAC2"}, ["M903PAC3 CHM"]={Range=160,Blindspot=1,Height=40,Type="Long",Radar="MIM104_M903_PAC3"}, -["TorM2 CHM"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2"}, -["TorM2K CHM"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2K"}, -["TorM2M CHM"]={Range=16,Blindspot=1,Height=10,Type="Short",Radar="TorM2M"}, +["TorM2 CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["TorM2K CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2K",Point="true"}, +["TorM2M CHM"]={Range=16,Blindspot=1,Height=10,Type="Point",Radar="TorM2M",Point="true"}, ["NASAMS3-AMRAAMER CHM"]={Range=50,Blindspot=2,Height=35.7,Type="Medium",Radar="CH_NASAMS3_LN_AMRAAM_ER"}, ["NASAMS3-AIM9X2 CHM"]={Range=20,Blindspot=0.2,Height=18,Type="Short",Radar="CH_NASAMS3_LN_AIM9X2"}, ["C-RAM CHM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="CH_Centurion_C_RAM",Point="true"}, @@ -54891,13 +57008,14 @@ MANTIS.SamDataCH={ ["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} @@ -55009,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -55041,11 +57264,78 @@ self:T(self.lid.."AddZones") self.AcceptZones=AcceptZones or{} self.RejectZones=RejectZones or{} self.ConflictZones=ConflictZones or{} -if#self.AcceptZones>0 or#self.RejectZones>0 or#self.ConflictZones>0 then +self.AcceptZonesNo=UTILS.TableLength(self.AcceptZones) +self.RejectZonesNo=UTILS.TableLength(self.RejectZones) +self.ConflictZonesNo=UTILS.TableLength(self.ConflictZones) +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 or self.RejectZonesNo>0 or self.ConflictZonesNo>0 then self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -55302,7 +57592,8 @@ end function MANTIS:_CheckCoordinateInZones(coord) self:T(self.lid.."_CheckCoordinateInZones") local inzone=false -if#self.AcceptZones>0 then +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 then for _,_zone in pairs(self.AcceptZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -55312,7 +57603,7 @@ break end end end -if#self.RejectZones>0 and inzone then +if self.RejectZonesNo>0 then for _,_zone in pairs(self.RejectZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -55322,7 +57613,7 @@ break end end end -if#self.ConflictZones>0 and not inzone then +if self.ConflictZonesNo>0 then for _,_zone in pairs(self.ConflictZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -55334,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -55343,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -55356,7 +57661,7 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end if self.checkforfriendlies==true and self.friendlyset==nil then self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() @@ -55368,6 +57673,7 @@ if not targetdistance then targetdistance=samcoordinate:Get2DDistance(coord) end local zonecheck=true +self:T("self.usezones = "..tostring(self.usezones)) if self.usezones then zonecheck=self:_CheckCoordinateInZones(coord) end @@ -55411,9 +57717,27 @@ self:T(self.lid.."Starting Intel Detection") local groupset=self.EWR_Group local samset=self.SAM_Group self.intelset={} -local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne") +local IntelOne=INTEL:New(groupset,self.coalition,self.name.." IntelOne") +IntelOne.DetectAccoustic=self.DetectAccoustic +IntelOne.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelOne.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelOne:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelOne:Start() -local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo") +local IntelTwo=INTEL:New(samset,self.coalition,self.name.." IntelTwo") +IntelTwo.DetectAccoustic=self.DetectAccoustic +IntelTwo.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelTwo.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelTwo:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelTwo:Start() local CacheTime=self.DLinkCacheTime or 120 local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) @@ -55669,6 +57993,22 @@ self:T(self.lid.."RemoveShorad") self.ShoradLink=false return self end +function MANTIS:_SmokeUnits(Group) +self:T("Smoking") +local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME")or 0 +local TNow=timer.getTime() +if TNow-LastSmoketime>290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 @@ -55682,7 +58022,7 @@ local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 -local shortsam=(_data[6]==MANTIS.SamType.SHORT)and true or false +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false if not shortsam then shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false end @@ -55707,17 +58047,10 @@ switch=true end if self.SamStateTracker[name]~="RED"and switch then self:__RedState(1,samgroup) -self.SamStateTracker[name]="RED" end -if shortsam==true and self.SmokeDecoy==true then +if shortsam==true and self.SmokeDecoy==true and 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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -60574,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -60595,8 +63213,12 @@ WASHINGTON="CVN_73", TRUMAN="CVN_75", STENNIS="Stennis", FORRESTAL="Forrestal", +ENTERPRISE66="USS Enterprise 1966", +ENTERPRISEMODERN="cvn-65", VINSON="VINSON", ESSEX="Essex", +BONHOMMERICHARD="USS Bon Homme Richard", +ESSEXSCB125="essex_scb125", HERMES="HERMES81", INVINCIBLE="hms_invincible", TARAWA="LHA_Tarawa", @@ -60654,7 +63276,7 @@ HARD="TOPGUN Graduate", } AIRBOSS.MenuF10={} AIRBOSS.MenuF10Root=nil -AIRBOSS.version="1.4.1" +AIRBOSS.version="1.4.2" function AIRBOSS:New(carriername,alias) local self=BASE:Inherit(self,FSM:New()) self:F2({carriername=carriername,alias=alias}) @@ -60733,10 +63355,18 @@ elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() +elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISE66 then +self:_InitEnterprise() +elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISEMODERN then +self:_InitEnterprise() elseif self.carriertype==AIRBOSS.CarrierType.VINSON then self:_InitStennis() elseif self.carriertype==AIRBOSS.CarrierType.ESSEX then self:_InitEssex() +elseif self.carriertype==AIRBOSS.CarrierType.BONHOMMERICHARD then +self:_InitBonHommeRichard() +elseif self.carriertype==AIRBOSS.CarrierType.ESSEXSCB125 then +self:_InitEssexSCB125() elseif self.carriertype==AIRBOSS.CarrierType.HERMES then self:_InitHermes() elseif self.carriertype==AIRBOSS.CarrierType.INVINCIBLE then @@ -61189,8 +63819,7 @@ 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:SetPort(Port or MSRS.port or 5002) self.SRS:SetLabel(self.AirbossRadio.alias or"AIRBOSS") self.SRS:SetCoordinate(self.carrier:GetCoordinate()) self.SRS:SetVolume(Volume or 1) @@ -61200,7 +63829,9 @@ end if Voice then self.SRS:SetVoice(Voice) end -self.SRS:SetVolume(Volume or 1.0) +if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then +self.SRS.voice=MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B +end self.SRSQ=MSRSQUEUE:New("AIRBOSS") self.SRSQ:SetTransmitOnlyWithPlayers(true) if not self.PilotRadio then @@ -61969,6 +64600,17 @@ self.carrierparam.wire3=64 self.carrierparam.wire4=74 self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 end +function AIRBOSS:_InitEnterprise() +self:_InitForrestal() +self.carrierparam.sterndist=-164.30 +self.carrierparam.deckheight=19.52 +self.carrierparam.totlength=335 +self.carrierparam.rwylength=223 +self.carrierparam.wire1=57.7 +self.carrierparam.wire2=69.6 +self.carrierparam.wire3=79.5 +self.carrierparam.wire4=90.0 +end function AIRBOSS:_InitEssex() self:_InitNimitz() self.carrierparam.sterndist=-126 @@ -61996,6 +64638,20 @@ self.carrierparam.wire14=121.0 self.carrierparam.wire15=128.5 self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 end +function AIRBOSS:_InitBonHommeRichard() +self:_InitEssex() +self.carrierparam.deckheight=16.95 +self.carrierparam.rwyangle=-11.4 +self.carrierparam.rwylength=97 +self.carrierparam.rwywidth=20 +self.carrierparam.wire1=40.4 +self.carrierparam.wire2=45 +self.carrierparam.wire3=51 +self.carrierparam.wire4=58.1 +end +function AIRBOSS:_InitEssexSCB125() +self:_InitBonHommeRichard() +end function AIRBOSS:_InitHermes() self:_InitStennis() self.carrierparam.sterndist=-105 @@ -62502,7 +65158,7 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then @@ -62522,13 +65178,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -62558,7 +65214,7 @@ return aoa end function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -62568,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -63201,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -63564,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -63830,7 +66487,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -63852,6 +66514,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -64594,77 +67278,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -64952,19 +67568,19 @@ end if 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" +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" +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)" +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)" +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 @@ -65579,7 +68195,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true) text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) local unitClient=Unit.getByName(unit:GetName()) local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early if hornet then local nozzlePosL=0 local burnerPosL=unitClient:getDrawArgumentValue(28) @@ -65695,7 +68311,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89) else nozzlePosL=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosL=unitClient:getDrawArgumentValue(434) end return nozzlePosL @@ -65712,7 +68328,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90) else nozzlePosR=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosR=unitClient:getDrawArgumentValue(433) end return nozzlePosR @@ -65763,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -65864,7 +68478,7 @@ 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=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -66055,7 +68669,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -66070,6 +68684,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66177,14 +68792,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66232,6 +68841,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66248,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -67295,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -70201,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -70247,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -70445,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -70464,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -70474,14 +73093,12 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) return self @@ -72540,6 +75157,9 @@ self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false self.ResourceMap=nil self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end self.StaticShape=nil self.TypeNames=nil self.StaticCategory="Cargos" @@ -72547,6 +75167,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -72603,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -72628,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -72919,7 +75544,9 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, @@ -72934,8 +75561,9 @@ CTLD.FixedWingTypes={ ["Hercules"]="Hercules", ["Bronco"]="Bronco", ["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", } -CTLD.version="1.3.37" +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -72978,12 +75606,15 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") self:AddTransition("*","CratesPacked","*") self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") @@ -73024,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -73037,13 +75670,17 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false self.enableFixedWing=false self.FixedMinAngels=165 self.FixedMaxAngels=2000 self.FixedMaxSpeed=77 +self.validateAndRepositionUnits=false self.suppressmessages=false self.repairtime=300 self.buildtime=300 @@ -73063,6 +75700,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -73075,6 +75713,8 @@ self.filename=string.format("CTLD_%s_Persist.csv",AliaS) self.allowcratepickupagain=true self.enableslingload=false self.basetype="container_cargo" +self.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do @@ -73155,6 +75795,15 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end if _unit:IsHelicopter()or _group:IsHelicopter()then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil @@ -73190,6 +75839,7 @@ end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end elseif event.id==EVENTS.NewDynamicCargo then self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo @@ -73215,6 +75865,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73251,6 +75902,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73258,6 +75910,13 @@ self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -73285,6 +75944,12 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end function CTLD:AddAllowedFixedWingType(typename) @@ -73434,6 +76099,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -73529,7 +76195,7 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) local hassecondaries=false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!",10,false,Group) @@ -73658,18 +76324,340 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -73698,7 +76686,7 @@ 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 +if not self.debug then return false end end end end @@ -73708,13 +76696,16 @@ local loaddist=self.CrateDistance or 35 local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false +end +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -73729,6 +76720,7 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 @@ -73771,8 +76763,12 @@ self.CrateCounter=self.CrateCounter+1 local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() local basetype=CType or self.basetype or"container_cargo" CCat=CCat or"Cargos" -if isstatic then -basetype=cratetemplate +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -73818,8 +76814,8 @@ 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) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -73831,23 +76827,35 @@ realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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) else +if not quiet then self:_SendMessage(text,10,false,Group) end +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -73946,6 +76954,52 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis end return self end +function CTLD:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +end +return self +end function CTLD:_RemoveCratesNearby(_group,_unit) self:T(self.lid.." _RemoveCratesNearby") local finddist=self.CrateDistance or 35 @@ -73958,8 +77012,10 @@ for _,_entry in pairs(crates)do local entry=_entry local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -if entry:GetPositionable()then -entry:GetPositionable():Destroy(false) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end table.insert(removedIDs,entry:GetID()) end @@ -73968,8 +77024,17 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -73995,7 +77060,7 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() @@ -74031,8 +77096,16 @@ local cando=cargo:UnitCanCarry(_unit) if ignoretype==true then cando=true end self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distance0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -74264,6 +77347,18 @@ end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -74407,7 +77502,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -74463,6 +77558,7 @@ local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type) @@ -74502,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74512,6 +77611,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74600,6 +77700,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74609,6 +77710,9 @@ end end return self end +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then @@ -74625,11 +77729,19 @@ self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end -local finddist=self.CrateDistance or 35 +local baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -74639,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -74661,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -74689,19 +77812,55 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end self:__CratesBuildStarted(1,Group,Unit,build.Name) -self:_RefreshDropTroopsMenu(Group,Unit) else self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end +end end end end @@ -74714,24 +77873,32 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) self:_RefreshLoadCratesMenu(Group,Unit) self:__CratesPacked(1,Group,Unit,_entry) -return true +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end +if not packedAny then self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end +return true +end function CTLD:_RepairCrates(Group,Unit,Engineering) self:T(self.lid.." _RepairCrates") local finddist=self.CrateDistance or 35 @@ -74836,11 +78003,13 @@ local alias=string.format("%s-%d",_template,math.random(1,100000)) if canmove then self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord) else self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord) end @@ -74897,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -74944,13 +78114,57 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -74959,6 +78173,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -75083,85 +78530,112 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu,subcatmenus) +if cargoObj.DontShowInMenu then +return +end +local isStaticCargo=false +if(cargoObj.GetType and cargoObj:GetType()==CTLD_CARGO.Enum.STATIC)or cargoObj.CargoType==CTLD_CARGO.Enum.STATIC then +isStaticCargo=true +end +if isStaticCargo and cargoObj.UnitCanCarry and not cargoObj:UnitCanCarry(_unit)then +return +end +local parent=parentMenu +if subcatmenus and cargoObj.Subcategory then +parent=subcatmenus[cargoObj.Subcategory] +if not parent then +parent=MENU_GROUP:New(_group,cargoObj.Subcategory,cratesmenu) +subcatmenus[cargoObj.Subcategory]=parent +end +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for catName,_ in pairs(self.subcats)do -subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) -end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} -for catName,_ in pairs(self.subcats)do +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end +return subcatmenus[catName] +end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75169,8 +78643,14 @@ MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,c end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75219,6 +78699,43 @@ MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,sel end end end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end +else +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) @@ -75241,6 +78758,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75252,6 +78771,10 @@ end function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then @@ -75285,7 +78808,7 @@ lineIndex=lineIndex+1 end end end -function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) local grounded=not self:IsUnitInAir(Unit) local hover=self:CanHoverLoad(Unit) @@ -75316,18 +78839,23 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) local capacity=capabilities.cratelimit or 0 if loadedData.Cratesloaded>=capacity then self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress return self end local spaceLeft=capacity-loadedData.Cratesloaded local toLoad=math.min(found,needed,spaceLeft) if toLoad<1 then self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress return self end local crateIDsLoaded={} @@ -75360,6 +78888,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if details or(not batch)then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75377,8 +78906,29 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75472,6 +79022,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -75515,29 +79066,62 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75554,33 +79138,98 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) -MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then -MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) -end i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75594,7 +79243,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end 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) @@ -75670,6 +79319,7 @@ local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],cType) @@ -75701,6 +79351,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -75731,6 +79382,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -75803,7 +79455,45 @@ table.insert(self.Cargo_Troops,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnits") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Units for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock or nil +entry.Stock0=Stock or nil +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=true +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddUnitsNoMove(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnitsNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."UnitsNoMove for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock +entry.Stock0=Stock +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=false +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) self:T(self.lid.." AddCratesCargo") if not self:_CheckTemplates(Templates)then self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") @@ -75818,11 +79508,41 @@ cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end +cargo.C130TypeName=C130TypeName table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) +self:T(self.lid.." AddCratesCargoNoMove") +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) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +cargo.C130TypeName=C130TypeName +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 local type=CTLD_CARGO.Enum.STATIC @@ -75833,6 +79553,9 @@ if unittemplate and unittemplate.resourcePayload then ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) end local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end @@ -75974,7 +79697,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -75997,9 +79725,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -76333,6 +80097,7 @@ return self end function CTLD:IsCorrectHover(Unit) self:T(self.lid.." IsCorrectHover") +if self:IsFixedWing(Unit)then return false end local outcome=false if self:IsUnitInAir(Unit)then local uspeed=Unit:GetVelocityMPS() @@ -76510,6 +80275,23 @@ Troopstable[genname].GenericCargo=generic end end end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end for _id,_cargo in pairs(self.Cargo_Troops)do local generic=_cargo local genname=generic:GetName() @@ -76540,10 +80322,31 @@ Troopstable[genname].Infield=Troopstable[genname].Infield+1 Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo end else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) end end end +end for _unitname,_loaded in pairs(self.Loaded_Cargo)do local _unit=UNIT:FindByName(_unitname) if _unit and _unit:IsAlive()then @@ -76552,12 +80355,12 @@ local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} for _,_cgo in pairs(loadedcargo)do local cargo=_cgo local type=cargo.CargoType -local gname=cargo.Name +local gname=cargo:GetName() local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) -self:T("Looking at "..gname.." in the helo - type = "..type) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then if gcargo and gcargo:GetStock0()>0 then -self:T("Adding "..gname.." in the helo - type = "..type) +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 end @@ -76625,6 +80428,47 @@ end end return Troopstable end +function CTLD:AddStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock+number +end +break +end +end +return self +end +function CTLD:SetStockUnits(Name,Number) +local name=Name or"none" +local number=Number +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +if number==nil or number==-1 then +_unit.Stock=-1 +else +_unit.Stock=number +end +break +end +end +return self +end +function CTLD:GetStockUnits() +local Stock={} +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +Stock[_unit.Name]=_unit.Stock or-1 +end +return Stock +end function CTLD:AddStockTroops(Name,Number) local name=Name or"none" local number=Number or 1 @@ -76632,6 +80476,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76644,6 +80489,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76656,6 +80502,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76668,6 +80515,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76680,6 +80528,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76692,6 +80541,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76742,6 +80592,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -76753,6 +80604,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -76764,6 +80616,27 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() +end +end +return self +end +function CTLD:RemoveStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock-number +if _unit.Stock<0 then +_unit.Stock=0 +end +end +break end end return self @@ -76908,6 +80781,7 @@ 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(randompositions,20,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitDelayOff() :OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp) :SpawnFromVec2(randomcoord) @@ -77025,12 +80899,14 @@ 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) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitDelayOff() :OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp) :SpawnFromVec2(randomcoord) else self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp) :SpawnFromVec2(randomcoord) end @@ -77210,11 +81086,44 @@ 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) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +for _,_cargo in pairs(Cargotable)do +local Vehicle=_cargo.Positionable +if Vehicle then +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 +end +end +return self +end +function CTLD:OnAfterGetCrates(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:OnAfterRemoveCratesNearby(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) return self end function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) @@ -77242,9 +81151,13 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -77491,11 +81404,22 @@ 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) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +if self.C130GetUnits then +for _,_unit in pairs(self.C130GetUnits)do +if _unit.Name==cargoname then +if type(_unit.Stock)=="number"and _unit.Stock~=-1 then +_unit.Stock0=_unit.Stock0 or _unit.Stock +_unit.Stock=math.max(0,(_unit.Stock or 0)-1) +end +break +end +end +end 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,timestamp) end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +elseif self.loadSavedCrates and(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic=nil if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then @@ -77507,8 +81431,12 @@ injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) 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) injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) -local map=cargotype:GetStaticResourceMap() -injectstatic:SetStaticResourceMap(map) +local unittemplate=_DATABASE:GetStaticUnitTemplate(cargoname) +local ResourceMap=nil +if unittemplate and unittemplate.resourcePayload then +ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) +end +injectstatic:SetStaticResourceMap(ResourceMap) end if injectstatic then self:InjectStatics(dropzone,injectstatic,false,true) @@ -77977,6 +81905,8 @@ CreateRadioBeacons=true, UserSetGroup=nil, AllowIRStrobe=false, IRStrobeRuntime=300, +FARPRescueDistance=500, +EnableMenuSmokeMASH=true, } CSAR.AircraftType={} CSAR.AircraftType["SA342Mistral"]=2 @@ -77990,13 +81920,14 @@ CSAR.AircraftType["Mi-24P"]=8 CSAR.AircraftType["Mi-24V"]=8 CSAR.AircraftType["Bell-47"]=2 CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 CSAR.AircraftType["AH-64D_BLK_II"]=2 CSAR.AircraftType["Bronco-OV-10A"]=2 CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.35" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -78076,12 +82007,13 @@ self.loadtimemax=135 self.radioSound="beacon.ogg" self.beaconRefresher=29 self.allowFARPRescue=true -self.FARPRescueDistance=1000 +self.FARPRescueDistance=500 self.max_units=6 self.useprefix=true self.csarPrefix={"helicargo","MEDEVAC"} self.template=Template or"generic" self.mashprefix={"MASH"} +self.EnableMenuSmokeMASH=true self.autosmoke=false self.autosmokedistance=2000 self.limitmaxdownedpilots=true @@ -78259,6 +82191,9 @@ BeaconName=_unitName..math.random(1,10000) else BeaconName="Ghost-1-1"..math.random(1,10000) end +if _playerName==nil or _playerName==""then +_playerName="AI MIA" +end if(_freq and _freq~=0)then self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) end @@ -78442,11 +82377,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78501,7 +82436,7 @@ end self.takenOff[_event.IniUnitName]=nil local _place=_event.Place if _place==nil then -self:T(self.lid.." Landing Place Nil") +self:T(self.lid.." Landing Place nil") return self end if self.inTransitGroups[_event.IniUnitName]==nil then @@ -78509,7 +82444,10 @@ 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) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -78526,7 +82464,7 @@ 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:T({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") Unit.destroy(_event.initiator) end @@ -78864,7 +82802,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -78882,7 +82820,13 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_distself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end end for _,_mashes in pairs(MashSets)do for _,_mashUnit in pairs(_mashes or{})do @@ -79202,11 +83186,12 @@ _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance,MashName +return _shortestDistance,MashName,Coordinate else return-1 end @@ -79261,6 +83246,9 @@ local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -79275,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -79346,7 +83335,7 @@ local name=_radioUnit:GetName() local Frequency=_freq local Sound="l10n/DEFAULT/"..self.radioSound local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end @@ -79656,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -79665,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -79735,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -79811,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -80089,7 +84096,7 @@ function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength local patrolpoint={} patrolpoint.type=Type or"Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10,15)),math.random(360)) -if Coordinate:IsInstanceOf("ZONE_BASE")then +if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then patrolpoint.IsZonePoint=true patrolpoint.patrolzone=Coordinate patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate() @@ -80740,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -81081,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -81195,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -81473,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -81496,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -81543,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -81732,7 +85741,12 @@ 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) +local mission +if Leg==0 then +mission=AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed) +else +mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) +end mission.type=AUFTRAG.Type.TANKER mission:_SetLogID() mission.refuelSystem=RefuelSystem @@ -82047,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -82159,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -82167,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -82201,6 +86216,20 @@ mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.mi mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -82336,33 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) elseif MissionType==AUFTRAG.Type.STRAFING then -mission=self:NewSTRAFING(Target,Altitude) +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -82423,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -82986,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -83190,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -83436,12 +87476,18 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 -if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) return false end return true @@ -83706,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -84248,6 +88294,15 @@ param.action="Wedge" param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 +DCStask.params=param +table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then local DCStask={} DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY @@ -84508,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.72", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -85125,6 +89180,11 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end function AWACS:SetMaxMissionRange(NM) self.MaxMissionRange=NM or 125 return self @@ -85384,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -85397,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -86875,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -86908,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -86933,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -87025,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -87568,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -88211,24 +92324,36 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) @@ -89243,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -89272,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.1" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -90302,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -90526,7 +94712,7 @@ descriptors={}, properties={}, operations={}, } -COHORT.version="0.3.6" +COHORT.version="0.3.7" _COHORTNAMES={} function COHORT:New(TemplateGroupName,Ngroups,CohortName) local name=tostring(CohortName or TemplateGroupName) @@ -90677,9 +94863,10 @@ function COHORT:SetMissionRange(Range) self.engageRange=UTILS.NMToMeters(Range or 150) return self end -function COHORT:SetCallsign(Callsign,Index) +function COHORT:SetCallsign(Callsign,Index,CallsignString) self.callsignName=Callsign self.callsignIndex=Index +self.callsignClearName=CallsignString self.callsign={} self.callsign.NumberSquad=Callsign self.callsign.NumberGroup=Index @@ -90738,8 +94925,11 @@ end end return self end -function COHORT:RemoveAssets(N) +function COHORT:RemoveAssets(N,Delay) self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else N=N or 1 local n=0 for i=#self.assets,1,-1 do @@ -90747,6 +94937,7 @@ local asset=self.assets[i] self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) if not(asset.requested or asset.spawned or asset.isReserved)then self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) table.remove(self.assets,i) n=n+1 else @@ -90757,6 +94948,7 @@ break end end self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end return self end function COHORT:GetName() @@ -90779,6 +94971,9 @@ self.callsigncounter=self.callsigncounter+2 else self.callsigncounter=self.callsigncounter+1 end +callsign["name"]=self.callsignClearName or UTILS.GetCallsignName(self.callsignName)or"None" +callsign["name"]=string.format("%s%d%d",callsign["name"],callsign[2],callsign[3]) +callsign[4]=callsign["name"] Asset.callsign[i]=callsign self:T3({callsign=callsign}) end @@ -96688,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -96912,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -96919,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -96972,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -96979,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -97011,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -97177,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -97218,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -97432,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -98281,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98336,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -98790,6 +103087,7 @@ if self:IsAirwing()then opsgroup=FLIGHTGROUP:New(asset.spawngroupname) elseif self:IsBrigade()then opsgroup=ARMYGROUP:New(asset.spawngroupname) +opsgroup:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) elseif self:IsFleet()then opsgroup=NAVYGROUP:New(asset.spawngroupname) else @@ -98826,7 +103124,7 @@ text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsR for _,mtype in pairs(AUFTRAG.Type)do local n=self:CountMissionsInQueue(mtype) if n>0 then -local N=self:CountMissionsInQueue(mtype) +local N=self:CountMissionsInQueue(mtype,true) text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) end end @@ -98921,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -99683,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.3" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -100050,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -100274,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -100282,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -100298,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -100316,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -102346,6 +106648,12 @@ end function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +function OPSGROUP:IsOutOfA2GAmmo() +if(self.outofMissilesAG and self.outofBombs and self.outofGuns)or self.outofAmmo then +return true +end +return false +end function OPSGROUP:IsLasing() return self.spot.On end @@ -103125,14 +107433,32 @@ end function OPSGROUP:_UpdateTask(Task,Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +if Mission.type==AUFTRAG.Type.RESCUEHELO then +self:T("**********") +self:T("** RESCUEHELO USED") +self:T("**********") +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +local helogroupname=self:GetGroup():GetName() +Task.formation=RESCUEHELO:New(followUnit,helogroupname) +Task.formation:SetRespawnOnOff(false) +Task.formation.respawninair=false +Task.formation:SetTakeoffCold() +Task.formation:SetHomeBase(followUnit) +Task.formation.helo=self:GetGroup() +Task.formation:Start() +if self:IsFlightgroup()then +self:SetDespawnAfterLanding() +end +else 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=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION) 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() +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then local zone=Task.dcstask.params.zone local surfacetypes=nil @@ -103216,6 +107542,15 @@ end if target then self:EngageTarget(target,speed,Task.dcstask.params.formation) end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT 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=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.altitude) +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then if self.isFlightgroup then self:T("We are Special Auftrag Patrol Race Track, starting now ...") @@ -103275,7 +107610,12 @@ 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())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -103323,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -103399,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -103779,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -104059,10 +108399,12 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then @@ -104642,7 +108984,7 @@ 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:T(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() @@ -104789,8 +109131,11 @@ function OPSGROUP:_Spawn(Delay,Template) if Delay and Delay>0 then self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) else -self:T2({Template=Template}) +if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end self.group=_DATABASE:Spawn(Template) +self.group:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) self.dcsgroup=self:GetDCSGroup() self.controller=self.dcsgroup:getController() self.isLateActivated=Template.lateActivation @@ -104853,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -104911,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -106426,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -107194,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -107559,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -107574,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -107594,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -107624,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -107669,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -107916,6 +112279,9 @@ end end return targetgroup,targetdist end +function OPSGROUP:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end OPSTRANSPORT={ ClassName="OPSTRANSPORT", verbose=0, @@ -109715,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -109842,6 +114211,13 @@ end end) return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -109878,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110275,10 +114661,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -110321,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -110381,6 +114770,12 @@ Scoring=nil, MenuNoTask=nil, InformationMenu=false, TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -110409,6 +114804,12 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.CAP]=100, [AUFTRAG.Type.CAPTUREZONE]=100, } +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, +} PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, AAA=GROUP.Attribute.GROUND_AAA, @@ -110437,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -110521,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -110584,7 +114991,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -110655,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -111010,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -111562,17 +115982,24 @@ end PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end +self:__TaskAdded(10,PlayerTask,Silent) else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end return self end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) self:T({Force,Group,Client}) self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end local force=false if type(Force)=="boolean"then force=Force @@ -111657,7 +116084,7 @@ 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 +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) @@ -111700,8 +116127,9 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -111717,7 +116145,12 @@ 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) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -111819,8 +116252,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -111873,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -111882,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -112290,6 +116738,49 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -112421,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -115576,16 +120213,21 @@ return self end function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) self:T(self.lid.."AddPatrolPointCAP") +local coordinate=Coordinate local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end EntryCAP.AirbaseName=AirbaseName -EntryCAP.Coordinate=Coordinate +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() +local mark=MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll() end return self end @@ -115687,8 +120329,13 @@ local Altitude=data.Altitude local Speed=data.Speed local Heading=data.Heading local LegLength=data.LegLength +local Zone=_data.Zone +if Zone then +Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength) +else Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) end +end return self end function EASYGCICAP:_SetReconPatrolPoints() @@ -115837,7 +120484,9 @@ Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetMissionRange(self.missionrange) Squadron_One:SetRadio(Frequency,Modulation) +if TACAN then Squadron_One:AddTacanChannel(TACAN,TACAN) +end local wing=self.wings[AirbaseName][1] wing:AddSquadron(Squadron_One) wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75) @@ -115875,20 +120524,47 @@ self.ConflictZoneSet:AddZone(Zone) self.GoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) -self:I("_TryAssignIntercept for size "..WingSize or 1) +self:T("_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())) +self:T(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)) +self:T(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 @@ -115902,7 +120578,7 @@ FG:AddMission(InterceptAuftrag) local cm=FG:GetMissionCurrent() if cm then cm:Cancel()end wingsize=wingsize-1 -self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) +self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) if wingsize==0 then assigned=true break @@ -115959,6 +120635,9 @@ for _,_data in pairs(ctlpts)do local data=_data local name=data.AirbaseName local zonecoord=data.Coordinate +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end local airwing=wings[name][1] local coa=AIRBASE:FindByName(name):GetCoalition() local samecoalitionab=coa==self.coalition and true or false @@ -115996,6 +120675,8 @@ end if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then success=false end +else +success=true end return success end, @@ -116004,6 +120685,13 @@ nogozoneset, conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -116029,6 +120717,12 @@ BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -116101,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -116111,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -116144,6 +120839,477 @@ _wing:Stop() end return self end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) +return self +end AI_BALANCER={ ClassName="AI_BALANCER", PatrolZones={}, @@ -131818,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -3303,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3860,7 +4407,57 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +4475,61 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4558,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -4417,6 +5045,185 @@ end end return nil end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -7703,7 +8510,7 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName=Event.TgtDCSUnitName Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) @@ -7726,10 +8533,12 @@ 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -9543,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -9553,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9600,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9918,6 +10737,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9979,6 +10808,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -10607,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10629,7 +11468,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true self.ScanData.Units[ZoneObject]=ZoneObject end end -if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then +if ObjectCategory==Object.Category.SCENERY then local SceneryType=ZoneObject:getTypeName() local SceneryName=ZoneObject:getName() self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} @@ -11392,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -12071,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -12385,6 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -12426,6 +13283,20 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +end +return self +end function SET_BASE:_Find(ObjectName) local ObjectFound=self.Set[ObjectName] return ObjectFound @@ -12738,7 +13609,8 @@ return true end function SET_BASE:IsInSet(Object) local outcome=false -local name=Object:GetName() +if Object==nil then return false end +local name=(Object~=nil and Object.GetName)and Object:GetName()or"none" self:ForEach( function(object) if object:GetName()==name then @@ -12909,16 +13781,6 @@ self.Filter.Zones[zonename]=Zone end return self end -function SET_GROUP:FilterCoalitions(Coalitions,Clear) -if Clear or(not self.Filter.Coalitions)then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_GROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -13373,16 +14235,6 @@ function SET_UNIT:FindUnit(UnitName) local UnitFound=self.Set[UnitName] return UnitFound end -function SET_UNIT:FilterCoalitions(Coalitions) -self.Filter.Coalitions={} -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_UNIT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14027,18 +14879,6 @@ 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={} @@ -14465,18 +15305,6 @@ self.Filter.Playernames[playername]=playername end return self end -function SET_CLIENT: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_CLIENT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14650,8 +15478,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -14866,18 +15693,6 @@ function SET_PLAYER:FindClient(PlayerName) local ClientFound=self.Set[PlayerName] return ClientFound end -function SET_PLAYER: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_PLAYER:FilterZones(Zones) if not self.Filter.Zones then self.Filter.Zones={} @@ -15131,18 +15946,6 @@ function SET_AIRBASE:GetRandomAirbase() local RandomAirbase=self:GetRandom() return RandomAirbase end -function SET_AIRBASE: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_AIRBASE:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -15303,18 +16106,6 @@ function SET_CARGO:FindCargo(CargoName) local CargoFound=self.Set[CargoName] return CargoFound end -function SET_CARGO: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_CARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -15934,16 +16725,6 @@ self.Filter.Prefixes[Prefix]=Prefix end return self end -function SET_OPSZONE:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_OPSZONE:FilterOnce() for ObjectName,Object in pairs(self.Database)do self:Remove(ObjectName,true) @@ -16160,6 +16941,18 @@ self:Remove(RemoveGroupName) end return self end +function SET_OPSGROUP:CountAlive() +local CountG=0 +local CountU=0 +local Set=self:GetSet() +for GroupID,GroupData in pairs(Set)do +if GroupData and GroupData:IsAlive()then +CountG=CountG+1 +CountU=CountU+GroupData:GetGroup():CountAliveUnits() +end +end +return CountG,CountU +end function SET_OPSGROUP:FindGroup(GroupName) local GroupFound=self.Set[GroupName] return GroupFound @@ -16176,18 +16969,6 @@ function SET_OPSGROUP:FindNavyGroup(GroupName) local GroupFound=self:FindGroup(GroupName) return GroupFound end -function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear) -if Clear or 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_OPSGROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -16669,18 +17450,6 @@ DCargoInclude=DCargoInclude and MClientFunc end return DCargoInclude end -function SET_DYNAMICCARGO: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_DYNAMICCARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -17606,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -17814,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -18693,41 +19480,6 @@ return nil 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 -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 -end -do VELOCITY={ ClassName="VELOCITY", } @@ -20107,6 +20859,9 @@ SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-Curren end end if self.SpawnRandomizeUnits then +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -21032,6 +21787,11 @@ min=9 max=18 ctable=CALLSIGN.F15E end +if SpawnTemplate.units[1].type=="A6E"then +min=4 +max=18 +ctable=CALLSIGN.Intruder +end local callsignnr=math.random(min,max) local callsignname="Enfield" for name,value in pairs(ctable)do @@ -21547,6 +22307,18 @@ function SPAWNSTATIC:InitCargo(IsCargo) self.InitStaticCargo=IsCargo return self end +function SPAWNSTATIC:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWNSTATIC:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWNSTATIC:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead return self @@ -21640,6 +22412,15 @@ end if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end +if self.SpawnHiddenOnPlanner then +Template.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +Template.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +Template.hidden=self.SpawnHiddenOnMap +end if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -21705,7 +22486,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21713,8 +22494,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -21962,20 +22741,17 @@ MARKEROPS_BASE={ ClassName="MARKEROPS", Tag="mytag", Keywords={}, -version="0.1.4", +version="0.1.5", debug=false, Casesensitive=true, } -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) 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","*") @@ -22038,9 +22814,9 @@ end end function MARKEROPS_BASE:_MatchTag(Eventtext) local matches=false -if not self.Casesensitive then +if self.Casesensitive==false then local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then +if string.find(string.lower(Eventtext),type,1,true)then matches=true end else @@ -22076,6 +22852,14 @@ self:UnHandleEvent(EVENTS.MarkAdded) self:UnHandleEvent(EVENTS.MarkChange) self:UnHandleEvent(EVENTS.MarkRemoved) end +function MARKEROPS_BASE:SwitchCaseSensitiveOff() +self.Casesensitive=false +return self +end +function MARKEROPS_BASE:SwitchCaseSensitiveOn() +self.Casesensitive=true +return self +end TEXTANDSOUND={ ClassName="TEXTANDSOUND", version="0.0.1", @@ -22180,7 +22964,7 @@ ClassName="PATHLINE", lid=nil, points={}, } -PATHLINE.version="0.1.1" +PATHLINE.version="0.2.0" function PATHLINE:New(Name) local self=BASE:Inherit(self,BASE:New()) self.name=Name or"Unknown Path" @@ -22279,12 +23063,26 @@ return point.vec2 end return nil end +function PATHLINE:GetLength(Project2D) +local l=0 +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +if Project2D then +l=l+UTILS.VecDist2D(p1.vec2,p2.vec2) +else +l=l+UTILS.VecDist3D(p1.vec3,p2.vec3) +end +end +return l +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) +UTILS.RemoveMark(point.markerID) end else if point.markerID then @@ -22295,6 +23093,31 @@ local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m trigger.action.markToAll(point.markerID,text,point.vec3,"") end end +return self +end +function PATHLINE:DrawLine(Recipient,Color,LineType) +Recipient=Recipient or-1 +Color=Color or{1,0,0,1.0} +LineType=LineType or 1 +local ReadOnly=false +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +p1.lineID=UTILS.GetMarkID() +trigger.action.lineToAll(Recipient,p1.lineID,p1.vec3,p2.vec3,Color,LineType,ReadOnly,"") +end +return self +end +function PATHLINE:UnDrawLine(Delay) +local np=#self.points +for _,_point in pairs(self.points)do +local p=_point +if p.lineID then +UTILS.RemoveMark(p.lineID,Delay) +end +end +return self end function PATHLINE:_CreatePoint(Vec) local point={} @@ -22833,6 +23656,529 @@ end end return self end +VECTOR={ +ClassName="VECTOR", +verbose=0, +} +VECTOR.version="0.1.0" +_VECTORID=0 +VECTOR.__index=VECTOR +function VECTOR:New(x,y,z) +if z==nil then +self=setmetatable({x=x or 0,y=0,z=y or 0},VECTOR) +else +self=setmetatable({x=x or 0,y=y or 0,z=z or 0},VECTOR) +end +_VECTORID=_VECTORID+1 +self.uid=_VECTORID +return self +end +function VECTOR:NewFromVec(Vec) +local vector=VECTOR:New(Vec.x,Vec.y,Vec.z) +return vector +end +function VECTOR:NewFromPolar(r,phi) +local Phi=math.rad(phi) +local x=r*math.cos(phi) +local y=r*math.sin(phi) +local v=VECTOR:New(x,y) +return self +end +function VECTOR:NewFromSpherical(r,theta,phi) +local sinPhi=math.sin(math.rad(phi)) +local cosPhi=math.cos(math.rad(phi)) +local sinTheta=math.sin(math.rad(theta)) +local cosTheta=math.cos(math.rad(theta)) +local x=r*sinTheta*cosPhi +local y=r*sinTheta*sinPhi +local z=r*cosTheta +local v=VECTOR:New(x,y,z) +return self +end +function VECTOR:NewDirectionalVector(a,b) +local x=b.x-a.x +local y +local z +if a.z and b.z then +y=b.y-a.y +z=b.z-a.z +elseif b.z then +y=b.y-0 +z=b.z-a.y +elseif a.z then +y=0-a.y +z=b.y-a.z +else +y=b.y-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:NewFromLLDD(Latitude,Longitude,Altitude) +local vec3=coord.LLtoLO(Latitude,Longitude) +self=VECTOR:NewFromVec(vec3) +if Altitude then +self.y=Altitude +end +return self +end +function VECTOR:NewFromLLDMS(Latitude,Longitude,Altitude) +local lat=UTILS.LLDMSstringToDD(Latitude) +local lon=UTILS.LLDMSstringToDD(Longitude) +self=VECTOR:NewFromLLDD(lat,lon,Altitude) +return self +end +function VECTOR:GetVec2() +local vec={x=self.x,y=self.z} +return vec +end +function VECTOR:GetVec3(OnSurface) +local x=self.x +local y=OnSurface and land.getHeight({x=self.x,y=self.z})or self.y +local z=self.z +local vec={x=x,y=y,z=z} +return vec +end +function VECTOR:GetCoordinate(OnSurface) +local vec3=self:GetVec3(OnSurface) +local coordinate=COORDINATE:NewFromVec3(vec3) +return coordinate +end +function VECTOR:GetDistance(Vector,Only2D) +local dx=self.x-Vector.x +local dy=0 +local dz=0 +if Vector.z then +if not Only2D then +dy=self.y-Vector.y +end +dz=self.z-Vector.z +else +dy=0 +dz=self.z-Vector.y +end +local dist=math.sqrt(dx*dx+dy*dy+dz*dz) +return dist +end +function VECTOR:GetDirectionalVectorTo(a) +local x=a.x-self.x +local y=0 +local z=nil +if a.z then +y=a.y-self.y +z=a.z-self.z +else +y=a.y-self.z +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetDirectionalVectorFrom(a) +local x=self.x-a.x +local y +local z +if a.z then +y=self.y-a.y +z=self.z-a.z +else +y=self.z-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetLength() +local l=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) +return l +end +function VECTOR:GetHeading(To360) +local heading=math.atan2(self.z,self.x) +heading=math.deg(heading) +if To360==nil or To360==true then +heading=UTILS.AdjustHeading360(heading) +else +if heading==360.0 then +heading=0.0 +end +end +return heading +end +function VECTOR:GetHeadingTo(Vector) +local a=self:GetDirectionalVectorTo(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetHeadingFrom(Vector) +local a=self:GetDirectionalVectorFrom(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetLatitudeLongitude() +local vec3=self:GetVec3() +local latitude,longitude,altitude=coord.LOtoLL(vec3) +return latitude,longitude +end +function VECTOR:GetMGRS() +local lat,long=self:GetLatitudeLongitude() +local mgrs=coord.LLtoMGRS(lat,long) +return mgrs +end +function VECTOR:GetHeadingDelta(Vector) +local h1=self:GetHeading(false) +local h2=Vector:GetHeading(false) +local delta=h2-h1 +return delta +end +function VECTOR:GetIntermediateVector(Vector,Fraction) +local f=Fraction or 0.5 +local vec=self:GetDirectionalVectorTo(Vector) +local length=vec:GetLength() +vec:SetLength(f*length) +vec=self+vec +return vec +end +function VECTOR:SetLength(Length) +self:Normalize() +local v=self*Length +self:Replace(v) +return self +end +function VECTOR:SetX(x) +self.x=x or 0 +return self +end +function VECTOR:SetY(y) +if y==nil then +y=self:GetSurfaceHeight() +end +self.y=y +return self +end +function VECTOR:SetZ(z) +self.z=z or 0 +return self +end +function VECTOR:AddVec(Vec) +self.x=self.x+Vec.x +if Vec.z then +self.y=self.y+Vec.y +self.z=self.z+Vec.z +else +self.z=self.z+Vec.y +end +return self +end +function VECTOR:SubVec(Vec) +self.x=self.x-Vec.x +if Vec.z then +self.y=self.y-Vec.y +self.z=self.z-Vec.z +else +self.z=self.z-Vec.y +end +return self +end +function VECTOR:Dot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Rot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Copy() +local c=VECTOR:New(self.x,self.y,self.z) +return c +end +function VECTOR:Replace(Vector,Project2D) +self.x=Vector.x +if Vector.z then +self.y=Vector.y +self.z=Vector.z +else +if Project2D then +self.y=0 +end +self.z=Vector.y +end +return self +end +function VECTOR:Normalize() +local l=self:GetLength() +if l~=0 then +self:Replace(self/l) +end +return self +end +function VECTOR:Translate(Distance,Heading,Copy) +Distance=Distance or 1000 +local alpha=math.rad(Heading or 0) +local vector=Copy and self:Copy()or self +vector.x=Distance*math.cos(alpha)+vector.x +vector.z=Distance*math.sin(alpha)+vector.z +return vector +end +function VECTOR:Rotate2D(Angle,Copy) +local phi=-math.rad(Angle or 0) +local sinPhi=math.sin(phi) +local cosPhi=math.cos(phi) +local X=self.z +local Y=self.x +local z=X*cosPhi-Y*sinPhi +local x=X*sinPhi+Y*cosPhi +if Copy then +local vector=VECTOR:New(x,self.y,z) +return vector +else +self:SetX(x) +self:SetZ(z) +return self +end +end +function VECTOR: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) +local text="MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +return text +end +function VECTOR:GetSurfaceType() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +return s +end +function VECTOR:GetSurfaceTypeName() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +for name,id in land.SurfaceType()do +if id==s then +return name +end +end +return"unknown" +end +function VECTOR:IsVisible(Vec) +local vec1=self:GetVec3() +local vec2={x=Vec.x,Vec.y,Vec.z} +local los=land.isVisible(vec1,vec2) +return los +end +function VECTOR:GetClosestRoad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('roads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetClosestRailroad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('railroads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetPathOnRoad(Vec) +local vec1=self:GetVec2() +local vec2=Vec:GetVec2() +local vec2points=land.findPathOnRoads("roads",vec1.x,vec1.y,vec2.x,vec2.y) +local path=nil +if vec2points then +path=PATHLINE:NewFromVec2Array("Road",vec2points) +end +return path +end +function VECTOR:GetProfile(Vec3) +local vec3=self:GetVec3() +local vec3s=land.profile(vec3,Vec3) +local profile=nil +if vec3s then +profile=PATHLINE:NewFromVec3Array("Profile",vec3s) +end +return profile +end +function VECTOR:GetInterceptPoint(DirectionVector,Distance) +local vec3=self:GetVec3() +local ip3=land.getIP(vec3,DirectionVector,Distance or 1000) +local ipvector=nil +if ip3 then +ipvector=VECTOR:New(ip3.x,ip3.y,ip3.z) +end +return ipvector +end +function VECTOR:GetSurfaceHeight() +local vec2=self:GetVec2() +local h=land.getHeight(vec2) +return h +end +function VECTOR:GetSurfaceHeightAndDepth() +local vec2=self:GetVec2() +local h,d=land.getSurfaceHeightWithSeabed(vec2) +return h,d +end +function VECTOR:GetWindVector(WithTurbulence) +local vec3=self:GetVec3() +local wind=nil +if WithTurbulence then +wind=atmosphere.getWindWithTurbulence(vec3) +else +wind=atmosphere.getWind(vec3) +end +local vector=VECTOR:New(wind) +return vector +end +function VECTOR:GetTemperaturAndPressure() +local vec3=self:GetVec3() +local t,p=atmosphere.getTemperatureAndPressure(vec3) +return t,p +end +function VECTOR:Smoke(Color,Duration) +local vec3=self:GetVec3() +Color=Color or 0 +local name=string.format("Vector-Smoke-%d",self.uid) +trigger.action.smoke(vec3,Color,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -22932,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -23079,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23116,6 +24483,16 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetPointVec2() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23147,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -23154,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -23314,12 +24693,18 @@ end BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -23439,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -23934,6 +25329,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -26196,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -27852,33 +29288,45 @@ end function GROUP:GetTypeName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +end return nil end function GROUP:GetNatoReportingName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +end return"Bogey" end function GROUP:GetPlayerName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +end return nil end function GROUP:GetCallsign() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -27898,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -28549,7 +30006,7 @@ function GROUP:GetTaskMission() return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) end function GROUP:GetTaskRoute() -if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) else return{} @@ -28563,7 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -28585,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -28828,6 +30285,10 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +return self +end function GROUP:SetCommandInvisible(switch) return self:CommandSetInvisible(switch) end @@ -29954,6 +31415,20 @@ end function UNIT:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits=Enabled end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -30261,6 +31736,7 @@ self.Life0=Life0 else self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -30273,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -30338,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30348,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30357,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30446,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -30454,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -30476,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -30486,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -30504,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -30511,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -30524,18 +32028,15 @@ AIRBASE.Normandy={ ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -["Hawkinge"]="Hawkinge", } 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -30578,13 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -30600,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -30612,16 +32115,17 @@ AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", -["Marka"]="Marka", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -30638,17 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Ben_Gurion"]="Ben Gurion", -["Hatzor"]="Hatzor", -["Palmachim"]="Palmachim", -["Tel_Nof"]="Tel Nof", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -30656,8 +32157,7 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } -AIRBASE.MarianaIslandsWWII= -{ +AIRBASE.MarianaIslandsWWII={ ["Agana"]="Agana", ["Airfield_3"]="Airfield 3", ["Charon_Kanoa"]="Charon Kanoa", @@ -30720,6 +32220,7 @@ AIRBASE.Sinai={ ["Cairo_West"]="Cairo West", ["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["El_Minya"]="El Minya", @@ -30731,8 +32232,11 @@ AIRBASE.Sinai={ ["Inshas_Airbase"]="Inshas Airbase", ["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", ["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", ["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", @@ -30746,45 +32250,50 @@ AIRBASE.Sinai={ ["Sde_Dov"]="Sde Dov", ["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", ["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", ["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", ["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", ["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", ["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", ["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", ["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", ["Severomorsk_1"]="Severomorsk-1", ["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", ["Vidsel"]="Vidsel", ["Vuojarvi"]="Vuojarvi", -["Andoya"]="Andoya", -["Alakurtti"]="Alakurtti", -["Kittila"]="Kittila", -["Bardufoss"]="Bardufoss", -["Alta"]="Alta", -["Sodankyla"]="Sodankyla", -["Enontekio"]="Enontekio", -["Evenes"]="Evenes", -["Hosio"]="Hosio", -["Kilpyavr"]="Kilpyavr", -["Afrikanda"]="Afrikanda", -["Kalevala"]="Kalevala", -["Koshka_Yavr"]="Koshka Yavr", -["Poduzhemye"]="Poduzhemye", -["Luostari_Pechenga"]="Luostari Pechenga", } AIRBASE.Afghanistan={ ["Bagram"]="Bagram", @@ -30794,6 +32303,10 @@ AIRBASE.Afghanistan={ ["Camp_Bastion_Heliport"]="Camp Bastion Heliport", ["Chaghcharan"]="Chaghcharan", ["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", ["Farah"]="Farah", ["Gardez"]="Gardez", ["Ghazni_Heliport"]="Ghazni Heliport", @@ -30803,7 +32316,6 @@ AIRBASE.Afghanistan={ ["Kandahar"]="Kandahar", ["Kandahar_Heliport"]="Kandahar Heliport", ["Khost"]="Khost", -["Khost_Heliport"]="Khost Heliport", ["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", ["Nimroz"]="Nimroz", ["Qala_i_Naw"]="Qala i Naw", @@ -30814,21 +32326,28 @@ AIRBASE.Afghanistan={ ["Urgoon_Heliport"]="Urgoon Heliport", } AIRBASE.Iraq={ -["Baghdad_International_Airport"]="Baghdad International Airport", -["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", -["Al_Sahra_Airport"]="Al-Sahra Airport", -["Erbil_International_Airport"]="Erbil International Airport", -["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", ["Al_Salam_Airbase"]="Al-Salam Airbase", -["Balad_Airbase"]="Balad Airbase", -["Kirkuk_International_Airport"]="Kirkuk International Airport", -["Bashur_Airport"]="Bashur Airport", +["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Taquddum_Airport"]="Al-Taquddum Airport", -["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", ["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", } AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", ["Airracing_Frankfurt"]="Airracing Frankfurt", ["Airracing_Koblenz"]="Airracing Koblenz", ["Airracing_Luebeck"]="Airracing Lubeck", @@ -30839,16 +32358,23 @@ AIRBASE.GermanyCW={ ["Bienenfarm"]="Bienenfarm", ["Bindersleben"]="Bindersleben", ["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", ["Braunschweig"]="Braunschweig", ["Bremen"]="Bremen", ["Briest"]="Briest", ["Buechel"]="Buchel", ["Bueckeburg"]="Buckeburg", ["Celle"]="Celle", +["Chojna"]="Chojna", ["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", ["Damgarten"]="Damgarten", ["Dedelow"]="Dedelow", ["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", ["Fassberg"]="Fassberg", ["Finow"]="Finow", ["Frankfurt"]="Frankfurt", @@ -30859,7 +32385,7 @@ AIRBASE.GermanyCW={ ["Gatow"]="Gatow", ["Gelnhausen"]="Gelnhausen", ["Giebelstadt"]="Giebelstadt", -["Glindbruchkippe"]="Glindbruchkippe ", +["Glindbruchkippe"]="Glindbruchkippe", ["Gross_Mohrdorf"]="Gross Mohrdorf", ["Grosse_Wiese"]="Grosse Wiese", ["Guetersloh"]="Gutersloh", @@ -30977,43 +32503,60 @@ AIRBASE.GermanyCW={ ["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", ["Hannover"]="Hannover", ["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", ["Herrenteich"]="Herrenteich", ["Hildesheim"]="Hildesheim", ["Hockenheim"]="Hockenheim", ["Holzdorf"]="Holzdorf", ["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", ["Koethen"]="Kothen", ["Laage"]="Laage", +["Landstuhl"]="Landstuhl", ["Langenselbold"]="Langenselbold", ["Laerz"]="Larz", -["Leipzig_Halle"]="Leipzig Halle", ["Leipzig_Mockau"]="Leipzig Mockau", ["Luebeck"]="Lubeck", ["Lueneburg"]="Luneburg", ["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", ["Mendig"]="Mendig", ["Merseburg"]="Merseburg", ["Neubrandenburg"]="Neubrandenburg", ["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", ["Northeim"]="Northeim", +["Noervenich"]="Norvenich", ["Ober_Moerlen"]="Ober-Morlen", ["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", ["Parchim"]="Parchim", ["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", ["Pferdsfeld"]="Pferdsfeld", ["Pinnow"]="Pinnow", ["Pottschutthoehe"]="Pottschutthohe", ["Ramstein"]="Ramstein", +["Revinge"]="Revinge", ["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", ["Schoenefeld"]="Schonefeld", ["Schweinfurt"]="Schweinfurt", ["Sembach"]="Sembach", +["Sittensen"]="Sittensen", ["Spangdahlem"]="Spangdahlem", ["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", ["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", ["Tegel"]="Tegel", ["Tempelhof"]="Tempelhof", ["Templin"]="Templin", +["Thurland"]="Thurland", ["Tutow"]="Tutow", ["Uelzen"]="Uelzen", ["Uetersen"]="Uetersen", @@ -31029,6 +32572,7 @@ AIRBASE.GermanyCW={ ["Worms"]="Worms", ["Wunstorf"]="Wunstorf", ["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", ["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ @@ -31096,9 +32640,19 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -31345,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -32036,16 +33617,55 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end if self.SceneryObject and self.SceneryObject.getLife then -self.Life0=self.SceneryObject:getLife()or 0 +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) @@ -32064,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 +local life=1 if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -32104,14 +33746,16 @@ end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -32130,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -32154,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -32183,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -33036,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -33508,7 +35167,7 @@ self:SetLiquid(lqno,lqam) end end else -self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end if self:IsLimitedAircraft()then @@ -33526,7 +35185,7 @@ self:SetAmount(acname,acnumber) end end else -self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end if self:IsLimitedWeapons()then @@ -33555,7 +35214,7 @@ end end end else -self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end return self @@ -33626,6 +35285,9 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33634,8 +35296,26 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.1.0" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -33858,13 +35538,29 @@ end end self.LastPosition=pos else -if self.timer and self.timer:IsRunning()then self.timer:Stop()end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") self.CargoState=DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end return self end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end function DYNAMICCARGO._FilterHeloTypes(client) if not client then return false end local typename=client:GetTypeName() @@ -35200,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -35213,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -35235,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -35247,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -35274,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -35306,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -35313,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -35452,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -35562,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -35588,6 +37412,7 @@ local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -35601,7 +37426,6 @@ 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 @@ -35613,14 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName -if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then TargetCategory=Unit.Category.STRUCTURE TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") end 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) @@ -35629,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -35640,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -35712,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -35730,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -35819,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -35918,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -35935,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -36173,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -36196,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -36204,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -36272,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -36293,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -43849,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -43860,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -43886,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -43916,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -43939,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -43954,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -43963,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -44575,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -44607,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -44626,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -46820,7 +48690,7 @@ maxrange=7000, reloadtime=nil, }, } -ARTY.version="1.3.3" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -47500,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -51029,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -51326,7 +53220,6 @@ 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) @@ -51430,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -51632,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -52441,7 +54334,6 @@ 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 @@ -54931,7 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", -version="0.9.34", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -54982,6 +56874,9 @@ SmokeDecoyColor=SMOKECOLOR.White, checkcounter=1, DLinkCacheTime=120, logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -55027,10 +56922,14 @@ MANTIS.SamData={ ["SA-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, ["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"}, -["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, ["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, ["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, ["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, @@ -55109,13 +57008,14 @@ MANTIS.SamDataCH={ ["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} @@ -55227,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -55268,6 +57273,69 @@ self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -55557,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -55566,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -55579,7 +57661,7 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end if self.checkforfriendlies==true and self.friendlyset==nil then self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() @@ -55635,9 +57717,27 @@ self:T(self.lid.."Starting Intel Detection") local groupset=self.EWR_Group local samset=self.SAM_Group self.intelset={} -local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne") +local IntelOne=INTEL:New(groupset,self.coalition,self.name.." IntelOne") +IntelOne.DetectAccoustic=self.DetectAccoustic +IntelOne.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelOne.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelOne:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelOne:Start() -local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo") +local IntelTwo=INTEL:New(samset,self.coalition,self.name.." IntelTwo") +IntelTwo.DetectAccoustic=self.DetectAccoustic +IntelTwo.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelTwo.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelTwo:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelTwo:Start() local CacheTime=self.DLinkCacheTime or 120 local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) @@ -55893,6 +57993,22 @@ self:T(self.lid.."RemoveShorad") self.ShoradLink=false return self end +function MANTIS:_SmokeUnits(Group) +self:T("Smoking") +local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME")or 0 +local TNow=timer.getTime() +if TNow-LastSmoketime>290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 @@ -55906,7 +58022,7 @@ local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 -local shortsam=(_data[6]==MANTIS.SamType.SHORT)and true or false +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false if not shortsam then shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false end @@ -55931,17 +58047,10 @@ switch=true end if self.SamStateTracker[name]~="RED"and switch then self:__RedState(1,samgroup) -self.SamStateTracker[name]="RED" end -if shortsam==true and self.SmokeDecoy==true then +if shortsam==true and self.SmokeDecoy==true and 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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -60798,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -62764,7 +65158,7 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then @@ -62784,13 +65178,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -62820,7 +65214,7 @@ return aoa end function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -62830,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -63463,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -63826,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -64092,7 +66487,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -64114,6 +66514,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -64856,77 +67278,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -65214,19 +67568,19 @@ end if 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" +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" +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)" +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)" +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 @@ -65841,7 +68195,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true) text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) local unitClient=Unit.getByName(unit:GetName()) local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early if hornet then local nozzlePosL=0 local burnerPosL=unitClient:getDrawArgumentValue(28) @@ -65957,7 +68311,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89) else nozzlePosL=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosL=unitClient:getDrawArgumentValue(434) end return nozzlePosL @@ -65974,7 +68328,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90) else nozzlePosR=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosR=unitClient:getDrawArgumentValue(433) end return nozzlePosR @@ -66025,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -66126,7 +68478,7 @@ 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=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -66317,7 +68669,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -66332,6 +68684,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66439,14 +68792,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66494,6 +68841,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66510,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -67557,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -70463,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -70509,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -70707,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -70726,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -70736,14 +73093,12 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) return self @@ -72802,6 +75157,9 @@ self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false self.ResourceMap=nil self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end self.StaticShape=nil self.TypeNames=nil self.StaticCategory="Cargos" @@ -72809,6 +75167,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -72865,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -72890,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -73181,7 +75544,9 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, @@ -73196,8 +75561,9 @@ CTLD.FixedWingTypes={ ["Hercules"]="Hercules", ["Bronco"]="Bronco", ["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", } -CTLD.version="1.3.37" +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -73240,12 +75606,15 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") self:AddTransition("*","CratesPacked","*") self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") @@ -73286,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -73299,7 +75670,10 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false self.enableFixedWing=false @@ -73326,6 +75700,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -73338,6 +75713,8 @@ self.filename=string.format("CTLD_%s_Persist.csv",AliaS) self.allowcratepickupagain=true self.enableslingload=false self.basetype="container_cargo" +self.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do @@ -73418,6 +75795,15 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end if _unit:IsHelicopter()or _group:IsHelicopter()then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil @@ -73453,6 +75839,7 @@ end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end elseif event.id==EVENTS.NewDynamicCargo then self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo @@ -73478,6 +75865,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73514,6 +75902,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73521,6 +75910,13 @@ self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -73548,6 +75944,12 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end function CTLD:AddAllowedFixedWingType(typename) @@ -73697,6 +76099,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -73792,7 +76195,7 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) local hassecondaries=false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!",10,false,Group) @@ -73921,18 +76324,340 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -73961,7 +76686,7 @@ 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 +if not self.debug then return false end end end end @@ -73971,13 +76696,16 @@ local loaddist=self.CrateDistance or 35 local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false +end +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -73992,6 +76720,7 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 @@ -74034,8 +76763,12 @@ self.CrateCounter=self.CrateCounter+1 local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() local basetype=CType or self.basetype or"container_cargo" CCat=CCat or"Cargos" -if isstatic then -basetype=cratetemplate +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -74081,8 +76814,8 @@ 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) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -74094,23 +76827,35 @@ realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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) else +if not quiet then self:_SendMessage(text,10,false,Group) end +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -74209,6 +76954,52 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis end return self end +function CTLD:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +end +return self +end function CTLD:_RemoveCratesNearby(_group,_unit) self:T(self.lid.." _RemoveCratesNearby") local finddist=self.CrateDistance or 35 @@ -74221,8 +77012,10 @@ for _,_entry in pairs(crates)do local entry=_entry local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -if entry:GetPositionable()then -entry:GetPositionable():Destroy(false) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end table.insert(removedIDs,entry:GetID()) end @@ -74231,8 +77024,17 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -74258,7 +77060,7 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() @@ -74294,8 +77096,16 @@ local cando=cargo:UnitCanCarry(_unit) if ignoretype==true then cando=true end self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distance0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -74527,6 +77347,18 @@ end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -74670,7 +77502,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -74766,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74776,6 +77611,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74864,6 +77700,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74873,6 +77710,9 @@ end end return self end +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then @@ -74889,11 +77729,19 @@ self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end -local finddist=self.CrateDistance or 35 +local baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -74903,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -74925,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -74953,19 +77812,55 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end self:__CratesBuildStarted(1,Group,Unit,build.Name) -self:_RefreshDropTroopsMenu(Group,Unit) else self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end +end end end end @@ -74978,24 +77873,32 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) self:_RefreshLoadCratesMenu(Group,Unit) self:__CratesPacked(1,Group,Unit,_entry) -return true +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end +if not packedAny then self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end +return true +end function CTLD:_RepairCrates(Group,Unit,Engineering) self:T(self.lid.." _RepairCrates") local finddist=self.CrateDistance or 35 @@ -75163,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -75210,13 +78114,57 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -75225,6 +78173,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -75349,85 +78530,112 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu,subcatmenus) +if cargoObj.DontShowInMenu then +return +end +local isStaticCargo=false +if(cargoObj.GetType and cargoObj:GetType()==CTLD_CARGO.Enum.STATIC)or cargoObj.CargoType==CTLD_CARGO.Enum.STATIC then +isStaticCargo=true +end +if isStaticCargo and cargoObj.UnitCanCarry and not cargoObj:UnitCanCarry(_unit)then +return +end +local parent=parentMenu +if subcatmenus and cargoObj.Subcategory then +parent=subcatmenus[cargoObj.Subcategory] +if not parent then +parent=MENU_GROUP:New(_group,cargoObj.Subcategory,cratesmenu) +subcatmenus[cargoObj.Subcategory]=parent +end +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for catName,_ in pairs(self.subcats)do -subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) -end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} -for catName,_ in pairs(self.subcats)do +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end +return subcatmenus[catName] +end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75435,8 +78643,14 @@ MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,c end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75485,6 +78699,43 @@ MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,sel end end end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end +else +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) @@ -75507,6 +78758,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75518,6 +78771,10 @@ end function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then @@ -75551,7 +78808,7 @@ lineIndex=lineIndex+1 end end end -function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) local grounded=not self:IsUnitInAir(Unit) local hover=self:CanHoverLoad(Unit) @@ -75582,18 +78839,23 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) local capacity=capabilities.cratelimit or 0 if loadedData.Cratesloaded>=capacity then self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress return self end local spaceLeft=capacity-loadedData.Cratesloaded local toLoad=math.min(found,needed,spaceLeft) if toLoad<1 then self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress return self end local crateIDsLoaded={} @@ -75626,6 +78888,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if details or(not batch)then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75643,8 +78906,29 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75738,6 +79022,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -75781,29 +79066,62 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75820,33 +79138,98 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) -MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then -MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) -end i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75860,7 +79243,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end 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) @@ -75968,6 +79351,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -75998,6 +79382,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -76070,7 +79455,45 @@ table.insert(self.Cargo_Troops,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnits") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Units for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock or nil +entry.Stock0=Stock or nil +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=true +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddUnitsNoMove(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnitsNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."UnitsNoMove for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock +entry.Stock0=Stock +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=false +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) self:T(self.lid.." AddCratesCargo") if not self:_CheckTemplates(Templates)then self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") @@ -76085,11 +79508,41 @@ cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end +cargo.C130TypeName=C130TypeName table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) +self:T(self.lid.." AddCratesCargoNoMove") +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) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +cargo.C130TypeName=C130TypeName +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 local type=CTLD_CARGO.Enum.STATIC @@ -76100,6 +79553,9 @@ if unittemplate and unittemplate.resourcePayload then ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) end local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end @@ -76241,7 +79697,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -76264,9 +79725,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -76600,6 +80097,7 @@ return self end function CTLD:IsCorrectHover(Unit) self:T(self.lid.." IsCorrectHover") +if self:IsFixedWing(Unit)then return false end local outcome=false if self:IsUnitInAir(Unit)then local uspeed=Unit:GetVelocityMPS() @@ -76777,6 +80275,23 @@ Troopstable[genname].GenericCargo=generic end end end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end for _id,_cargo in pairs(self.Cargo_Troops)do local generic=_cargo local genname=generic:GetName() @@ -76807,10 +80322,31 @@ Troopstable[genname].Infield=Troopstable[genname].Infield+1 Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo end else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) end end end +end for _unitname,_loaded in pairs(self.Loaded_Cargo)do local _unit=UNIT:FindByName(_unitname) if _unit and _unit:IsAlive()then @@ -76819,12 +80355,12 @@ local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} for _,_cgo in pairs(loadedcargo)do local cargo=_cgo local type=cargo.CargoType -local gname=cargo.Name +local gname=cargo:GetName() local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) -self:T("Looking at "..gname.." in the helo - type = "..type) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then if gcargo and gcargo:GetStock0()>0 then -self:T("Adding "..gname.." in the helo - type = "..type) +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 end @@ -76892,6 +80428,47 @@ end end return Troopstable end +function CTLD:AddStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock+number +end +break +end +end +return self +end +function CTLD:SetStockUnits(Name,Number) +local name=Name or"none" +local number=Number +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +if number==nil or number==-1 then +_unit.Stock=-1 +else +_unit.Stock=number +end +break +end +end +return self +end +function CTLD:GetStockUnits() +local Stock={} +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +Stock[_unit.Name]=_unit.Stock or-1 +end +return Stock +end function CTLD:AddStockTroops(Name,Number) local name=Name or"none" local number=Number or 1 @@ -76899,6 +80476,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76911,6 +80489,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76923,6 +80502,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76935,6 +80515,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76947,6 +80528,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76959,6 +80541,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -77009,6 +80592,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -77020,6 +80604,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -77031,6 +80616,27 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() +end +end +return self +end +function CTLD:RemoveStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock-number +if _unit.Stock<0 then +_unit.Stock=0 +end +end +break end end return self @@ -77480,11 +81086,44 @@ 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) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +for _,_cargo in pairs(Cargotable)do +local Vehicle=_cargo.Positionable +if Vehicle then +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 +end +end +return self +end +function CTLD:OnAfterGetCrates(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:OnAfterRemoveCratesNearby(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) return self end function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) @@ -77512,9 +81151,13 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -77761,11 +81404,22 @@ 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) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +if self.C130GetUnits then +for _,_unit in pairs(self.C130GetUnits)do +if _unit.Name==cargoname then +if type(_unit.Stock)=="number"and _unit.Stock~=-1 then +_unit.Stock0=_unit.Stock0 or _unit.Stock +_unit.Stock=math.max(0,(_unit.Stock or 0)-1) +end +break +end +end +end 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,timestamp) end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +elseif self.loadSavedCrates and(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic=nil if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then @@ -77777,8 +81431,12 @@ injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) 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) injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) -local map=cargotype:GetStaticResourceMap() -injectstatic:SetStaticResourceMap(map) +local unittemplate=_DATABASE:GetStaticUnitTemplate(cargoname) +local ResourceMap=nil +if unittemplate and unittemplate.resourcePayload then +ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) +end +injectstatic:SetStaticResourceMap(ResourceMap) end if injectstatic then self:InjectStatics(dropzone,injectstatic,false,true) @@ -78247,6 +81905,8 @@ CreateRadioBeacons=true, UserSetGroup=nil, AllowIRStrobe=false, IRStrobeRuntime=300, +FARPRescueDistance=500, +EnableMenuSmokeMASH=true, } CSAR.AircraftType={} CSAR.AircraftType["SA342Mistral"]=2 @@ -78260,13 +81920,14 @@ CSAR.AircraftType["Mi-24P"]=8 CSAR.AircraftType["Mi-24V"]=8 CSAR.AircraftType["Bell-47"]=2 CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 CSAR.AircraftType["AH-64D_BLK_II"]=2 CSAR.AircraftType["Bronco-OV-10A"]=2 CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.35" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -78346,12 +82007,13 @@ self.loadtimemax=135 self.radioSound="beacon.ogg" self.beaconRefresher=29 self.allowFARPRescue=true -self.FARPRescueDistance=1000 +self.FARPRescueDistance=500 self.max_units=6 self.useprefix=true self.csarPrefix={"helicargo","MEDEVAC"} self.template=Template or"generic" self.mashprefix={"MASH"} +self.EnableMenuSmokeMASH=true self.autosmoke=false self.autosmokedistance=2000 self.limitmaxdownedpilots=true @@ -78529,6 +82191,9 @@ BeaconName=_unitName..math.random(1,10000) else BeaconName="Ghost-1-1"..math.random(1,10000) end +if _playerName==nil or _playerName==""then +_playerName="AI MIA" +end if(_freq and _freq~=0)then self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) end @@ -78712,11 +82377,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78771,7 +82436,7 @@ end self.takenOff[_event.IniUnitName]=nil local _place=_event.Place if _place==nil then -self:T(self.lid.." Landing Place Nil") +self:T(self.lid.." Landing Place nil") return self end if self.inTransitGroups[_event.IniUnitName]==nil then @@ -78779,7 +82444,10 @@ 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) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -78796,7 +82464,7 @@ 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:T({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") Unit.destroy(_event.initiator) end @@ -79134,7 +82802,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -79152,7 +82820,13 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_distself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end end for _,_mashes in pairs(MashSets)do for _,_mashUnit in pairs(_mashes or{})do @@ -79472,11 +83186,12 @@ _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance,MashName +return _shortestDistance,MashName,Coordinate else return-1 end @@ -79531,6 +83246,9 @@ local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -79545,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -79616,7 +83335,7 @@ local name=_radioUnit:GetName() local Frequency=_freq local Sound="l10n/DEFAULT/"..self.radioSound local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end @@ -79926,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -79935,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -80005,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -80081,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -81010,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -81351,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -81465,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -81743,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -81766,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -81813,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -82322,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -82434,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -82442,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -82476,6 +86216,20 @@ mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.mi mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -82611,33 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) elseif MissionType==AUFTRAG.Type.STRAFING then -mission=self:NewSTRAFING(Target,Altitude) +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -82698,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -83261,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -83465,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -83711,12 +87476,18 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 -if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) return false end return true @@ -83981,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -84523,6 +88294,15 @@ param.action="Wedge" param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 +DCStask.params=param +table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then local DCStask={} DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY @@ -84783,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.72", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -85400,6 +89180,11 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end function AWACS:SetMaxMissionRange(NM) self.MaxMissionRange=NM or 125 return self @@ -85659,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -85672,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -87150,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -87183,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -87208,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -87300,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -87843,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -88486,24 +92324,36 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) @@ -89518,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -89547,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.1" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -90577,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -91014,8 +94925,11 @@ end end return self end -function COHORT:RemoveAssets(N) +function COHORT:RemoveAssets(N,Delay) self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else N=N or 1 local n=0 for i=#self.assets,1,-1 do @@ -91023,6 +94937,7 @@ local asset=self.assets[i] self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) if not(asset.requested or asset.spawned or asset.isReserved)then self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) table.remove(self.assets,i) n=n+1 else @@ -91033,6 +94948,7 @@ break end end self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end return self end function COHORT:GetName() @@ -96967,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -97191,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -97198,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -97251,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -97258,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -97290,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -97456,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -97497,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -97711,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -98560,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98615,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -99106,7 +103124,7 @@ text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsR for _,mtype in pairs(AUFTRAG.Type)do local n=self:CountMissionsInQueue(mtype) if n>0 then -local N=self:CountMissionsInQueue(mtype) +local N=self:CountMissionsInQueue(mtype,true) text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) end end @@ -99201,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -99963,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.3" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -100330,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -100554,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -100562,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -100578,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -100596,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -102626,6 +106648,12 @@ end function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +function OPSGROUP:IsOutOfA2GAmmo() +if(self.outofMissilesAG and self.outofBombs and self.outofGuns)or self.outofAmmo then +return true +end +return false +end function OPSGROUP:IsLasing() return self.spot.On end @@ -103405,14 +107433,32 @@ end function OPSGROUP:_UpdateTask(Task,Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +if Mission.type==AUFTRAG.Type.RESCUEHELO then +self:T("**********") +self:T("** RESCUEHELO USED") +self:T("**********") +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +local helogroupname=self:GetGroup():GetName() +Task.formation=RESCUEHELO:New(followUnit,helogroupname) +Task.formation:SetRespawnOnOff(false) +Task.formation.respawninair=false +Task.formation:SetTakeoffCold() +Task.formation:SetHomeBase(followUnit) +Task.formation.helo=self:GetGroup() +Task.formation:Start() +if self:IsFlightgroup()then +self:SetDespawnAfterLanding() +end +else 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=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION) 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() +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then local zone=Task.dcstask.params.zone local surfacetypes=nil @@ -103496,6 +107542,15 @@ end if target then self:EngageTarget(target,speed,Task.dcstask.params.formation) end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT 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=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.altitude) +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then if self.isFlightgroup then self:T("We are Special Auftrag Patrol Race Track, starting now ...") @@ -103555,7 +107610,12 @@ 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())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -103603,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -103679,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -104059,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -104339,10 +108399,12 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then @@ -104922,7 +108984,7 @@ 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:T(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() @@ -105069,7 +109131,6 @@ function OPSGROUP:_Spawn(Delay,Template) if Delay and Delay>0 then self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) else -self:T2({Template=Template}) if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then UTILS.ValidateAndRepositionGroundUnits(Template.units) end @@ -105137,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -105195,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -106710,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -107478,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -107843,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -107858,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -107878,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -107908,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -107953,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -110002,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -110129,6 +114211,13 @@ end end) return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -110165,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110562,10 +114661,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -110608,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -110668,6 +114770,12 @@ Scoring=nil, MenuNoTask=nil, InformationMenu=false, TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -110696,6 +114804,12 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.CAP]=100, [AUFTRAG.Type.CAPTUREZONE]=100, } +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, +} PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, AAA=GROUP.Attribute.GROUND_AAA, @@ -110724,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -110808,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -110871,7 +114991,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -110942,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -111297,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -111849,17 +115982,24 @@ end PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end +self:__TaskAdded(10,PlayerTask,Silent) else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end return self end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) self:T({Force,Group,Client}) self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end local force=false if type(Force)=="boolean"then force=Force @@ -111944,7 +116084,7 @@ 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 +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) @@ -111987,8 +116127,9 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -112004,7 +116145,12 @@ 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) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -112106,8 +116252,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -112160,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -112169,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -112577,6 +116738,49 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -112708,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -116174,6 +120524,33 @@ self.ConflictZoneSet:AddZone(Zone) self.GoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) self:T("_TryAssignIntercept for size "..WingSize or 1) local assigned=false @@ -116298,6 +120675,8 @@ end if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then success=false end +else +success=true end return success end, @@ -116306,6 +120685,13 @@ nogozoneset, conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -116331,6 +120717,12 @@ BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -116403,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -116413,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -116446,6 +120839,477 @@ _wing:Stop() end return self end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) +return self +end AI_BALANCER={ ClassName="AI_BALANCER", PatrolZones={}, @@ -132120,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -3303,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3860,7 +4407,57 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +4475,61 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4558,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -4152,7 +4780,7 @@ end function UTILS.DoStringIn(State,DoString) return net.dostring_in(State,DoString) end -function UTILS.ShowPicture(FileName,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +function UTILS.ShowPictureToAll(FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) ClearView=ClearView or false StartDelay=StartDelay or 0 HorizontalAlign=HorizontalAlign or 1 @@ -4160,11 +4788,68 @@ VerticalAlign=VerticalAlign or 1 Size=Size or 100 SizeUnits=SizeUnits or 0 if ClearView then ClearView="true"else ClearView="false"end -net.dostring_in("mission",string.format("a_out_picture(getValueResourceByKey(\"%s\"), %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",FileName,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +net.dostring_in("mission",string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToCoalition(Coalition,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +local coalName=string.lower(UTILS.GetCoalitionName(Coalition)) +net.dostring_in("mission",string.format("a_out_picture_s(\"%s\", \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",coalName,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToCountry(Country,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_c(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Country,FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToGroup(Group,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_g(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Group:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.ShowPictureToUnit(Unit,FilePath,Duration,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits) +ClearView=ClearView or false +StartDelay=StartDelay or 0 +HorizontalAlign=HorizontalAlign or 1 +VerticalAlign=VerticalAlign or 1 +Size=Size or 100 +SizeUnits=SizeUnits or 0 +if ClearView then ClearView="true"else ClearView="false"end +net.dostring_in("mission",string.format("a_out_picture_u(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")",Unit:GetID(),FilePath,Duration or 10,ClearView,StartDelay,HorizontalAlign,VerticalAlign,Size,SizeUnits)) +end +function UTILS.LoadMission(FileName) +net.dostring_in("mission",string.format("a_load_mission(\"%s\")",FileName)) +end +function UTILS.SetMissionBriefing(Coalition,Text,Picture) +Text=Text or"" +Text=Text:gsub("\n","\\n") +Picture=Picture or"" +local coalName=string.lower(UTILS.GetCoalitionName(Coalition)) +net.dostring_in("mission",string.format("a_set_briefing(\"%s\", \"%s\", \"%s\")",coalName,Picture,Text)) end function UTILS.ShowHelperGate(pos,heading) net.dostring_in("mission",string.format("a_show_helper_gate(%s, %s, %s, %f)",pos.x,pos.y,pos.z,math.rad(heading))) end +function UTILS.ShowHelperGateForUnit(Unit,Flag) +net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")",Unit:GetID(),Flag)) +end +function UTILS.SetCarrierIlluminationMode(UnitID,Mode) +net.dostring_in("mission",string.format("a_set_carrier_illumination_mode(%d, %d)",UnitID,Mode)) +end function UTILS.ShellZone(name,power,count) local z=UTILS.GetEnvZone(name) if z then @@ -4231,6 +4916,314 @@ qx=qx+shift_factor*norm_dx qy=qy+shift_factor*norm_dy return{x=qx,y=qy} end +function UTILS.ValidateAndRepositionGroundUnits(Positions,Anchor,MaxRadius,Spacing) +local units=Positions +Anchor=Anchor or UTILS.GetCenterPoint(units) +local gPos={x=Anchor.x,y=Anchor.z or Anchor.y} +local maxRadius=0 +local unitCount=0 +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local dist=UTILS.VecDist2D(pos,gPos) +if dist>maxRadius then +maxRadius=dist +end +unitCount=unitCount+1 +end +maxRadius=MaxRadius or math.max(maxRadius*2,10) +local spacing=Spacing or math.max(maxRadius*0.05,5) +if unitCount>0 and maxRadius>5 then +local spots=UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos),maxRadius,spacing,1000) +if spots and#spots>0 then +local validSpots={} +for _,spot in pairs(spots)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +table.insert(validSpots,spot) +end +end +spots=validSpots +end +local step=spacing +for _,unit in pairs(units)do +local pos={x=unit.x,y=unit.z or unit.y} +local isOnLand=land.getSurfaceType(pos)==land.SurfaceType.LAND +local isValid=false +if spots and#spots>0 then +local si=1 +local sid=0 +local closestDist=100000000 +local closestSpot +for _,spot in pairs(spots)do +local dist=UTILS.VecDist2D(pos,spot) +if dist=spacing then +pos=closestSpot +end +isValid=true +table.remove(spots,sid) +end +end +if not isValid and not isOnLand then +local h=UTILS.HdgTo(pos,gPos) +local retries=0 +while not isValid and retries<500 do +local dist=UTILS.VecDist2D(pos,gPos) +pos=UTILS.Vec2Translate(pos,step,h) +local skip=false +for _,unit2 in pairs(units)do +if unit~=unit2 then +local pos2={x=unit2.x,y=unit2.z or unit2.y} +local dist2=UTILS.VecDist2D(pos,pos2) +if dist2<12 then +isValid=false +skip=true +break +end +end +end +if not skip and dist>step and land.getSurfaceType(pos)==land.SurfaceType.LAND then +isValid=true +break +elseif dist<=step then +break +end +retries=retries+1 +end +end +if isValid then +unit.x=pos.x +if unit.z then +unit.z=pos.y +else +unit.y=pos.y +end +end +end +end +end +function UTILS.ValidateAndRepositionStatic(Country,Category,Type,Position,ShapeName,MaxRadius) +local coord=COORDINATE:NewFromVec2(Position) +local st=SPAWNSTATIC:NewFromType(Type,Category,Country) +if ShapeName then +st:InitShape(ShapeName) +end +local sName="s-"..timer.getTime().."-"..math.random(1,10000) +local tempStatic=st:SpawnFromCoordinate(coord,0,sName) +if tempStatic then +local sRadius=tempStatic:GetBoundingRadius(2)or 3 +tempStatic:Destroy() +sRadius=sRadius*0.5 +MaxRadius=MaxRadius or math.max(sRadius*10,100) +local positions=UTILS.GetSimpleZones(coord:GetVec3(),MaxRadius,sRadius,20) +if positions and#positions>0 then +local closestSpot +local closestDist=math.huge +for _,spot in pairs(positions)do +if land.getSurfaceType(spot)==land.SurfaceType.LAND then +local dist=UTILS.VecDist2D(Position,spot) +if dist=sRadius then +return closestSpot +else +return Position +end +end +end +end +return nil +end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -7517,7 +8510,7 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName=Event.TgtDCSUnitName Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) @@ -7540,10 +8533,12 @@ 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -9139,6 +10134,10 @@ self:_TriggerCheck(true) self:__TriggerRunCheck(self.Checktime) return self end +function ZONE_BASE:SetPartlyInside(state) +self.PartlyInside=state or not(state==false) +return self +end function ZONE_BASE:_TriggerCheck(fromstart) local objectset=self.objectset or{} if fromstart then @@ -9166,7 +10165,12 @@ end if not obj.TriggerInZone[self.ZoneName]then obj.TriggerInZone[self.ZoneName]=false end -local inzone=self:IsCoordinateInZone(obj:GetCoordinate()) +local inzone +if self.PartlyInside and obj.ClassName=="GROUP"then +inzone=obj:IsAnyInZone(self) +else +inzone=self:IsCoordinateInZone(obj:GetCoordinate()) +end if inzone and obj.TriggerInZone[self.ZoneName]then objcount=objcount+1 self.ObjectsInZone=true @@ -9348,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -9358,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9405,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9545,6 +10559,9 @@ function ZONE_RADIUS:GetRandomVec2(inner,outer,surfacetypes) local Vec2=self:GetVec2() local _inner=inner or 0 local _outer=outer or self:GetRadius() +math.random() +math.random() +math.random() if surfacetypes and type(surfacetypes)~="table"then surfacetypes={surfacetypes} end @@ -9720,6 +10737,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9781,6 +10808,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -10243,6 +11281,9 @@ local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) return InZone end function ZONE_POLYGON_BASE:GetRandomVec2() +math.random() +math.random() +math.random() local weights={} for _,triangle in pairs(self._Triangles)do weights[triangle]=triangle.SurfaceArea/self.SurfaceArea @@ -10406,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10428,7 +11468,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true self.ScanData.Units[ZoneObject]=ZoneObject end end -if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then +if ObjectCategory==Object.Category.SCENERY then local SceneryType=ZoneObject:getTypeName() local SceneryName=ZoneObject:getName() self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} @@ -11191,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -11870,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -12184,6 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -12225,6 +13283,20 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +end +return self +end function SET_BASE:_Find(ObjectName) local ObjectFound=self.Set[ObjectName] return ObjectFound @@ -12537,7 +13609,8 @@ return true end function SET_BASE:IsInSet(Object) local outcome=false -local name=Object:GetName() +if Object==nil then return false end +local name=(Object~=nil and Object.GetName)and Object:GetName()or"none" self:ForEach( function(object) if object:GetName()==name then @@ -12708,16 +13781,6 @@ self.Filter.Zones[zonename]=Zone end return self end -function SET_GROUP:FilterCoalitions(Coalitions,Clear) -if Clear or(not self.Filter.Coalitions)then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_GROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -13172,16 +14235,6 @@ function SET_UNIT:FindUnit(UnitName) local UnitFound=self.Set[UnitName] return UnitFound end -function SET_UNIT:FilterCoalitions(Coalitions) -self.Filter.Coalitions={} -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_UNIT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -13826,18 +14879,6 @@ 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={} @@ -14264,18 +15305,6 @@ self.Filter.Playernames[playername]=playername end return self end -function SET_CLIENT: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_CLIENT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14449,8 +15478,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -14665,18 +15693,6 @@ function SET_PLAYER:FindClient(PlayerName) local ClientFound=self.Set[PlayerName] return ClientFound end -function SET_PLAYER: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_PLAYER:FilterZones(Zones) if not self.Filter.Zones then self.Filter.Zones={} @@ -14930,18 +15946,6 @@ function SET_AIRBASE:GetRandomAirbase() local RandomAirbase=self:GetRandom() return RandomAirbase end -function SET_AIRBASE: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_AIRBASE:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -15102,18 +16106,6 @@ function SET_CARGO:FindCargo(CargoName) local CargoFound=self.Set[CargoName] return CargoFound end -function SET_CARGO: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_CARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -15475,6 +16467,14 @@ self:_TriggerCheck(true) self:__TriggerRunCheck(self.Checktime) return self end +function SET_ZONE:SetPartlyInside(state) +for _,Zone in pairs(self.Set)do +if Zone.SetPartlyInside then +Zone:SetPartlyInside(state) +end +end +return self +end function SET_ZONE:_TriggerCheck(fromstart) if fromstart then for _,_object in pairs(self.objectset)do @@ -15501,7 +16501,12 @@ end if not obj.TriggerInZone[_zone.ZoneName]then obj.TriggerInZone[_zone.ZoneName]=false end -local inzone=_zone:IsCoordinateInZone(obj:GetCoordinate()) +local inzone +if _zone.PartlyInside and obj.ClassName=="GROUP"then +inzone=obj:IsAnyInZone(_zone) +else +inzone=_zone:IsCoordinateInZone(obj:GetCoordinate()) +end if inzone and not obj.TriggerInZone[_zone.ZoneName]then self:__EnteredZone(0.5,obj,_zone) obj.TriggerInZone[_zone.ZoneName]=true @@ -15720,16 +16725,6 @@ self.Filter.Prefixes[Prefix]=Prefix end return self end -function SET_OPSZONE:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_OPSZONE:FilterOnce() for ObjectName,Object in pairs(self.Database)do self:Remove(ObjectName,true) @@ -15946,6 +16941,18 @@ self:Remove(RemoveGroupName) end return self end +function SET_OPSGROUP:CountAlive() +local CountG=0 +local CountU=0 +local Set=self:GetSet() +for GroupID,GroupData in pairs(Set)do +if GroupData and GroupData:IsAlive()then +CountG=CountG+1 +CountU=CountU+GroupData:GetGroup():CountAliveUnits() +end +end +return CountG,CountU +end function SET_OPSGROUP:FindGroup(GroupName) local GroupFound=self.Set[GroupName] return GroupFound @@ -15962,18 +16969,6 @@ function SET_OPSGROUP:FindNavyGroup(GroupName) local GroupFound=self:FindGroup(GroupName) return GroupFound end -function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear) -if Clear or 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_OPSGROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -16455,18 +17450,6 @@ DCargoInclude=DCargoInclude and MClientFunc end return DCargoInclude end -function SET_DYNAMICCARGO: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_DYNAMICCARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -16970,6 +17953,9 @@ return coord end function COORDINATE:GetRandomVec2InRadius(OuterRadius,InnerRadius) self:F2({OuterRadius,InnerRadius}) +math.random() +math.random() +math.random() local Theta=2*math.pi*math.random() local Radials=math.random()+math.random() if Radials>1 then @@ -17389,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -17597,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -18476,41 +19480,6 @@ return nil 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 -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 -end -do VELOCITY={ ClassName="VELOCITY", } @@ -19649,6 +20618,12 @@ self.SpawnUnitsWithAbsolutePositions=true self.UnitsAbsolutePositions=Positions return self end +function SPAWN:InitValidateAndRepositionGroundUnits(OnOff,MaxRadius,Spacing) +self.SpawnValidateAndRepositionGroundUnits=OnOff +self.SpawnValidateAndRepositionGroundUnitsRadius=MaxRadius +self.SpawnValidateAndRepositionGroundUnitsSpacing=Spacing +return self +end function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable) local temptable={} for _,_temp in pairs(SpawnTemplatePrefixTable)do @@ -19884,6 +20859,9 @@ SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-Curren end end if self.SpawnRandomizeUnits then +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -20018,6 +20996,11 @@ end if self.SpawnHiddenOnMap then SpawnTemplate.hidden=self.SpawnHiddenOnMap end +if self.SpawnValidateAndRepositionGroundUnits then +local units=SpawnTemplate.units +local gPos={x=SpawnTemplate.x,y=SpawnTemplate.y} +UTILS.ValidateAndRepositionGroundUnits(units,gPos,self.SpawnValidateAndRepositionGroundUnitsRadius,self.SpawnValidateAndRepositionGroundUnitsSpacing) +end SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID @@ -20804,6 +21787,11 @@ min=9 max=18 ctable=CALLSIGN.F15E end +if SpawnTemplate.units[1].type=="A6E"then +min=4 +max=18 +ctable=CALLSIGN.Intruder +end local callsignnr=math.random(min,max) local callsignname="Enfield" for name,value in pairs(ctable)do @@ -21319,6 +22307,18 @@ function SPAWNSTATIC:InitCargo(IsCargo) self.InitStaticCargo=IsCargo return self end +function SPAWNSTATIC:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWNSTATIC:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWNSTATIC:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead return self @@ -21338,6 +22338,11 @@ self.InitOffsetY=OffsetY or 0 self.InitOffsetAngle=OffsetAngle or 0 return self end +function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff,MaxRadius) +self.ValidateAndRepositionStatic=OnOff +self.ValidateAndRepositionStaticMaxRadius=MaxRadius +return self +end function SPAWNSTATIC:OnSpawnStatic(SpawnCallBackFunction,...) self:F("OnSpawnStatic") self.SpawnFunctionHook=SpawnCallBackFunction @@ -21407,6 +22412,15 @@ end if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end +if self.SpawnHiddenOnPlanner then +Template.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +Template.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +Template.hidden=self.SpawnHiddenOnMap +end if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -21424,6 +22438,13 @@ end self.SpawnIndex=self.SpawnIndex+1 Template.name=self.InitStaticName or string.format("%s#%05d",self.SpawnTemplatePrefix,self.SpawnIndex) local Static=nil +if self.ValidateAndRepositionStatic then +local validPos=UTILS.ValidateAndRepositionStatic(CountryID,Template.category,Template.type,Template,Template.shape_name,self.ValidateAndRepositionStaticMaxRadius) +if validPos then +Template.x=validPos.x +Template.y=validPos.y +end +end if self.InitFarp then local TemplateGroup={} TemplateGroup.units={} @@ -21465,7 +22486,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21473,8 +22494,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -21722,20 +22741,17 @@ MARKEROPS_BASE={ ClassName="MARKEROPS", Tag="mytag", Keywords={}, -version="0.1.4", +version="0.1.5", debug=false, Casesensitive=true, } -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) 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","*") @@ -21798,9 +22814,9 @@ end end function MARKEROPS_BASE:_MatchTag(Eventtext) local matches=false -if not self.Casesensitive then +if self.Casesensitive==false then local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then +if string.find(string.lower(Eventtext),type,1,true)then matches=true end else @@ -21836,6 +22852,14 @@ self:UnHandleEvent(EVENTS.MarkAdded) self:UnHandleEvent(EVENTS.MarkChange) self:UnHandleEvent(EVENTS.MarkRemoved) end +function MARKEROPS_BASE:SwitchCaseSensitiveOff() +self.Casesensitive=false +return self +end +function MARKEROPS_BASE:SwitchCaseSensitiveOn() +self.Casesensitive=true +return self +end TEXTANDSOUND={ ClassName="TEXTANDSOUND", version="0.0.1", @@ -21940,7 +22964,7 @@ ClassName="PATHLINE", lid=nil, points={}, } -PATHLINE.version="0.1.1" +PATHLINE.version="0.2.0" function PATHLINE:New(Name) local self=BASE:Inherit(self,BASE:New()) self.name=Name or"Unknown Path" @@ -22039,12 +23063,26 @@ return point.vec2 end return nil end +function PATHLINE:GetLength(Project2D) +local l=0 +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +if Project2D then +l=l+UTILS.VecDist2D(p1.vec2,p2.vec2) +else +l=l+UTILS.VecDist3D(p1.vec3,p2.vec3) +end +end +return l +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) +UTILS.RemoveMark(point.markerID) end else if point.markerID then @@ -22055,6 +23093,31 @@ local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m trigger.action.markToAll(point.markerID,text,point.vec3,"") end end +return self +end +function PATHLINE:DrawLine(Recipient,Color,LineType) +Recipient=Recipient or-1 +Color=Color or{1,0,0,1.0} +LineType=LineType or 1 +local ReadOnly=false +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +p1.lineID=UTILS.GetMarkID() +trigger.action.lineToAll(Recipient,p1.lineID,p1.vec3,p2.vec3,Color,LineType,ReadOnly,"") +end +return self +end +function PATHLINE:UnDrawLine(Delay) +local np=#self.points +for _,_point in pairs(self.points)do +local p=_point +if p.lineID then +UTILS.RemoveMark(p.lineID,Delay) +end +end +return self end function PATHLINE:_CreatePoint(Vec) local point={} @@ -22238,7 +23301,7 @@ end CLIENTMENUMANAGER={ ClassName="CLIENTMENUMANAGER", lid="", -version="0.1.6", +version="0.1.7", name=nil, clientset=nil, menutree={}, @@ -22512,6 +23575,10 @@ self.menutree=nil self.menutree={} return self end +function CLIENTMENUMANAGER:DeleteEntry(Entry,Client) +self:T(self.lid.."DeleteEntry") +return self:DeleteF10Entry(Entry,Client) +end function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client) self:T(self.lid.."DeleteF10Entry") local Set=self.clientset.Set @@ -22589,6 +23656,529 @@ end end return self end +VECTOR={ +ClassName="VECTOR", +verbose=0, +} +VECTOR.version="0.1.0" +_VECTORID=0 +VECTOR.__index=VECTOR +function VECTOR:New(x,y,z) +if z==nil then +self=setmetatable({x=x or 0,y=0,z=y or 0},VECTOR) +else +self=setmetatable({x=x or 0,y=y or 0,z=z or 0},VECTOR) +end +_VECTORID=_VECTORID+1 +self.uid=_VECTORID +return self +end +function VECTOR:NewFromVec(Vec) +local vector=VECTOR:New(Vec.x,Vec.y,Vec.z) +return vector +end +function VECTOR:NewFromPolar(r,phi) +local Phi=math.rad(phi) +local x=r*math.cos(phi) +local y=r*math.sin(phi) +local v=VECTOR:New(x,y) +return self +end +function VECTOR:NewFromSpherical(r,theta,phi) +local sinPhi=math.sin(math.rad(phi)) +local cosPhi=math.cos(math.rad(phi)) +local sinTheta=math.sin(math.rad(theta)) +local cosTheta=math.cos(math.rad(theta)) +local x=r*sinTheta*cosPhi +local y=r*sinTheta*sinPhi +local z=r*cosTheta +local v=VECTOR:New(x,y,z) +return self +end +function VECTOR:NewDirectionalVector(a,b) +local x=b.x-a.x +local y +local z +if a.z and b.z then +y=b.y-a.y +z=b.z-a.z +elseif b.z then +y=b.y-0 +z=b.z-a.y +elseif a.z then +y=0-a.y +z=b.y-a.z +else +y=b.y-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:NewFromLLDD(Latitude,Longitude,Altitude) +local vec3=coord.LLtoLO(Latitude,Longitude) +self=VECTOR:NewFromVec(vec3) +if Altitude then +self.y=Altitude +end +return self +end +function VECTOR:NewFromLLDMS(Latitude,Longitude,Altitude) +local lat=UTILS.LLDMSstringToDD(Latitude) +local lon=UTILS.LLDMSstringToDD(Longitude) +self=VECTOR:NewFromLLDD(lat,lon,Altitude) +return self +end +function VECTOR:GetVec2() +local vec={x=self.x,y=self.z} +return vec +end +function VECTOR:GetVec3(OnSurface) +local x=self.x +local y=OnSurface and land.getHeight({x=self.x,y=self.z})or self.y +local z=self.z +local vec={x=x,y=y,z=z} +return vec +end +function VECTOR:GetCoordinate(OnSurface) +local vec3=self:GetVec3(OnSurface) +local coordinate=COORDINATE:NewFromVec3(vec3) +return coordinate +end +function VECTOR:GetDistance(Vector,Only2D) +local dx=self.x-Vector.x +local dy=0 +local dz=0 +if Vector.z then +if not Only2D then +dy=self.y-Vector.y +end +dz=self.z-Vector.z +else +dy=0 +dz=self.z-Vector.y +end +local dist=math.sqrt(dx*dx+dy*dy+dz*dz) +return dist +end +function VECTOR:GetDirectionalVectorTo(a) +local x=a.x-self.x +local y=0 +local z=nil +if a.z then +y=a.y-self.y +z=a.z-self.z +else +y=a.y-self.z +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetDirectionalVectorFrom(a) +local x=self.x-a.x +local y +local z +if a.z then +y=self.y-a.y +z=self.z-a.z +else +y=self.z-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetLength() +local l=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) +return l +end +function VECTOR:GetHeading(To360) +local heading=math.atan2(self.z,self.x) +heading=math.deg(heading) +if To360==nil or To360==true then +heading=UTILS.AdjustHeading360(heading) +else +if heading==360.0 then +heading=0.0 +end +end +return heading +end +function VECTOR:GetHeadingTo(Vector) +local a=self:GetDirectionalVectorTo(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetHeadingFrom(Vector) +local a=self:GetDirectionalVectorFrom(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetLatitudeLongitude() +local vec3=self:GetVec3() +local latitude,longitude,altitude=coord.LOtoLL(vec3) +return latitude,longitude +end +function VECTOR:GetMGRS() +local lat,long=self:GetLatitudeLongitude() +local mgrs=coord.LLtoMGRS(lat,long) +return mgrs +end +function VECTOR:GetHeadingDelta(Vector) +local h1=self:GetHeading(false) +local h2=Vector:GetHeading(false) +local delta=h2-h1 +return delta +end +function VECTOR:GetIntermediateVector(Vector,Fraction) +local f=Fraction or 0.5 +local vec=self:GetDirectionalVectorTo(Vector) +local length=vec:GetLength() +vec:SetLength(f*length) +vec=self+vec +return vec +end +function VECTOR:SetLength(Length) +self:Normalize() +local v=self*Length +self:Replace(v) +return self +end +function VECTOR:SetX(x) +self.x=x or 0 +return self +end +function VECTOR:SetY(y) +if y==nil then +y=self:GetSurfaceHeight() +end +self.y=y +return self +end +function VECTOR:SetZ(z) +self.z=z or 0 +return self +end +function VECTOR:AddVec(Vec) +self.x=self.x+Vec.x +if Vec.z then +self.y=self.y+Vec.y +self.z=self.z+Vec.z +else +self.z=self.z+Vec.y +end +return self +end +function VECTOR:SubVec(Vec) +self.x=self.x-Vec.x +if Vec.z then +self.y=self.y-Vec.y +self.z=self.z-Vec.z +else +self.z=self.z-Vec.y +end +return self +end +function VECTOR:Dot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Rot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Copy() +local c=VECTOR:New(self.x,self.y,self.z) +return c +end +function VECTOR:Replace(Vector,Project2D) +self.x=Vector.x +if Vector.z then +self.y=Vector.y +self.z=Vector.z +else +if Project2D then +self.y=0 +end +self.z=Vector.y +end +return self +end +function VECTOR:Normalize() +local l=self:GetLength() +if l~=0 then +self:Replace(self/l) +end +return self +end +function VECTOR:Translate(Distance,Heading,Copy) +Distance=Distance or 1000 +local alpha=math.rad(Heading or 0) +local vector=Copy and self:Copy()or self +vector.x=Distance*math.cos(alpha)+vector.x +vector.z=Distance*math.sin(alpha)+vector.z +return vector +end +function VECTOR:Rotate2D(Angle,Copy) +local phi=-math.rad(Angle or 0) +local sinPhi=math.sin(phi) +local cosPhi=math.cos(phi) +local X=self.z +local Y=self.x +local z=X*cosPhi-Y*sinPhi +local x=X*sinPhi+Y*cosPhi +if Copy then +local vector=VECTOR:New(x,self.y,z) +return vector +else +self:SetX(x) +self:SetZ(z) +return self +end +end +function VECTOR: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) +local text="MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +return text +end +function VECTOR:GetSurfaceType() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +return s +end +function VECTOR:GetSurfaceTypeName() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +for name,id in land.SurfaceType()do +if id==s then +return name +end +end +return"unknown" +end +function VECTOR:IsVisible(Vec) +local vec1=self:GetVec3() +local vec2={x=Vec.x,Vec.y,Vec.z} +local los=land.isVisible(vec1,vec2) +return los +end +function VECTOR:GetClosestRoad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('roads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetClosestRailroad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('railroads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetPathOnRoad(Vec) +local vec1=self:GetVec2() +local vec2=Vec:GetVec2() +local vec2points=land.findPathOnRoads("roads",vec1.x,vec1.y,vec2.x,vec2.y) +local path=nil +if vec2points then +path=PATHLINE:NewFromVec2Array("Road",vec2points) +end +return path +end +function VECTOR:GetProfile(Vec3) +local vec3=self:GetVec3() +local vec3s=land.profile(vec3,Vec3) +local profile=nil +if vec3s then +profile=PATHLINE:NewFromVec3Array("Profile",vec3s) +end +return profile +end +function VECTOR:GetInterceptPoint(DirectionVector,Distance) +local vec3=self:GetVec3() +local ip3=land.getIP(vec3,DirectionVector,Distance or 1000) +local ipvector=nil +if ip3 then +ipvector=VECTOR:New(ip3.x,ip3.y,ip3.z) +end +return ipvector +end +function VECTOR:GetSurfaceHeight() +local vec2=self:GetVec2() +local h=land.getHeight(vec2) +return h +end +function VECTOR:GetSurfaceHeightAndDepth() +local vec2=self:GetVec2() +local h,d=land.getSurfaceHeightWithSeabed(vec2) +return h,d +end +function VECTOR:GetWindVector(WithTurbulence) +local vec3=self:GetVec3() +local wind=nil +if WithTurbulence then +wind=atmosphere.getWindWithTurbulence(vec3) +else +wind=atmosphere.getWind(vec3) +end +local vector=VECTOR:New(wind) +return vector +end +function VECTOR:GetTemperaturAndPressure() +local vec3=self:GetVec3() +local t,p=atmosphere.getTemperatureAndPressure(vec3) +return t,p +end +function VECTOR:Smoke(Color,Duration) +local vec3=self:GetVec3() +Color=Color or 0 +local name=string.format("Vector-Smoke-%d",self.uid) +trigger.action.smoke(vec3,Color,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -22688,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -22835,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -22872,6 +24483,16 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetPointVec2() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -22903,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -22910,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -23070,12 +24693,18 @@ end BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -23195,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -23690,6 +25329,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -25952,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -27175,6 +28855,7 @@ GROUND_EWR="Ground_EWR", GROUND_AAA="Ground_AAA", GROUND_SAM="Ground_SAM", GROUND_SHORAD="Ground_SHORAD", +GROUND_BALLISTICMISSILE="Ground_BallisticMissile", GROUND_OTHER="Ground_OtherGround", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_WARSHIP="Naval_WarShip", @@ -27607,33 +29288,45 @@ end function GROUP:GetTypeName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +end return nil end function GROUP:GetNatoReportingName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +end return"Bogey" end function GROUP:GetPlayerName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +end return nil end function GROUP:GetCallsign() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -27653,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -28225,6 +29927,9 @@ if self.InitRespawnModu then Template.modulation=self.InitRespawnModu end self:Destroy(false) +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) self:ResetEvents() return self @@ -28301,7 +30006,7 @@ function GROUP:GetTaskMission() return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) end function GROUP:GetTaskRoute() -if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) else return{} @@ -28315,7 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -28337,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -28429,6 +30134,8 @@ 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 ballisticMissile=artillery and self:HasAttribute("SS_missile") +local shorad=self:HasAttribute("SR SAM") local ewr=self:HasAttribute("EWR") local ifv=self:HasAttribute("IFV") local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker") @@ -28459,6 +30166,8 @@ elseif sam then attribute=GROUP.Attribute.GROUND_SAM elseif aaa then attribute=GROUP.Attribute.GROUND_AAA +elseif artillery and ballisticMissile then +attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE elseif artillery then attribute=GROUP.Attribute.GROUND_ARTILLERY elseif tank then @@ -28576,6 +30285,10 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +return self +end function GROUP:SetCommandInvisible(switch) return self:CommandSetInvisible(switch) end @@ -28738,6 +30451,44 @@ end end return isAAA end +function GROUP:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function GROUP:GetBoundingBox() +local bbox={min={x=math.huge,y=math.huge,z=math.huge}, +max={x=-math.huge,y=-math.huge,z=-math.huge} +} +local Units=self:GetUnits()or{} +if#Units==0 then +return nil +end +for _,unit in pairs(Units)do +if unit and unit:IsAlive()then +local ubox=unit:GetBoundingBox() +if ubox then +if ubox.min.xbbox.max.x then +bbox.max.x=ubox.max.x +end +if ubox.max.y>bbox.max.y then +bbox.max.y=ubox.max.y +end +if ubox.max.z>bbox.max.z then +bbox.max.z=ubox.max.z +end +end +end +end +return bbox +end UNIT={ ClassName="UNIT", UnitName=nil, @@ -28875,6 +30626,9 @@ i=i+1 end end SpawnGroupTemplate.groupId=nil +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units) +end _DATABASE:Spawn(SpawnGroupTemplate) end function UNIT:IsActive() @@ -29655,6 +31409,26 @@ end function UNIT:SetLife(Percent) net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)",self:GetID(),Percent)) end +function UNIT:SetCarrierIlluminationMode(Mode) +UTILS.SetCarrierIlluminationMode(self:GetID(),Mode) +end +function UNIT:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -29962,6 +31736,7 @@ self.Life0=Life0 else self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -29974,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -30039,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30049,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30058,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30147,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -30155,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -30177,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -30187,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -30205,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -30212,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -30225,18 +32028,15 @@ AIRBASE.Normandy={ ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -["Hawkinge"]="Hawkinge", } 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -30279,13 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -30301,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -30313,16 +32115,17 @@ AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", -["Marka"]="Marka", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -30339,17 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Ben_Gurion"]="Ben Gurion", -["Hatzor"]="Hatzor", -["Palmachim"]="Palmachim", -["Tel_Nof"]="Tel Nof", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -30357,8 +32157,7 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } -AIRBASE.MarianaIslandsWWII= -{ +AIRBASE.MarianaIslandsWWII={ ["Agana"]="Agana", ["Airfield_3"]="Airfield 3", ["Charon_Kanoa"]="Charon Kanoa", @@ -30419,7 +32218,9 @@ AIRBASE.Sinai={ ["Borg_El_Arab_International_Airport"]="Borg El Arab International Airport", ["Cairo_International_Airport"]="Cairo International Airport", ["Cairo_West"]="Cairo West", +["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["El_Minya"]="El Minya", @@ -30431,56 +32232,68 @@ AIRBASE.Sinai={ ["Inshas_Airbase"]="Inshas Airbase", ["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", ["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", +["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", ["Ovda"]="Ovda", ["Palmachim"]="Palmachim", ["Quwaysina"]="Quwaysina", +["Rafic_Hariri_Intl"]="Rafic Hariri Intl", +["Ramat_David"]="Ramat David", ["Ramon_Airbase"]="Ramon Airbase", ["Ramon_International_Airport"]="Ramon International Airport", ["Sde_Dov"]="Sde Dov", ["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", +["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", ["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", ["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", ["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", ["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", ["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", ["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", ["Severomorsk_1"]="Severomorsk-1", ["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", ["Vidsel"]="Vidsel", ["Vuojarvi"]="Vuojarvi", -["Andoya"]="Andoya", -["Alakurtti"]="Alakurtti", -["Kittila"]="Kittila", -["Bardufoss"]="Bardufoss", -["Alta"]="Alta", -["Sodankyla"]="Sodankyla", -["Enontekio"]="Enontekio", -["Evenes"]="Evenes", -["Hosio"]="Hosio", -["Kilpyavr"]="Kilpyavr", -["Afrikanda"]="Afrikanda", -["Kalevala"]="Kalevala", -["Koshka_Yavr"]="Koshka Yavr", -["Poduzhemye"]="Poduzhemye", -["Luostari_Pechenga"]="Luostari Pechenga", } AIRBASE.Afghanistan={ ["Bagram"]="Bagram", @@ -30490,6 +32303,10 @@ AIRBASE.Afghanistan={ ["Camp_Bastion_Heliport"]="Camp Bastion Heliport", ["Chaghcharan"]="Chaghcharan", ["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", ["Farah"]="Farah", ["Gardez"]="Gardez", ["Ghazni_Heliport"]="Ghazni Heliport", @@ -30499,7 +32316,6 @@ AIRBASE.Afghanistan={ ["Kandahar"]="Kandahar", ["Kandahar_Heliport"]="Kandahar Heliport", ["Khost"]="Khost", -["Khost_Heliport"]="Khost Heliport", ["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", ["Nimroz"]="Nimroz", ["Qala_i_Naw"]="Qala i Naw", @@ -30510,21 +32326,28 @@ AIRBASE.Afghanistan={ ["Urgoon_Heliport"]="Urgoon Heliport", } AIRBASE.Iraq={ -["Baghdad_International_Airport"]="Baghdad International Airport", -["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", -["Al_Sahra_Airport"]="Al-Sahra Airport", -["Erbil_International_Airport"]="Erbil International Airport", -["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", ["Al_Salam_Airbase"]="Al-Salam Airbase", -["Balad_Airbase"]="Balad Airbase", -["Kirkuk_International_Airport"]="Kirkuk International Airport", -["Bashur_Airport"]="Bashur Airport", +["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Taquddum_Airport"]="Al-Taquddum Airport", -["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", ["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", } AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", ["Airracing_Frankfurt"]="Airracing Frankfurt", ["Airracing_Koblenz"]="Airracing Koblenz", ["Airracing_Luebeck"]="Airracing Lubeck", @@ -30535,16 +32358,23 @@ AIRBASE.GermanyCW={ ["Bienenfarm"]="Bienenfarm", ["Bindersleben"]="Bindersleben", ["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", ["Braunschweig"]="Braunschweig", ["Bremen"]="Bremen", ["Briest"]="Briest", ["Buechel"]="Buchel", ["Bueckeburg"]="Buckeburg", ["Celle"]="Celle", +["Chojna"]="Chojna", ["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", ["Damgarten"]="Damgarten", ["Dedelow"]="Dedelow", ["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", ["Fassberg"]="Fassberg", ["Finow"]="Finow", ["Frankfurt"]="Frankfurt", @@ -30555,7 +32385,7 @@ AIRBASE.GermanyCW={ ["Gatow"]="Gatow", ["Gelnhausen"]="Gelnhausen", ["Giebelstadt"]="Giebelstadt", -["Glindbruchkippe"]="Glindbruchkippe ", +["Glindbruchkippe"]="Glindbruchkippe", ["Gross_Mohrdorf"]="Gross Mohrdorf", ["Grosse_Wiese"]="Grosse Wiese", ["Guetersloh"]="Gutersloh", @@ -30673,43 +32503,60 @@ AIRBASE.GermanyCW={ ["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", ["Hannover"]="Hannover", ["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", ["Herrenteich"]="Herrenteich", ["Hildesheim"]="Hildesheim", ["Hockenheim"]="Hockenheim", ["Holzdorf"]="Holzdorf", ["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", ["Koethen"]="Kothen", ["Laage"]="Laage", +["Landstuhl"]="Landstuhl", ["Langenselbold"]="Langenselbold", ["Laerz"]="Larz", -["Leipzig_Halle"]="Leipzig Halle", ["Leipzig_Mockau"]="Leipzig Mockau", ["Luebeck"]="Lubeck", ["Lueneburg"]="Luneburg", ["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", ["Mendig"]="Mendig", ["Merseburg"]="Merseburg", ["Neubrandenburg"]="Neubrandenburg", ["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", ["Northeim"]="Northeim", +["Noervenich"]="Norvenich", ["Ober_Moerlen"]="Ober-Morlen", ["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", ["Parchim"]="Parchim", ["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", ["Pferdsfeld"]="Pferdsfeld", ["Pinnow"]="Pinnow", ["Pottschutthoehe"]="Pottschutthohe", ["Ramstein"]="Ramstein", +["Revinge"]="Revinge", ["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", ["Schoenefeld"]="Schonefeld", ["Schweinfurt"]="Schweinfurt", ["Sembach"]="Sembach", +["Sittensen"]="Sittensen", ["Spangdahlem"]="Spangdahlem", ["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", ["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", ["Tegel"]="Tegel", ["Tempelhof"]="Tempelhof", ["Templin"]="Templin", +["Thurland"]="Thurland", ["Tutow"]="Tutow", ["Uelzen"]="Uelzen", ["Uetersen"]="Uetersen", @@ -30725,6 +32572,7 @@ AIRBASE.GermanyCW={ ["Worms"]="Worms", ["Wunstorf"]="Wunstorf", ["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", ["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ @@ -30792,9 +32640,19 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -31041,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -31732,16 +33617,55 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end if self.SceneryObject and self.SceneryObject.getLife then -self.Life0=self.SceneryObject:getLife()or 0 +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) @@ -31760,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 +local life=1 if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -31800,14 +33746,16 @@ end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -31826,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -31850,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -31879,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -32732,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -33204,7 +35167,7 @@ self:SetLiquid(lqno,lqam) end end else -self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end if self:IsLimitedAircraft()then @@ -33222,7 +35185,7 @@ self:SetAmount(acname,acnumber) end end else -self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end if self:IsLimitedWeapons()then @@ -33251,7 +35214,7 @@ end end end else -self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end return self @@ -33322,6 +35285,9 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33330,8 +35296,26 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.1.0" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -33554,13 +35538,29 @@ end end self.LastPosition=pos else -if self.timer and self.timer:IsRunning()then self.timer:Stop()end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") self.CargoState=DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end return self end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end function DYNAMICCARGO._FilterHeloTypes(client) if not client then return false end local typename=client:GetTypeName() @@ -34896,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -34909,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -34931,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -34943,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -34970,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -35002,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -35009,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -35148,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -35258,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -35283,6 +37411,8 @@ local TargetType=nil local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil +local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -35296,7 +37426,6 @@ 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 @@ -35308,10 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then +TargetCategory=Unit.Category.STRUCTURE +TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") +end 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) @@ -35320,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -35331,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -35377,17 +37512,22 @@ MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit en MESSAGE.Type.Update) :ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) :ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) -else +elseif TargetIsScenery~=true then 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()) +elseif TargetIsScenery==true then +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.".." 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:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit nothing special.", MESSAGE.Type.Update) :ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll()) :ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) @@ -35398,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -35416,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -35505,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -35604,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -35621,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -35859,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -35882,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -35890,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -35958,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -35979,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -43535,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -43546,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -43572,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -43602,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -43625,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -43640,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -43649,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -44261,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -44293,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -44312,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -46506,7 +48690,7 @@ maxrange=7000, reloadtime=nil, }, } -ARTY.version="1.3.3" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -47186,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -50715,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -51012,7 +53220,6 @@ 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) @@ -51116,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -51210,6 +53418,9 @@ self:I(self.lid..text) self:T({DCSdesc=asset.DCSdesc}) self:T3({Template=asset.template}) end +function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +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 @@ -51315,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -52025,6 +54235,9 @@ template.route.points[1].y=coord.z template.x=coord.x template.y=coord.z template.alt=coord.y +if self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(template.units) +end local group=_DATABASE:Spawn(template) return group end @@ -52121,7 +54334,6 @@ 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 @@ -54611,7 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", -version="0.9.32", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -54662,6 +56874,9 @@ SmokeDecoyColor=SMOKECOLOR.White, checkcounter=1, DLinkCacheTime=120, logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -54702,14 +56917,23 @@ MANTIS.SamData={ ["Chaparral"]={Range=8,Blindspot=0,Height=3,Type="Short",Radar="Chaparral"}, ["Linebacker"]={Range=4,Blindspot=0,Height=3,Type="Point",Radar="Linebacker",Point="true"}, ["Silkworm"]={Range=90,Blindspot=1,Height=0.2,Type="Long",Radar="Silkworm"}, -["HEMTT_C-RAM_Phalanx"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="HEMTT_C-RAM_Phalanx",Point="true"}, +["C-RAM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="HEMTT_C-RAM_Phalanx",Point="true"}, ["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"}, ["SA-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, ["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"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, +["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, +["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["IRIS-T SLM"]={Range=40,Blindspot=0.5,Height=20,Type="Medium",Radar="CH_IRIST_SLM"}, } MANTIS.SamDataHDS={ ["SA-2 HDS"]={Range=56,Blindspot=7,Height=30,Type="Medium",Radar="V759"}, @@ -54740,15 +56964,15 @@ MANTIS.SamDataSMA={ } MANTIS.SamDataCH={ ["2S38 CHM"]={Range=6,Blindspot=0.1,Height=4.5,Type="Short",Radar="2S38"}, -["PantsirS1 CHM"]={Range=20,Blindspot=1.2,Height=15,Type="Short",Radar="PantsirS1"}, +["PantsirS1 CHM"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, ["PantsirS2 CHM"]={Range=30,Blindspot=1.2,Height=18,Type="Medium",Radar="PantsirS2"}, ["PGL-625 CHM"]={Range=10,Blindspot=1,Height=5,Type="Short",Radar="PGL_625"}, ["HQ-17A CHM"]={Range=15,Blindspot=1.5,Height=10,Type="Short",Radar="HQ17A"}, ["M903PAC2 CHM"]={Range=120,Blindspot=3,Height=24.5,Type="Long",Radar="MIM104_M903_PAC2"}, ["M903PAC3 CHM"]={Range=160,Blindspot=1,Height=40,Type="Long",Radar="MIM104_M903_PAC3"}, -["TorM2 CHM"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2"}, -["TorM2K CHM"]={Range=12,Blindspot=1,Height=10,Type="Short",Radar="TorM2K"}, -["TorM2M CHM"]={Range=16,Blindspot=1,Height=10,Type="Short",Radar="TorM2M"}, +["TorM2 CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, +["TorM2K CHM"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2K",Point="true"}, +["TorM2M CHM"]={Range=16,Blindspot=1,Height=10,Type="Point",Radar="TorM2M",Point="true"}, ["NASAMS3-AMRAAMER CHM"]={Range=50,Blindspot=2,Height=35.7,Type="Medium",Radar="CH_NASAMS3_LN_AMRAAM_ER"}, ["NASAMS3-AIM9X2 CHM"]={Range=20,Blindspot=0.2,Height=18,Type="Short",Radar="CH_NASAMS3_LN_AIM9X2"}, ["C-RAM CHM"]={Range=2,Blindspot=0,Height=2,Type="Point",Radar="CH_Centurion_C_RAM",Point="true"}, @@ -54784,13 +57008,14 @@ MANTIS.SamDataCH={ ["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} @@ -54902,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -54934,11 +57264,78 @@ self:T(self.lid.."AddZones") self.AcceptZones=AcceptZones or{} self.RejectZones=RejectZones or{} self.ConflictZones=ConflictZones or{} -if#self.AcceptZones>0 or#self.RejectZones>0 or#self.ConflictZones>0 then +self.AcceptZonesNo=UTILS.TableLength(self.AcceptZones) +self.RejectZonesNo=UTILS.TableLength(self.RejectZones) +self.ConflictZonesNo=UTILS.TableLength(self.ConflictZones) +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 or self.RejectZonesNo>0 or self.ConflictZonesNo>0 then self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -55195,7 +57592,8 @@ end function MANTIS:_CheckCoordinateInZones(coord) self:T(self.lid.."_CheckCoordinateInZones") local inzone=false -if#self.AcceptZones>0 then +self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) +if self.AcceptZonesNo>0 then for _,_zone in pairs(self.AcceptZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -55205,7 +57603,7 @@ break end end end -if#self.RejectZones>0 and inzone then +if self.RejectZonesNo>0 then for _,_zone in pairs(self.RejectZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -55215,7 +57613,7 @@ break end end end -if#self.ConflictZones>0 and not inzone then +if self.ConflictZonesNo>0 then for _,_zone in pairs(self.ConflictZones)do local zone=_zone if zone:IsCoordinateInZone(coord)then @@ -55227,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -55236,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -55249,7 +57661,7 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end if self.checkforfriendlies==true and self.friendlyset==nil then self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() @@ -55261,6 +57673,7 @@ if not targetdistance then targetdistance=samcoordinate:Get2DDistance(coord) end local zonecheck=true +self:T("self.usezones = "..tostring(self.usezones)) if self.usezones then zonecheck=self:_CheckCoordinateInZones(coord) end @@ -55304,9 +57717,27 @@ self:T(self.lid.."Starting Intel Detection") local groupset=self.EWR_Group local samset=self.SAM_Group self.intelset={} -local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne") +local IntelOne=INTEL:New(groupset,self.coalition,self.name.." IntelOne") +IntelOne.DetectAccoustic=self.DetectAccoustic +IntelOne.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelOne.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelOne:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelOne:Start() -local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo") +local IntelTwo=INTEL:New(samset,self.coalition,self.name.." IntelTwo") +IntelTwo.DetectAccoustic=self.DetectAccoustic +IntelTwo.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelTwo.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelTwo:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelTwo:Start() local CacheTime=self.DLinkCacheTime or 120 local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) @@ -55562,6 +57993,22 @@ self:T(self.lid.."RemoveShorad") self.ShoradLink=false return self end +function MANTIS:_SmokeUnits(Group) +self:T("Smoking") +local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME")or 0 +local TNow=timer.getTime() +if TNow-LastSmoketime>290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 @@ -55575,7 +58022,7 @@ local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 -local shortsam=(_data[6]==MANTIS.SamType.SHORT)and true or false +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false if not shortsam then shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false end @@ -55600,17 +58047,10 @@ switch=true end if self.SamStateTracker[name]~="RED"and switch then self:__RedState(1,samgroup) -self.SamStateTracker[name]="RED" end -if shortsam==true and self.SmokeDecoy==true then +if shortsam==true and self.SmokeDecoy==true and 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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -60460,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -60481,8 +63213,12 @@ WASHINGTON="CVN_73", TRUMAN="CVN_75", STENNIS="Stennis", FORRESTAL="Forrestal", +ENTERPRISE66="USS Enterprise 1966", +ENTERPRISEMODERN="cvn-65", VINSON="VINSON", ESSEX="Essex", +BONHOMMERICHARD="USS Bon Homme Richard", +ESSEXSCB125="essex_scb125", HERMES="HERMES81", INVINCIBLE="hms_invincible", TARAWA="LHA_Tarawa", @@ -60540,7 +63276,7 @@ HARD="TOPGUN Graduate", } AIRBOSS.MenuF10={} AIRBOSS.MenuF10Root=nil -AIRBOSS.version="1.4.1" +AIRBOSS.version="1.4.2" function AIRBOSS:New(carriername,alias) local self=BASE:Inherit(self,FSM:New()) self:F2({carriername=carriername,alias=alias}) @@ -60619,10 +63355,18 @@ elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then self:_InitNimitz() elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() +elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISE66 then +self:_InitEnterprise() +elseif self.carriertype==AIRBOSS.CarrierType.ENTERPRISEMODERN then +self:_InitEnterprise() elseif self.carriertype==AIRBOSS.CarrierType.VINSON then self:_InitStennis() elseif self.carriertype==AIRBOSS.CarrierType.ESSEX then self:_InitEssex() +elseif self.carriertype==AIRBOSS.CarrierType.BONHOMMERICHARD then +self:_InitBonHommeRichard() +elseif self.carriertype==AIRBOSS.CarrierType.ESSEXSCB125 then +self:_InitEssexSCB125() elseif self.carriertype==AIRBOSS.CarrierType.HERMES then self:_InitHermes() elseif self.carriertype==AIRBOSS.CarrierType.INVINCIBLE then @@ -60723,6 +63467,10 @@ self:AddTransition("*","Save","*") self:AddTransition("*","Stop","Stopped") return self end +function AIRBOSS:SetCarrierIllumination(Mode) +self.carrier:SetCarrierIlluminationMode(Mode) +return self +end function AIRBOSS:SetWelcomePlayers(Switch) self.welcome=Switch return self @@ -61071,8 +63819,7 @@ 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:SetPort(Port or MSRS.port or 5002) self.SRS:SetLabel(self.AirbossRadio.alias or"AIRBOSS") self.SRS:SetCoordinate(self.carrier:GetCoordinate()) self.SRS:SetVolume(Volume or 1) @@ -61082,7 +63829,9 @@ end if Voice then self.SRS:SetVoice(Voice) end -self.SRS:SetVolume(Volume or 1.0) +if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then +self.SRS.voice=MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B +end self.SRSQ=MSRSQUEUE:New("AIRBOSS") self.SRSQ:SetTransmitOnlyWithPlayers(true) if not self.PilotRadio then @@ -61851,6 +64600,17 @@ self.carrierparam.wire3=64 self.carrierparam.wire4=74 self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 end +function AIRBOSS:_InitEnterprise() +self:_InitForrestal() +self.carrierparam.sterndist=-164.30 +self.carrierparam.deckheight=19.52 +self.carrierparam.totlength=335 +self.carrierparam.rwylength=223 +self.carrierparam.wire1=57.7 +self.carrierparam.wire2=69.6 +self.carrierparam.wire3=79.5 +self.carrierparam.wire4=90.0 +end function AIRBOSS:_InitEssex() self:_InitNimitz() self.carrierparam.sterndist=-126 @@ -61878,6 +64638,20 @@ self.carrierparam.wire14=121.0 self.carrierparam.wire15=128.5 self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3 end +function AIRBOSS:_InitBonHommeRichard() +self:_InitEssex() +self.carrierparam.deckheight=16.95 +self.carrierparam.rwyangle=-11.4 +self.carrierparam.rwylength=97 +self.carrierparam.rwywidth=20 +self.carrierparam.wire1=40.4 +self.carrierparam.wire2=45 +self.carrierparam.wire3=51 +self.carrierparam.wire4=58.1 +end +function AIRBOSS:_InitEssexSCB125() +self:_InitBonHommeRichard() +end function AIRBOSS:_InitHermes() self:_InitStennis() self.carrierparam.sterndist=-105 @@ -62384,7 +65158,7 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then @@ -62404,13 +65178,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -62440,7 +65214,7 @@ return aoa end function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -62450,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -63083,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -63446,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -63712,7 +66487,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -63734,6 +66514,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -64476,77 +67278,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -64834,19 +67568,19 @@ end if 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" +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" +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)" +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)" +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 @@ -65461,7 +68195,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true) text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) local unitClient=Unit.getByName(unit:GetName()) local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early if hornet then local nozzlePosL=0 local burnerPosL=unitClient:getDrawArgumentValue(28) @@ -65577,7 +68311,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89) else nozzlePosL=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosL=unitClient:getDrawArgumentValue(434) end return nozzlePosL @@ -65594,7 +68328,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90) else nozzlePosR=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosR=unitClient:getDrawArgumentValue(433) end return nozzlePosR @@ -65645,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -65746,7 +68478,7 @@ 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=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -65937,7 +68669,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -65952,6 +68684,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66059,14 +68792,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66114,6 +68841,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66130,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -67177,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -70083,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -70129,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -70327,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -70346,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -70356,14 +73093,12 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) return self @@ -72422,6 +75157,9 @@ self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false self.ResourceMap=nil self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end self.StaticShape=nil self.TypeNames=nil self.StaticCategory="Cargos" @@ -72429,6 +75167,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -72485,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -72510,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -72775,6 +75518,7 @@ UserSetGroup=nil, LoadedGroupsTable={}, keeploadtable=true, allowCATransport=false, +VehicleMoveFormation=AI.Task.VehicleFormation.VEE, } CTLD.RadioModulation={ AM=0, @@ -72800,7 +75544,9 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, @@ -72815,8 +75561,9 @@ CTLD.FixedWingTypes={ ["Hercules"]="Hercules", ["Bronco"]="Bronco", ["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", } -CTLD.version="1.3.36" +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -72859,12 +75606,15 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") self:AddTransition("*","CratesPacked","*") self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") @@ -72905,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -72918,12 +75670,17 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true +self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false self.enableFixedWing=false self.FixedMinAngels=165 self.FixedMaxAngels=2000 self.FixedMaxSpeed=77 +self.validateAndRepositionUnits=false self.suppressmessages=false self.repairtime=300 self.buildtime=300 @@ -72943,6 +75700,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -72955,6 +75713,8 @@ self.filename=string.format("CTLD_%s_Persist.csv",AliaS) self.allowcratepickupagain=true self.enableslingload=false self.basetype="container_cargo" +self.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do @@ -73035,6 +75795,15 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end if _unit:IsHelicopter()or _group:IsHelicopter()then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil @@ -73070,6 +75839,7 @@ end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end elseif event.id==EVENTS.NewDynamicCargo then self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo @@ -73095,6 +75865,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73131,6 +75902,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73138,6 +75910,13 @@ self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -73165,6 +75944,12 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end function CTLD:AddAllowedFixedWingType(typename) @@ -73314,6 +76099,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -73409,7 +76195,7 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) local hassecondaries=false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!",10,false,Group) @@ -73538,18 +76324,340 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -73578,7 +76686,7 @@ 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 +if not self.debug then return false end end end end @@ -73588,13 +76696,16 @@ local loaddist=self.CrateDistance or 35 local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false +end +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -73609,6 +76720,7 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 @@ -73651,8 +76763,12 @@ self.CrateCounter=self.CrateCounter+1 local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() local basetype=CType or self.basetype or"container_cargo" CCat=CCat or"Cargos" -if isstatic then -basetype=cratetemplate +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -73698,8 +76814,8 @@ 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) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -73711,23 +76827,35 @@ realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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) else +if not quiet then self:_SendMessage(text,10,false,Group) end +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -73826,6 +76954,52 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis end return self end +function CTLD:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +end +return self +end function CTLD:_RemoveCratesNearby(_group,_unit) self:T(self.lid.." _RemoveCratesNearby") local finddist=self.CrateDistance or 35 @@ -73838,8 +77012,10 @@ for _,_entry in pairs(crates)do local entry=_entry local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -if entry:GetPositionable()then -entry:GetPositionable():Destroy(false) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end table.insert(removedIDs,entry:GetID()) end @@ -73848,8 +77024,17 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -73875,7 +77060,7 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() @@ -73911,8 +77096,16 @@ local cando=cargo:UnitCanCarry(_unit) if ignoretype==true then cando=true end self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distance0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -74144,6 +77347,18 @@ end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -74287,7 +77502,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -74343,6 +77558,7 @@ local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type) @@ -74382,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74392,6 +77611,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74480,6 +77700,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74489,6 +77710,9 @@ end end return self end +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then @@ -74505,11 +77729,19 @@ self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end -local finddist=self.CrateDistance or 35 +local baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -74519,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -74541,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -74569,19 +77812,55 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end self:__CratesBuildStarted(1,Group,Unit,build.Name) -self:_RefreshDropTroopsMenu(Group,Unit) else self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end +end end end end @@ -74594,24 +77873,32 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) self:_RefreshLoadCratesMenu(Group,Unit) self:__CratesPacked(1,Group,Unit,_entry) -return true +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end +if not packedAny then self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end +return true +end function CTLD:_RepairCrates(Group,Unit,Engineering) self:T(self.lid.." _RepairCrates") local finddist=self.CrateDistance or 35 @@ -74716,11 +78003,13 @@ local alias=string.format("%s-%d",_template,math.random(1,100000)) if canmove then self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord) else self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord) end @@ -74735,6 +78024,13 @@ self:T(self.lid.."Group KIA while building!") end return self end +function CTLD:_GetVehicleFormation() +local VehicleMoveFormation=self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE +if type(self.VehicleMoveFormation)=="table"then +VehicleMoveFormation=self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)] +end +return VehicleMoveFormation +end function CTLD:_MoveGroupToZone(Group) self:T(self.lid.." _MoveGroupToZone") local groupname=Group:GetName()or"none" @@ -74744,12 +78040,12 @@ self:T({canmove=outcome,name=name,zone=zone,dist=distance,max=self.movetroopsdis 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() +local formation=self:_GetVehicleFormation() Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) -Group:OptionROEOpenFirePossible() -Group:RouteToVec2(coordinate,5) +Group:OptionROEOpenFire() +Group:RouteGroundTo(zonecoord,25,formation) end return self end @@ -74770,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -74817,13 +78114,57 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -74832,6 +78173,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -74956,85 +78530,112 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu,subcatmenus) +if cargoObj.DontShowInMenu then +return +end +local isStaticCargo=false +if(cargoObj.GetType and cargoObj:GetType()==CTLD_CARGO.Enum.STATIC)or cargoObj.CargoType==CTLD_CARGO.Enum.STATIC then +isStaticCargo=true +end +if isStaticCargo and cargoObj.UnitCanCarry and not cargoObj:UnitCanCarry(_unit)then +return +end +local parent=parentMenu +if subcatmenus and cargoObj.Subcategory then +parent=subcatmenus[cargoObj.Subcategory] +if not parent then +parent=MENU_GROUP:New(_group,cargoObj.Subcategory,cratesmenu) +subcatmenus[cargoObj.Subcategory]=parent +end +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for catName,_ in pairs(self.subcats)do -subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) -end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} -for catName,_ in pairs(self.subcats)do +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end +return subcatmenus[catName] +end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75042,8 +78643,14 @@ MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,c end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75092,6 +78699,43 @@ MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,sel end end end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end +else +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) @@ -75114,6 +78758,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75125,6 +78771,10 @@ end function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then @@ -75158,7 +78808,7 @@ lineIndex=lineIndex+1 end end end -function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) local grounded=not self:IsUnitInAir(Unit) local hover=self:CanHoverLoad(Unit) @@ -75189,18 +78839,23 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) local capacity=capabilities.cratelimit or 0 if loadedData.Cratesloaded>=capacity then self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress return self end local spaceLeft=capacity-loadedData.Cratesloaded local toLoad=math.min(found,needed,spaceLeft) if toLoad<1 then self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress return self end local crateIDsLoaded={} @@ -75233,6 +78888,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if details or(not batch)then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75250,8 +78906,29 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75345,6 +79022,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -75388,29 +79066,62 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75427,33 +79138,98 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) -MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then -MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) -end i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75467,7 +79243,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end 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) @@ -75543,6 +79319,7 @@ local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],cType) @@ -75574,6 +79351,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -75604,6 +79382,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -75676,7 +79455,45 @@ table.insert(self.Cargo_Troops,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnits") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Units for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock or nil +entry.Stock0=Stock or nil +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=true +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddUnitsNoMove(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnitsNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."UnitsNoMove for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock +entry.Stock0=Stock +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=false +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) self:T(self.lid.." AddCratesCargo") if not self:_CheckTemplates(Templates)then self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") @@ -75691,11 +79508,41 @@ cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end +cargo.C130TypeName=C130TypeName table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) +self:T(self.lid.." AddCratesCargoNoMove") +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) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +cargo.C130TypeName=C130TypeName +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 local type=CTLD_CARGO.Enum.STATIC @@ -75706,6 +79553,9 @@ if unittemplate and unittemplate.resourcePayload then ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) end local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end @@ -75847,7 +79697,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -75870,9 +79725,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -76206,6 +80097,7 @@ return self end function CTLD:IsCorrectHover(Unit) self:T(self.lid.." IsCorrectHover") +if self:IsFixedWing(Unit)then return false end local outcome=false if self:IsUnitInAir(Unit)then local uspeed=Unit:GetVelocityMPS() @@ -76383,6 +80275,23 @@ Troopstable[genname].GenericCargo=generic end end end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end for _id,_cargo in pairs(self.Cargo_Troops)do local generic=_cargo local genname=generic:GetName() @@ -76413,10 +80322,31 @@ Troopstable[genname].Infield=Troopstable[genname].Infield+1 Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo end else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) end end end +end for _unitname,_loaded in pairs(self.Loaded_Cargo)do local _unit=UNIT:FindByName(_unitname) if _unit and _unit:IsAlive()then @@ -76425,12 +80355,12 @@ local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} for _,_cgo in pairs(loadedcargo)do local cargo=_cgo local type=cargo.CargoType -local gname=cargo.Name +local gname=cargo:GetName() local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) -self:T("Looking at "..gname.." in the helo - type = "..type) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then if gcargo and gcargo:GetStock0()>0 then -self:T("Adding "..gname.." in the helo - type = "..type) +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 end @@ -76498,6 +80428,47 @@ end end return Troopstable end +function CTLD:AddStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock+number +end +break +end +end +return self +end +function CTLD:SetStockUnits(Name,Number) +local name=Name or"none" +local number=Number +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +if number==nil or number==-1 then +_unit.Stock=-1 +else +_unit.Stock=number +end +break +end +end +return self +end +function CTLD:GetStockUnits() +local Stock={} +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +Stock[_unit.Name]=_unit.Stock or-1 +end +return Stock +end function CTLD:AddStockTroops(Name,Number) local name=Name or"none" local number=Number or 1 @@ -76505,6 +80476,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76517,6 +80489,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76529,6 +80502,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76541,6 +80515,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76553,6 +80528,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76565,6 +80541,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76615,6 +80592,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -76626,6 +80604,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -76637,6 +80616,27 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() +end +end +return self +end +function CTLD:RemoveStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock-number +if _unit.Stock<0 then +_unit.Stock=0 +end +end +break end end return self @@ -76781,6 +80781,7 @@ 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(randompositions,20,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitDelayOff() :OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp) :SpawnFromVec2(randomcoord) @@ -76898,12 +80899,14 @@ 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) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitDelayOff() :OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp) :SpawnFromVec2(randomcoord) else self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias) :InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp) :SpawnFromVec2(randomcoord) end @@ -76955,6 +80958,14 @@ local filename=self.filename local filepath=self.filepath self:__Save(interval,filepath,filename) end +if type(self.VehicleMoveFormation)=="table"then +local Formations={} +for _,_formation in pairs(self.VehicleMoveFormation)do +table.insert(Formations,_formation) +end +self.VehicleMoveFormation=nil +self.VehicleMoveFormation=Formations +end return self end function CTLD:onbeforeStatus(From,Event,To) @@ -77075,11 +81086,44 @@ 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) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +for _,_cargo in pairs(Cargotable)do +local Vehicle=_cargo.Positionable +if Vehicle then +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 +end +end +return self +end +function CTLD:OnAfterGetCrates(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:OnAfterRemoveCratesNearby(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) return self end function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) @@ -77107,9 +81151,13 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -77356,11 +81404,22 @@ 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) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +if self.C130GetUnits then +for _,_unit in pairs(self.C130GetUnits)do +if _unit.Name==cargoname then +if type(_unit.Stock)=="number"and _unit.Stock~=-1 then +_unit.Stock0=_unit.Stock0 or _unit.Stock +_unit.Stock=math.max(0,(_unit.Stock or 0)-1) +end +break +end +end +end 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,timestamp) end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +elseif self.loadSavedCrates and(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic=nil if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then @@ -77372,8 +81431,12 @@ injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) 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) injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) -local map=cargotype:GetStaticResourceMap() -injectstatic:SetStaticResourceMap(map) +local unittemplate=_DATABASE:GetStaticUnitTemplate(cargoname) +local ResourceMap=nil +if unittemplate and unittemplate.resourcePayload then +ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) +end +injectstatic:SetStaticResourceMap(ResourceMap) end if injectstatic then self:InjectStatics(dropzone,injectstatic,false,true) @@ -77842,6 +81905,8 @@ CreateRadioBeacons=true, UserSetGroup=nil, AllowIRStrobe=false, IRStrobeRuntime=300, +FARPRescueDistance=500, +EnableMenuSmokeMASH=true, } CSAR.AircraftType={} CSAR.AircraftType["SA342Mistral"]=2 @@ -77855,13 +81920,14 @@ CSAR.AircraftType["Mi-24P"]=8 CSAR.AircraftType["Mi-24V"]=8 CSAR.AircraftType["Bell-47"]=2 CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 CSAR.AircraftType["AH-64D_BLK_II"]=2 CSAR.AircraftType["Bronco-OV-10A"]=2 CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.35" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -77941,12 +82007,13 @@ self.loadtimemax=135 self.radioSound="beacon.ogg" self.beaconRefresher=29 self.allowFARPRescue=true -self.FARPRescueDistance=1000 +self.FARPRescueDistance=500 self.max_units=6 self.useprefix=true self.csarPrefix={"helicargo","MEDEVAC"} self.template=Template or"generic" self.mashprefix={"MASH"} +self.EnableMenuSmokeMASH=true self.autosmoke=false self.autosmokedistance=2000 self.limitmaxdownedpilots=true @@ -78124,6 +82191,9 @@ BeaconName=_unitName..math.random(1,10000) else BeaconName="Ghost-1-1"..math.random(1,10000) end +if _playerName==nil or _playerName==""then +_playerName="AI MIA" +end if(_freq and _freq~=0)then self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) end @@ -78307,11 +82377,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78344,7 +82414,7 @@ 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") +self:_AddCsar(_coalition,_unit:GetCountry(),initcoord,_unit:GetTypeName(),_unit:GetName(),_event.IniPlayerName,_freq,self.suppressmessages,"none") return self end elseif _event.id==EVENTS.Land then @@ -78366,7 +82436,7 @@ end self.takenOff[_event.IniUnitName]=nil local _place=_event.Place if _place==nil then -self:T(self.lid.." Landing Place Nil") +self:T(self.lid.." Landing Place nil") return self end if self.inTransitGroups[_event.IniUnitName]==nil then @@ -78374,7 +82444,10 @@ 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) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -78391,8 +82464,8 @@ 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") +self:T({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) +self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") Unit.destroy(_event.initiator) end end @@ -78729,7 +82802,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -78747,7 +82820,13 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_distself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end end for _,_mashes in pairs(MashSets)do for _,_mashUnit in pairs(_mashes or{})do @@ -79067,11 +83186,12 @@ _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance,MashName +return _shortestDistance,MashName,Coordinate else return-1 end @@ -79126,6 +83246,9 @@ local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -79140,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -79211,7 +83335,7 @@ local name=_radioUnit:GetName() local Frequency=_freq local Sound="l10n/DEFAULT/"..self.radioSound local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end @@ -79521,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -79530,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -79600,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -79623,7 +83748,7 @@ 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) +self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,false,description,nil) end return self end @@ -79644,7 +83769,7 @@ capFormation=nil, capOptionVaryStartTime=nil, capOptionVaryEndTime=nil, } -AIRWING.version="0.9.6" +AIRWING.version="0.9.7" function AIRWING:New(warehousename,airwingname) local self=BASE:Inherit(self,LEGION:New(warehousename,airwingname)) if not self then @@ -79676,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -79938,19 +84080,29 @@ point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed) return text end function AIRWING:UpdatePatrolPointMarker(point) -if self.markpoints then +if self and 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) +if point.IsZonePoint and point.IsZonePoint==true and point.patrolzone then +local Coordinate=point.patrolzone:GetCoordinate() +point.marker:UpdateCoordinate(Coordinate) +point.marker:UpdateText(text,1.5) +else 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 +function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem) local patrolpoint={} patrolpoint.type=Type or"Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10,15)),math.random(360)) +if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then +patrolpoint.IsZonePoint=true +patrolpoint.patrolzone=Coordinate +patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate() +else +patrolpoint.IsZonePoint=false +end patrolpoint.heading=Heading or math.random(360) patrolpoint.leg=LegLength or 15 patrolpoint.altitude=Altitude or math.random(10,20)*1000 @@ -79959,7 +84111,7 @@ patrolpoint.noccupied=0 patrolpoint.refuelsystem=RefuelSystem if self.markpoints then patrolpoint.marker=MARKER:New(Coordinate,"New Patrol Point"):ToAll() -AIRWING.UpdatePatrolPointMarker(patrolpoint) +self:UpdatePatrolPointMarker(patrolpoint) end return patrolpoint end @@ -80103,6 +84255,9 @@ if PatrolPoints and#PatrolPoints>0 then table.sort(PatrolPoints,sort) for _,_patrolpoint in pairs(PatrolPoints)do local patrolpoint=_patrolpoint +if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint==true and patrolpoint.patrolzone then +patrolpoint.coord=patrolpoint.patrolzone:GetCoordinate() +end if(RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem)or RefuelSystem==nil or patrolpoint.refuelsystem==nil then return patrolpoint end @@ -80133,7 +84288,7 @@ missionCAP:SetTime(ClockStart) end missionCAP.patroldata=patrol patrol.noccupied=patrol.noccupied+1 -if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(missionCAP) end return self @@ -80159,7 +84314,7 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(missionRECON) end return self @@ -80183,7 +84338,7 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(mission) end for i=1,self.nflightsTANKERprobe-Nprob do @@ -80192,7 +84347,7 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(mission) end return self @@ -80211,7 +84366,7 @@ 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 +if self.markpoints then self:UpdatePatrolPointMarker(patrol)end self:AddMission(mission) end return self @@ -80592,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -80933,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -81047,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -81325,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -81348,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -81395,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -81584,7 +85741,12 @@ 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) +local mission +if Leg==0 then +mission=AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed) +else +mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) +end mission.type=AUFTRAG.Type.TANKER mission:_SetLogID() mission.refuelSystem=RefuelSystem @@ -81754,7 +85916,6 @@ return mission end function AUFTRAG:NewSEADInZone(TargetZone,Altitude,TargetTypes,Duration) local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) -mission:_TargetFromObject(TargetZone) mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) @@ -81900,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -82012,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -82020,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -82054,6 +86216,20 @@ mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.mi mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -82189,33 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) elseif MissionType==AUFTRAG.Type.STRAFING then -mission=self:NewSTRAFING(Target,Altitude) +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -82276,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -82839,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -83043,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -83289,12 +87476,18 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 -if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) return false end return true @@ -83559,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -84101,6 +88294,15 @@ param.action="Wedge" param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 +DCStask.params=param +table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then local DCStask={} DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY @@ -84361,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.72", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -84978,6 +89180,11 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end function AWACS:SetMaxMissionRange(NM) self.MaxMissionRange=NM or 125 return self @@ -85237,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -85250,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -86728,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -86761,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -86786,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -86878,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -87421,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -88064,24 +92324,36 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) @@ -89096,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -89125,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.1" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -90155,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -90379,7 +94712,7 @@ descriptors={}, properties={}, operations={}, } -COHORT.version="0.3.6" +COHORT.version="0.3.7" _COHORTNAMES={} function COHORT:New(TemplateGroupName,Ngroups,CohortName) local name=tostring(CohortName or TemplateGroupName) @@ -90530,9 +94863,10 @@ function COHORT:SetMissionRange(Range) self.engageRange=UTILS.NMToMeters(Range or 150) return self end -function COHORT:SetCallsign(Callsign,Index) +function COHORT:SetCallsign(Callsign,Index,CallsignString) self.callsignName=Callsign self.callsignIndex=Index +self.callsignClearName=CallsignString self.callsign={} self.callsign.NumberSquad=Callsign self.callsign.NumberGroup=Index @@ -90591,8 +94925,11 @@ end end return self end -function COHORT:RemoveAssets(N) +function COHORT:RemoveAssets(N,Delay) self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else N=N or 1 local n=0 for i=#self.assets,1,-1 do @@ -90600,6 +94937,7 @@ local asset=self.assets[i] self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) if not(asset.requested or asset.spawned or asset.isReserved)then self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) table.remove(self.assets,i) n=n+1 else @@ -90610,6 +94948,7 @@ break end end self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end return self end function COHORT:GetName() @@ -90632,6 +94971,9 @@ self.callsigncounter=self.callsigncounter+2 else self.callsigncounter=self.callsigncounter+1 end +callsign["name"]=self.callsignClearName or UTILS.GetCallsignName(self.callsignName)or"None" +callsign["name"]=string.format("%s%d%d",callsign["name"],callsign[2],callsign[3]) +callsign[4]=callsign["name"] Asset.callsign[i]=callsign self:T3({callsign=callsign}) end @@ -96340,12 +100682,14 @@ local clients=_DATABASE.CLIENTS local coords={} for clientname,client in pairs(clients)do local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) +if template then 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 airbasecategory=airbase:GetAirbaseCategory() @@ -96539,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -96763,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -96770,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -96823,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -96830,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -96862,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -97028,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -97069,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -97283,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -98132,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98187,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -98641,6 +103087,7 @@ if self:IsAirwing()then opsgroup=FLIGHTGROUP:New(asset.spawngroupname) elseif self:IsBrigade()then opsgroup=ARMYGROUP:New(asset.spawngroupname) +opsgroup:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) elseif self:IsFleet()then opsgroup=NAVYGROUP:New(asset.spawngroupname) else @@ -98677,7 +103124,7 @@ text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsR for _,mtype in pairs(AUFTRAG.Type)do local n=self:CountMissionsInQueue(mtype) if n>0 then -local N=self:CountMissionsInQueue(mtype) +local N=self:CountMissionsInQueue(mtype,true) text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) end end @@ -98772,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -99534,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.3" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -99901,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -100125,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -100133,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -100149,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -100167,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -102197,6 +106648,12 @@ end function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +function OPSGROUP:IsOutOfA2GAmmo() +if(self.outofMissilesAG and self.outofBombs and self.outofGuns)or self.outofAmmo then +return true +end +return false +end function OPSGROUP:IsLasing() return self.spot.On end @@ -102976,14 +107433,32 @@ end function OPSGROUP:_UpdateTask(Task,Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +if Mission.type==AUFTRAG.Type.RESCUEHELO then +self:T("**********") +self:T("** RESCUEHELO USED") +self:T("**********") +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +local helogroupname=self:GetGroup():GetName() +Task.formation=RESCUEHELO:New(followUnit,helogroupname) +Task.formation:SetRespawnOnOff(false) +Task.formation.respawninair=false +Task.formation:SetTakeoffCold() +Task.formation:SetHomeBase(followUnit) +Task.formation.helo=self:GetGroup() +Task.formation:Start() +if self:IsFlightgroup()then +self:SetDespawnAfterLanding() +end +else 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=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION) 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() +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then local zone=Task.dcstask.params.zone local surfacetypes=nil @@ -103067,6 +107542,15 @@ end if target then self:EngageTarget(target,speed,Task.dcstask.params.formation) end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT 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=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.altitude) +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then if self.isFlightgroup then self:T("We are Special Auftrag Patrol Race Track, starting now ...") @@ -103126,7 +107610,12 @@ 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())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -103174,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -103250,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -103630,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -103639,7 +108128,7 @@ end self:_RemoveMissionWaypoints(Mission) if Mission.patroldata then Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 -AIRWING.UpdatePatrolPointMarker(Mission.patroldata) +AIRWING.UpdatePatrolPointMarker(self,Mission.patroldata) end if Mission.engagedetectedOn then self:SetEngageDetectedOff() @@ -103910,10 +108399,12 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then @@ -104493,7 +108984,7 @@ 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:T(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() @@ -104640,8 +109131,11 @@ function OPSGROUP:_Spawn(Delay,Template) if Delay and Delay>0 then self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) else -self:T2({Template=Template}) +if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then +UTILS.ValidateAndRepositionGroundUnits(Template.units) +end self.group=_DATABASE:Spawn(Template) +self.group:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) self.dcsgroup=self:GetDCSGroup() self.controller=self.dcsgroup:getController() self.isLateActivated=Template.lateActivation @@ -104704,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -104762,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -106277,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -107045,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -107410,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -107425,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -107445,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -107475,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -107520,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -107767,6 +112279,9 @@ end end return targetgroup,targetdist end +function OPSGROUP:SetValidateAndRepositionGroundUnits(Enabled) +self.ValidateAndRepositionGroundUnits=Enabled +end OPSTRANSPORT={ ClassName="OPSTRANSPORT", verbose=0, @@ -109566,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -109693,6 +114211,13 @@ end end) return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -109729,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110126,10 +114661,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -110172,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -110232,6 +114770,12 @@ Scoring=nil, MenuNoTask=nil, InformationMenu=false, TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -110260,6 +114804,12 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.CAP]=100, [AUFTRAG.Type.CAPTUREZONE]=100, } +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, +} PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, AAA=GROUP.Attribute.GROUND_AAA, @@ -110288,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -110372,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -110435,7 +114991,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -110506,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -110861,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -111413,17 +115982,24 @@ end PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end +self:__TaskAdded(10,PlayerTask,Silent) else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end return self end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) self:T({Force,Group,Client}) self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end local force=false if type(Force)=="boolean"then force=Force @@ -111508,7 +116084,7 @@ 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 +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) @@ -111551,8 +116127,9 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -111568,7 +116145,12 @@ 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) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -111670,8 +116252,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -111724,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -111733,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -112141,6 +116738,49 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -112272,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -115377,17 +120212,22 @@ self.wings[Airbasename]={CAP_Wing,AIRBASE:FindByName(Airbasename):GetZone(),Airb return self end function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) -self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM()) +self:T(self.lid.."AddPatrolPointCAP") +local coordinate=Coordinate local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end EntryCAP.AirbaseName=AirbaseName -EntryCAP.Coordinate=Coordinate +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() +local mark=MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll() end return self end @@ -115489,8 +120329,13 @@ local Altitude=data.Altitude local Speed=data.Speed local Heading=data.Heading local LegLength=data.LegLength +local Zone=_data.Zone +if Zone then +Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength) +else Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) end +end return self end function EASYGCICAP:_SetReconPatrolPoints() @@ -115639,7 +120484,9 @@ Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetMissionRange(self.missionrange) Squadron_One:SetRadio(Frequency,Modulation) +if TACAN then Squadron_One:AddTacanChannel(TACAN,TACAN) +end local wing=self.wings[AirbaseName][1] wing:AddSquadron(Squadron_One) wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75) @@ -115677,20 +120524,47 @@ self.ConflictZoneSet:AddZone(Zone) self.GoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) -self:I("_TryAssignIntercept for size "..WingSize or 1) +self:T("_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())) +self:T(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)) +self:T(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 @@ -115704,7 +120578,7 @@ FG:AddMission(InterceptAuftrag) local cm=FG:GetMissionCurrent() if cm then cm:Cancel()end wingsize=wingsize-1 -self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) +self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) if wingsize==0 then assigned=true break @@ -115761,6 +120635,9 @@ for _,_data in pairs(ctlpts)do local data=_data local name=data.AirbaseName local zonecoord=data.Coordinate +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end local airwing=wings[name][1] local coa=AIRBASE:FindByName(name):GetCoalition() local samecoalitionab=coa==self.coalition and true or false @@ -115798,6 +120675,8 @@ end if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then success=false end +else +success=true end return success end, @@ -115806,6 +120685,13 @@ nogozoneset, conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -115831,6 +120717,12 @@ BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -115903,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -115913,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -115941,6 +120834,480 @@ end function EASYGCICAP:onafterStop(From,Event,To) self:T({From,Event,To}) self.Intel:Stop() +for _,_wing in pairs(self.wings or{})do +_wing:Stop() +end +return self +end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) return self end AI_BALANCER={ @@ -124679,12 +130046,18 @@ end return true end function CIRCLE:GetRandomVec2() +math.random() +math.random() +math.random() 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() +math.random() +math.random() +math.random() 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 @@ -125367,6 +130740,9 @@ local has_pos=(d1>0)or(d2>0)or(d3>0) return not(has_neg and has_pos) end function TRIANGLE:GetRandomVec2(points) +math.random() +math.random() +math.random() points=points or self.Points local pt={math.random(),math.random()} table.sort(pt) @@ -126517,13 +131893,15 @@ Standard={ ["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-N', -["en_GB_Standard_B"]='en-GB-Standard-O', -["en_GB_Standard_C"]='en-GB-Standard-N', -["en_GB_Standard_D"]='en-GB-Standard-O', -["en_GB_Standard_F"]='en-GB-Standard-N', -["en_GB_Standard_O"]='en-GB-Standard-O', +["en_IN_Standard_E"]='en-IN-Standard-E', +["en_IN_Standard_F"]='en-IN-Standard-F', +["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_GB_Standard_N"]='en-GB-Standard-N', +["en_GB_Standard_O"]='en-GB-Standard-O', ["en_US_Standard_A"]='en-US-Standard-A', ["en_US_Standard_B"]='en-US-Standard-B', ["en_US_Standard_C"]='en-US-Standard-C', @@ -126541,14 +131919,14 @@ Standard={ ["fr_FR_Standard_E"]="fr-FR-Standard-F", ["fr_FR_Standard_G"]="fr-FR-Standard-G", ["fr_FR_Standard_F"]="fr-FR-Standard-F", -["de_DE_Standard_A"]="de-DE-Standard-G", -["de_DE_Standard_B"]="de-DE-Standard-H", -["de_DE_Standard_C"]="de-DE-Standard-G", -["de_DE_Standard_D"]="de-DE-Standard-H", -["de_DE_Standard_E"]="de-DE-Standard-H", -["de_DE_Standard_F"]="de-DE-Standard-G", -["de_DE_Standard_H"]="de-DE-Standard-H", -["de_DE_Standard_G"]="de-DE-Standard-G", +["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', +["de_DE_Standard_G"]='de-DE-Standard-G', +["de_DE_Standard_H"]='de-DE-Standard-H', ["es_ES_Standard_A"]="es-ES-Standard-E", ["es_ES_Standard_B"]="es-ES-Standard-F", ["es_ES_Standard_C"]="es-ES-Standard-E", @@ -126571,11 +131949,13 @@ Wavenet={ ["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-N', -["en_GB_Wavenet_B"]='en-GB-Wavenet-O', -["en_GB_Wavenet_C"]='en-GB-Wavenet-N', -["en_GB_Wavenet_D"]='en-GB-Wavenet-O', -["en_GB_Wavenet_F"]='en-GB-Wavenet-N', +["en_IN_Wavenet_E"]='en-IN-Wavenet-E', +["en_IN_Wavenet_F"]='en-IN-Wavenet-F', +["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_GB_Wavenet_O"]='en-GB-Wavenet-O', ["en_GB_Wavenet_N"]='en-GB-Wavenet-N', ["en_US_Wavenet_A"]='en-US-Wavenet-A', @@ -126595,14 +131975,14 @@ Wavenet={ ["fr_FR_Wavenet_E"]="fr-FR-Wavenet-F", ["fr_FR_Wavenet_G"]="fr-FR-Wavenet-G", ["fr_FR_Wavenet_F"]="fr-FR-Wavenet-F", -["de_DE_Wavenet_A"]="de-DE-Wavenet-G", -["de_DE_Wavenet_B"]="de-DE-Wavenet-H", -["de_DE_Wavenet_C"]="de-DE-Wavenet-G", -["de_DE_Wavenet_D"]="de-DE-Wavenet-H", -["de_DE_Wavenet_E"]="de-DE-Wavenet-H", -["de_DE_Wavenet_F"]="de-DE-Wavenet-G", -["de_DE_Wavenet_H"]="de-DE-Wavenet-H", -["de_DE_Wavenet_G"]="de-DE-Wavenet-G", +["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', +["de_DE_Wavenet_G"]='de-DE-Wavenet-G', +["de_DE_Wavenet_H"]='de-DE-Wavenet-H', ["es_ES_Wavenet_B"]="es-ES-Wavenet-E", ["es_ES_Wavenet_C"]="es-ES-Wavenet-F", ["es_ES_Wavenet_D"]="es-ES-Wavenet-E", @@ -126615,6 +131995,117 @@ Wavenet={ ["it_IT_Wavenet_E"]="it-IT-Wavenet-E", ["it_IT_Wavenet_F"]="it-IT-Wavenet-F", }, +Chirp3HD={ +["en_GB_Chirp3_HD_Aoede"]='en-GB-Chirp3-HD-Aoede', +["en_GB_Chirp3_HD_Charon"]='en-GB-Chirp3-HD-Charon', +["en_GB_Chirp3_HD_Fenrir"]='en-GB-Chirp3-HD-Fenrir', +["en_GB_Chirp3_HD_Kore"]='en-GB-Chirp3-HD-Kore', +["en_GB_Chirp3_HD_Leda"]='en-GB-Chirp3-HD-Leda', +["en_GB_Chirp3_HD_Orus"]='en-GB-Chirp3-HD-Orus', +["en_GB_Chirp3_HD_Puck"]='en-GB-Chirp3-HD-Puck', +["en_GB_Chirp3_HD_Zephyr"]='en-GB-Chirp3-HD-Zephyr', +["en_US_Chirp3_HD_Charon"]='en-US-Chirp3-HD-Charon', +["en_US_Chirp3_HD_Fenrir"]='en-US-Chirp3-HD-Fenrir', +["en_US_Chirp3_HD_Kore"]='en-US-Chirp3-HD-Kore', +["en_US_Chirp3_HD_Leda"]='en-US-Chirp3-HD-Leda', +["en_US_Chirp3_HD_Orus"]='en-US-Chirp3-HD-Orus', +["en_US_Chirp3_HD_Puck"]='en-US-Chirp3-HD-Puck', +["de_DE_Chirp3_HD_Aoede"]='de-DE-Chirp3-HD-Aoede', +["de_DE_Chirp3_HD_Charon"]='de-DE-Chirp3-HD-Charon', +["de_DE_Chirp3_HD_Fenrir"]='de-DE-Chirp3-HD-Fenrir', +["de_DE_Chirp3_HD_Kore"]='de-DE-Chirp3-HD-Kore', +["de_DE_Chirp3_HD_Leda"]='de-DE-Chirp3-HD-Leda', +["de_DE_Chirp3_HD_Orus"]='de-DE-Chirp3-HD-Orus', +["de_DE_Chirp3_HD_Puck"]='de-DE-Chirp3-HD-Puck', +["de_DE_Chirp3_HD_Zephyr"]='de-DE-Chirp3-HD-Zephyr', +["en_AU_Chirp3_HD_Aoede"]='en-AU-Chirp3-HD-Aoede', +["en_AU_Chirp3_HD_Charon"]='en-AU-Chirp3-HD-Charon', +["en_AU_Chirp3_HD_Fenrir"]='en-AU-Chirp3-HD-Fenrir', +["en_AU_Chirp3_HD_Kore"]='en-AU-Chirp3-HD-Kore', +["en_AU_Chirp3_HD_Leda"]='en-AU-Chirp3-HD-Leda', +["en_AU_Chirp3_HD_Orus"]='en-AU-Chirp3-HD-Orus', +["en_AU_Chirp3_HD_Puck"]='en-AU-Chirp3-HD-Puck', +["en_AU_Chirp3_HD_Zephyr"]='en-AU-Chirp3-HD-Zephyr', +["en_IN_Chirp3_HD_Aoede"]='en-IN-Chirp3-HD-Aoede', +["en_IN_Chirp3_HD_Charon"]='en-IN-Chirp3-HD-Charon', +["en_IN_Chirp3_HD_Fenrir"]='en-IN-Chirp3-HD-Fenrir', +["en_IN_Chirp3_HD_Kore"]='en-IN-Chirp3-HD-Kore', +["en_IN_Chirp3_HD_Leda"]='en-IN-Chirp3-HD-Leda', +["en_IN_Chirp3_HD_Orus"]='en-IN-Chirp3-HD-Orus', +}, +ChirpHD={ +["en_US_Chirp_HD_D"]='en-US-Chirp-HD-D', +["en_US_Chirp_HD_F"]='en-US-Chirp-HD-F', +["en_US_Chirp_HD_O"]='en-US-Chirp-HD-O', +["de_DE_Chirp_HD_D"]='de-DE-Chirp-HD-D', +["de_DE_Chirp_HD_F"]='de-DE-Chirp-HD-F', +["de_DE_Chirp_HD_O"]='de-DE-Chirp-HD-O', +["en_AU_Chirp_HD_D"]='en-AU-Chirp-HD-D', +["en_AU_Chirp_HD_F"]='en-AU-Chirp-HD-F', +["en_AU_Chirp_HD_O"]='en-AU-Chirp-HD-O', +["en_IN_Chirp_HD_D"]='en-IN-Chirp-HD-D', +["en_IN_Chirp_HD_F"]='en-IN-Chirp-HD-F', +["en_IN_Chirp_HD_O"]='en-IN-Chirp-HD-O', +}, +}, +Neural2={ +["en_GB_Neural2_A"]='en-GB-Neural2-A', +["en_GB_Neural2_B"]='en-GB-Neural2-B', +["en_GB_Neural2_C"]='en-GB-Neural2-C', +["en_GB_Neural2_D"]='en-GB-Neural2-D', +["en_GB_Neural2_F"]='en-GB-Neural2-F', +["en_GB_Neural2_N"]='en-GB-Neural2-N', +["en_GB_Neural2_O"]='en-GB-Neural2-O', +["en_US_Neural2_A"]='en-US-Neural2-A', +["en_US_Neural2_C"]='en-US-Neural2-C', +["en_US_Neural2_D"]='en-US-Neural2-D', +["en_US_Neural2_E"]='en-US-Neural2-E', +["en_US_Neural2_F"]='en-US-Neural2-F', +["en_US_Neural2_G"]='en-US-Neural2-G', +["en_US_Neural2_H"]='en-US-Neural2-H', +["en_US_Neural2_I"]='en-US-Neural2-I', +["en_US_Neural2_J"]='en-US-Neural2-J', +["de_DE_Neural2_G"]='de-DE-Neural2-G', +["de_DE_Neural2_H"]='de-DE-Neural2-H', +["en_AU_Neural2_A"]='en-AU-Neural2-A', +["en_AU_Neural2_B"]='en-AU-Neural2-B', +["en_AU_Neural2_C"]='en-AU-Neural2-C', +["en_AU_Neural2_D"]='en-AU-Neural2-D', +["en_IN_Neural2_A"]='en-IN-Neural2-A', +["en_IN_Neural2_B"]='en-IN-Neural2-B', +["en_IN_Neural2_C"]='en-IN-Neural2-C', +["en_IN_Neural2_D"]='en-IN-Neural2-D', +}, +News={ +["en_GB_News_G"]='en-GB-News-G', +["en_GB_News_H"]='en-GB-News-H', +["en_GB_News_I"]='en-GB-News-I', +["en_GB_News_J"]='en-GB-News-J', +["en_GB_News_K"]='en-GB-News-K', +["en_GB_News_L"]='en-GB-News-L', +["en_GB_News_M"]='en-GB-News-M', +["en_US_News_K"]='en-US-News-K', +["en_US_News_L"]='en-US-News-L', +["en_US_News_N"]='en-US-News-N', +["en_AU_News_E"]='en-AU-News-E', +["en_AU_News_F"]='en-AU-News-F', +["en_AU_News_G"]='en-AU-News-G', +}, +Casual={ +["en_US_Casual_K"]='en-US-Casual-K', +}, +Polyglot={ +["en_US_Polyglot_1"]='en-US-Polyglot-1', +["de_DE_Polyglot_1"]='de-DE-Polyglot-1', +["en_AU_Polyglot_1"]='en-AU-Polyglot-1', +}, +Studio={ +["en_GB_Studio_B"]='en-GB-Studio-B', +["en_GB_Studio_C"]='en-GB-Studio-C', +["en_US_Studio_O"]='en-US-Studio-O', +["en_US_Studio_Q"]='en-US-Studio-Q', +["de_DE_Studio_B"]='de-DE-Studio-B', +["de_DE_Studio_C"]='de-DE-Studio-C', }, } MSRS.Backend={ @@ -131493,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -3303,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3860,7 +4407,57 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +4475,61 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4558,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -4417,6 +5045,185 @@ end end return nil end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -7703,7 +8510,7 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName=Event.TgtDCSUnitName Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) @@ -7726,10 +8533,12 @@ 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -9543,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -9553,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9600,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9918,6 +10737,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9979,6 +10808,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -10607,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10629,7 +11468,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true self.ScanData.Units[ZoneObject]=ZoneObject end end -if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then +if ObjectCategory==Object.Category.SCENERY then local SceneryType=ZoneObject:getTypeName() local SceneryName=ZoneObject:getName() self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} @@ -11392,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -12071,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -12385,6 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -12426,6 +13283,20 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +end +return self +end function SET_BASE:_Find(ObjectName) local ObjectFound=self.Set[ObjectName] return ObjectFound @@ -12738,7 +13609,8 @@ return true end function SET_BASE:IsInSet(Object) local outcome=false -local name=Object:GetName() +if Object==nil then return false end +local name=(Object~=nil and Object.GetName)and Object:GetName()or"none" self:ForEach( function(object) if object:GetName()==name then @@ -12909,16 +13781,6 @@ self.Filter.Zones[zonename]=Zone end return self end -function SET_GROUP:FilterCoalitions(Coalitions,Clear) -if Clear or(not self.Filter.Coalitions)then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_GROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -13373,16 +14235,6 @@ function SET_UNIT:FindUnit(UnitName) local UnitFound=self.Set[UnitName] return UnitFound end -function SET_UNIT:FilterCoalitions(Coalitions) -self.Filter.Coalitions={} -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_UNIT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14027,18 +14879,6 @@ 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={} @@ -14465,18 +15305,6 @@ self.Filter.Playernames[playername]=playername end return self end -function SET_CLIENT: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_CLIENT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14650,8 +15478,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -14866,18 +15693,6 @@ function SET_PLAYER:FindClient(PlayerName) local ClientFound=self.Set[PlayerName] return ClientFound end -function SET_PLAYER: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_PLAYER:FilterZones(Zones) if not self.Filter.Zones then self.Filter.Zones={} @@ -15131,18 +15946,6 @@ function SET_AIRBASE:GetRandomAirbase() local RandomAirbase=self:GetRandom() return RandomAirbase end -function SET_AIRBASE: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_AIRBASE:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -15303,18 +16106,6 @@ function SET_CARGO:FindCargo(CargoName) local CargoFound=self.Set[CargoName] return CargoFound end -function SET_CARGO: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_CARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -15934,16 +16725,6 @@ self.Filter.Prefixes[Prefix]=Prefix end return self end -function SET_OPSZONE:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_OPSZONE:FilterOnce() for ObjectName,Object in pairs(self.Database)do self:Remove(ObjectName,true) @@ -16160,6 +16941,18 @@ self:Remove(RemoveGroupName) end return self end +function SET_OPSGROUP:CountAlive() +local CountG=0 +local CountU=0 +local Set=self:GetSet() +for GroupID,GroupData in pairs(Set)do +if GroupData and GroupData:IsAlive()then +CountG=CountG+1 +CountU=CountU+GroupData:GetGroup():CountAliveUnits() +end +end +return CountG,CountU +end function SET_OPSGROUP:FindGroup(GroupName) local GroupFound=self.Set[GroupName] return GroupFound @@ -16176,18 +16969,6 @@ function SET_OPSGROUP:FindNavyGroup(GroupName) local GroupFound=self:FindGroup(GroupName) return GroupFound end -function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear) -if Clear or 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_OPSGROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -16669,18 +17450,6 @@ DCargoInclude=DCargoInclude and MClientFunc end return DCargoInclude end -function SET_DYNAMICCARGO: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_DYNAMICCARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -17606,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -17814,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -18693,41 +19480,6 @@ return nil 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 -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 -end -do VELOCITY={ ClassName="VELOCITY", } @@ -20107,6 +20859,9 @@ SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-Curren end end if self.SpawnRandomizeUnits then +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -21032,6 +21787,11 @@ min=9 max=18 ctable=CALLSIGN.F15E end +if SpawnTemplate.units[1].type=="A6E"then +min=4 +max=18 +ctable=CALLSIGN.Intruder +end local callsignnr=math.random(min,max) local callsignname="Enfield" for name,value in pairs(ctable)do @@ -21547,6 +22307,18 @@ function SPAWNSTATIC:InitCargo(IsCargo) self.InitStaticCargo=IsCargo return self end +function SPAWNSTATIC:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWNSTATIC:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWNSTATIC:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead return self @@ -21640,6 +22412,15 @@ end if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end +if self.SpawnHiddenOnPlanner then +Template.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +Template.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +Template.hidden=self.SpawnHiddenOnMap +end if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -21705,7 +22486,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21713,8 +22494,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -21962,20 +22741,17 @@ MARKEROPS_BASE={ ClassName="MARKEROPS", Tag="mytag", Keywords={}, -version="0.1.4", +version="0.1.5", debug=false, Casesensitive=true, } -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) 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","*") @@ -22038,9 +22814,9 @@ end end function MARKEROPS_BASE:_MatchTag(Eventtext) local matches=false -if not self.Casesensitive then +if self.Casesensitive==false then local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then +if string.find(string.lower(Eventtext),type,1,true)then matches=true end else @@ -22076,6 +22852,14 @@ self:UnHandleEvent(EVENTS.MarkAdded) self:UnHandleEvent(EVENTS.MarkChange) self:UnHandleEvent(EVENTS.MarkRemoved) end +function MARKEROPS_BASE:SwitchCaseSensitiveOff() +self.Casesensitive=false +return self +end +function MARKEROPS_BASE:SwitchCaseSensitiveOn() +self.Casesensitive=true +return self +end TEXTANDSOUND={ ClassName="TEXTANDSOUND", version="0.0.1", @@ -22180,7 +22964,7 @@ ClassName="PATHLINE", lid=nil, points={}, } -PATHLINE.version="0.1.1" +PATHLINE.version="0.2.0" function PATHLINE:New(Name) local self=BASE:Inherit(self,BASE:New()) self.name=Name or"Unknown Path" @@ -22279,12 +23063,26 @@ return point.vec2 end return nil end +function PATHLINE:GetLength(Project2D) +local l=0 +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +if Project2D then +l=l+UTILS.VecDist2D(p1.vec2,p2.vec2) +else +l=l+UTILS.VecDist3D(p1.vec3,p2.vec3) +end +end +return l +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) +UTILS.RemoveMark(point.markerID) end else if point.markerID then @@ -22295,6 +23093,31 @@ local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m trigger.action.markToAll(point.markerID,text,point.vec3,"") end end +return self +end +function PATHLINE:DrawLine(Recipient,Color,LineType) +Recipient=Recipient or-1 +Color=Color or{1,0,0,1.0} +LineType=LineType or 1 +local ReadOnly=false +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +p1.lineID=UTILS.GetMarkID() +trigger.action.lineToAll(Recipient,p1.lineID,p1.vec3,p2.vec3,Color,LineType,ReadOnly,"") +end +return self +end +function PATHLINE:UnDrawLine(Delay) +local np=#self.points +for _,_point in pairs(self.points)do +local p=_point +if p.lineID then +UTILS.RemoveMark(p.lineID,Delay) +end +end +return self end function PATHLINE:_CreatePoint(Vec) local point={} @@ -22833,6 +23656,529 @@ end end return self end +VECTOR={ +ClassName="VECTOR", +verbose=0, +} +VECTOR.version="0.1.0" +_VECTORID=0 +VECTOR.__index=VECTOR +function VECTOR:New(x,y,z) +if z==nil then +self=setmetatable({x=x or 0,y=0,z=y or 0},VECTOR) +else +self=setmetatable({x=x or 0,y=y or 0,z=z or 0},VECTOR) +end +_VECTORID=_VECTORID+1 +self.uid=_VECTORID +return self +end +function VECTOR:NewFromVec(Vec) +local vector=VECTOR:New(Vec.x,Vec.y,Vec.z) +return vector +end +function VECTOR:NewFromPolar(r,phi) +local Phi=math.rad(phi) +local x=r*math.cos(phi) +local y=r*math.sin(phi) +local v=VECTOR:New(x,y) +return self +end +function VECTOR:NewFromSpherical(r,theta,phi) +local sinPhi=math.sin(math.rad(phi)) +local cosPhi=math.cos(math.rad(phi)) +local sinTheta=math.sin(math.rad(theta)) +local cosTheta=math.cos(math.rad(theta)) +local x=r*sinTheta*cosPhi +local y=r*sinTheta*sinPhi +local z=r*cosTheta +local v=VECTOR:New(x,y,z) +return self +end +function VECTOR:NewDirectionalVector(a,b) +local x=b.x-a.x +local y +local z +if a.z and b.z then +y=b.y-a.y +z=b.z-a.z +elseif b.z then +y=b.y-0 +z=b.z-a.y +elseif a.z then +y=0-a.y +z=b.y-a.z +else +y=b.y-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:NewFromLLDD(Latitude,Longitude,Altitude) +local vec3=coord.LLtoLO(Latitude,Longitude) +self=VECTOR:NewFromVec(vec3) +if Altitude then +self.y=Altitude +end +return self +end +function VECTOR:NewFromLLDMS(Latitude,Longitude,Altitude) +local lat=UTILS.LLDMSstringToDD(Latitude) +local lon=UTILS.LLDMSstringToDD(Longitude) +self=VECTOR:NewFromLLDD(lat,lon,Altitude) +return self +end +function VECTOR:GetVec2() +local vec={x=self.x,y=self.z} +return vec +end +function VECTOR:GetVec3(OnSurface) +local x=self.x +local y=OnSurface and land.getHeight({x=self.x,y=self.z})or self.y +local z=self.z +local vec={x=x,y=y,z=z} +return vec +end +function VECTOR:GetCoordinate(OnSurface) +local vec3=self:GetVec3(OnSurface) +local coordinate=COORDINATE:NewFromVec3(vec3) +return coordinate +end +function VECTOR:GetDistance(Vector,Only2D) +local dx=self.x-Vector.x +local dy=0 +local dz=0 +if Vector.z then +if not Only2D then +dy=self.y-Vector.y +end +dz=self.z-Vector.z +else +dy=0 +dz=self.z-Vector.y +end +local dist=math.sqrt(dx*dx+dy*dy+dz*dz) +return dist +end +function VECTOR:GetDirectionalVectorTo(a) +local x=a.x-self.x +local y=0 +local z=nil +if a.z then +y=a.y-self.y +z=a.z-self.z +else +y=a.y-self.z +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetDirectionalVectorFrom(a) +local x=self.x-a.x +local y +local z +if a.z then +y=self.y-a.y +z=self.z-a.z +else +y=self.z-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetLength() +local l=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) +return l +end +function VECTOR:GetHeading(To360) +local heading=math.atan2(self.z,self.x) +heading=math.deg(heading) +if To360==nil or To360==true then +heading=UTILS.AdjustHeading360(heading) +else +if heading==360.0 then +heading=0.0 +end +end +return heading +end +function VECTOR:GetHeadingTo(Vector) +local a=self:GetDirectionalVectorTo(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetHeadingFrom(Vector) +local a=self:GetDirectionalVectorFrom(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetLatitudeLongitude() +local vec3=self:GetVec3() +local latitude,longitude,altitude=coord.LOtoLL(vec3) +return latitude,longitude +end +function VECTOR:GetMGRS() +local lat,long=self:GetLatitudeLongitude() +local mgrs=coord.LLtoMGRS(lat,long) +return mgrs +end +function VECTOR:GetHeadingDelta(Vector) +local h1=self:GetHeading(false) +local h2=Vector:GetHeading(false) +local delta=h2-h1 +return delta +end +function VECTOR:GetIntermediateVector(Vector,Fraction) +local f=Fraction or 0.5 +local vec=self:GetDirectionalVectorTo(Vector) +local length=vec:GetLength() +vec:SetLength(f*length) +vec=self+vec +return vec +end +function VECTOR:SetLength(Length) +self:Normalize() +local v=self*Length +self:Replace(v) +return self +end +function VECTOR:SetX(x) +self.x=x or 0 +return self +end +function VECTOR:SetY(y) +if y==nil then +y=self:GetSurfaceHeight() +end +self.y=y +return self +end +function VECTOR:SetZ(z) +self.z=z or 0 +return self +end +function VECTOR:AddVec(Vec) +self.x=self.x+Vec.x +if Vec.z then +self.y=self.y+Vec.y +self.z=self.z+Vec.z +else +self.z=self.z+Vec.y +end +return self +end +function VECTOR:SubVec(Vec) +self.x=self.x-Vec.x +if Vec.z then +self.y=self.y-Vec.y +self.z=self.z-Vec.z +else +self.z=self.z-Vec.y +end +return self +end +function VECTOR:Dot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Rot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Copy() +local c=VECTOR:New(self.x,self.y,self.z) +return c +end +function VECTOR:Replace(Vector,Project2D) +self.x=Vector.x +if Vector.z then +self.y=Vector.y +self.z=Vector.z +else +if Project2D then +self.y=0 +end +self.z=Vector.y +end +return self +end +function VECTOR:Normalize() +local l=self:GetLength() +if l~=0 then +self:Replace(self/l) +end +return self +end +function VECTOR:Translate(Distance,Heading,Copy) +Distance=Distance or 1000 +local alpha=math.rad(Heading or 0) +local vector=Copy and self:Copy()or self +vector.x=Distance*math.cos(alpha)+vector.x +vector.z=Distance*math.sin(alpha)+vector.z +return vector +end +function VECTOR:Rotate2D(Angle,Copy) +local phi=-math.rad(Angle or 0) +local sinPhi=math.sin(phi) +local cosPhi=math.cos(phi) +local X=self.z +local Y=self.x +local z=X*cosPhi-Y*sinPhi +local x=X*sinPhi+Y*cosPhi +if Copy then +local vector=VECTOR:New(x,self.y,z) +return vector +else +self:SetX(x) +self:SetZ(z) +return self +end +end +function VECTOR: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) +local text="MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +return text +end +function VECTOR:GetSurfaceType() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +return s +end +function VECTOR:GetSurfaceTypeName() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +for name,id in land.SurfaceType()do +if id==s then +return name +end +end +return"unknown" +end +function VECTOR:IsVisible(Vec) +local vec1=self:GetVec3() +local vec2={x=Vec.x,Vec.y,Vec.z} +local los=land.isVisible(vec1,vec2) +return los +end +function VECTOR:GetClosestRoad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('roads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetClosestRailroad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('railroads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetPathOnRoad(Vec) +local vec1=self:GetVec2() +local vec2=Vec:GetVec2() +local vec2points=land.findPathOnRoads("roads",vec1.x,vec1.y,vec2.x,vec2.y) +local path=nil +if vec2points then +path=PATHLINE:NewFromVec2Array("Road",vec2points) +end +return path +end +function VECTOR:GetProfile(Vec3) +local vec3=self:GetVec3() +local vec3s=land.profile(vec3,Vec3) +local profile=nil +if vec3s then +profile=PATHLINE:NewFromVec3Array("Profile",vec3s) +end +return profile +end +function VECTOR:GetInterceptPoint(DirectionVector,Distance) +local vec3=self:GetVec3() +local ip3=land.getIP(vec3,DirectionVector,Distance or 1000) +local ipvector=nil +if ip3 then +ipvector=VECTOR:New(ip3.x,ip3.y,ip3.z) +end +return ipvector +end +function VECTOR:GetSurfaceHeight() +local vec2=self:GetVec2() +local h=land.getHeight(vec2) +return h +end +function VECTOR:GetSurfaceHeightAndDepth() +local vec2=self:GetVec2() +local h,d=land.getSurfaceHeightWithSeabed(vec2) +return h,d +end +function VECTOR:GetWindVector(WithTurbulence) +local vec3=self:GetVec3() +local wind=nil +if WithTurbulence then +wind=atmosphere.getWindWithTurbulence(vec3) +else +wind=atmosphere.getWind(vec3) +end +local vector=VECTOR:New(wind) +return vector +end +function VECTOR:GetTemperaturAndPressure() +local vec3=self:GetVec3() +local t,p=atmosphere.getTemperatureAndPressure(vec3) +return t,p +end +function VECTOR:Smoke(Color,Duration) +local vec3=self:GetVec3() +Color=Color or 0 +local name=string.format("Vector-Smoke-%d",self.uid) +trigger.action.smoke(vec3,Color,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -22932,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -23079,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23116,6 +24483,16 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetPointVec2() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23147,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -23154,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -23314,12 +24693,18 @@ end BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -23439,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -23934,6 +25329,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -26196,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -27852,33 +29288,45 @@ end function GROUP:GetTypeName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +end return nil end function GROUP:GetNatoReportingName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +end return"Bogey" end function GROUP:GetPlayerName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +end return nil end function GROUP:GetCallsign() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -27898,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -28549,7 +30006,7 @@ function GROUP:GetTaskMission() return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) end function GROUP:GetTaskRoute() -if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) else return{} @@ -28563,7 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -28585,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -28828,6 +30285,10 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +return self +end function GROUP:SetCommandInvisible(switch) return self:CommandSetInvisible(switch) end @@ -29954,6 +31415,20 @@ end function UNIT:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits=Enabled end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -30261,6 +31736,7 @@ self.Life0=Life0 else self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -30273,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -30338,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30348,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30357,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30446,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -30454,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -30476,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -30486,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -30504,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -30511,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -30524,18 +32028,15 @@ AIRBASE.Normandy={ ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -["Hawkinge"]="Hawkinge", } 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -30578,13 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -30600,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -30612,16 +32115,17 @@ AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", -["Marka"]="Marka", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -30638,17 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Ben_Gurion"]="Ben Gurion", -["Hatzor"]="Hatzor", -["Palmachim"]="Palmachim", -["Tel_Nof"]="Tel Nof", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -30656,8 +32157,7 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } -AIRBASE.MarianaIslandsWWII= -{ +AIRBASE.MarianaIslandsWWII={ ["Agana"]="Agana", ["Airfield_3"]="Airfield 3", ["Charon_Kanoa"]="Charon Kanoa", @@ -30720,6 +32220,7 @@ AIRBASE.Sinai={ ["Cairo_West"]="Cairo West", ["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["El_Minya"]="El Minya", @@ -30731,8 +32232,11 @@ AIRBASE.Sinai={ ["Inshas_Airbase"]="Inshas Airbase", ["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", ["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", ["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", @@ -30746,45 +32250,50 @@ AIRBASE.Sinai={ ["Sde_Dov"]="Sde Dov", ["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", ["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", ["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", ["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", ["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", ["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", ["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", ["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", ["Severomorsk_1"]="Severomorsk-1", ["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", ["Vidsel"]="Vidsel", ["Vuojarvi"]="Vuojarvi", -["Andoya"]="Andoya", -["Alakurtti"]="Alakurtti", -["Kittila"]="Kittila", -["Bardufoss"]="Bardufoss", -["Alta"]="Alta", -["Sodankyla"]="Sodankyla", -["Enontekio"]="Enontekio", -["Evenes"]="Evenes", -["Hosio"]="Hosio", -["Kilpyavr"]="Kilpyavr", -["Afrikanda"]="Afrikanda", -["Kalevala"]="Kalevala", -["Koshka_Yavr"]="Koshka Yavr", -["Poduzhemye"]="Poduzhemye", -["Luostari_Pechenga"]="Luostari Pechenga", } AIRBASE.Afghanistan={ ["Bagram"]="Bagram", @@ -30794,6 +32303,10 @@ AIRBASE.Afghanistan={ ["Camp_Bastion_Heliport"]="Camp Bastion Heliport", ["Chaghcharan"]="Chaghcharan", ["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", ["Farah"]="Farah", ["Gardez"]="Gardez", ["Ghazni_Heliport"]="Ghazni Heliport", @@ -30803,7 +32316,6 @@ AIRBASE.Afghanistan={ ["Kandahar"]="Kandahar", ["Kandahar_Heliport"]="Kandahar Heliport", ["Khost"]="Khost", -["Khost_Heliport"]="Khost Heliport", ["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", ["Nimroz"]="Nimroz", ["Qala_i_Naw"]="Qala i Naw", @@ -30814,21 +32326,28 @@ AIRBASE.Afghanistan={ ["Urgoon_Heliport"]="Urgoon Heliport", } AIRBASE.Iraq={ -["Baghdad_International_Airport"]="Baghdad International Airport", -["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", -["Al_Sahra_Airport"]="Al-Sahra Airport", -["Erbil_International_Airport"]="Erbil International Airport", -["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", ["Al_Salam_Airbase"]="Al-Salam Airbase", -["Balad_Airbase"]="Balad Airbase", -["Kirkuk_International_Airport"]="Kirkuk International Airport", -["Bashur_Airport"]="Bashur Airport", +["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Taquddum_Airport"]="Al-Taquddum Airport", -["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", ["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", } AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", ["Airracing_Frankfurt"]="Airracing Frankfurt", ["Airracing_Koblenz"]="Airracing Koblenz", ["Airracing_Luebeck"]="Airracing Lubeck", @@ -30839,16 +32358,23 @@ AIRBASE.GermanyCW={ ["Bienenfarm"]="Bienenfarm", ["Bindersleben"]="Bindersleben", ["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", ["Braunschweig"]="Braunschweig", ["Bremen"]="Bremen", ["Briest"]="Briest", ["Buechel"]="Buchel", ["Bueckeburg"]="Buckeburg", ["Celle"]="Celle", +["Chojna"]="Chojna", ["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", ["Damgarten"]="Damgarten", ["Dedelow"]="Dedelow", ["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", ["Fassberg"]="Fassberg", ["Finow"]="Finow", ["Frankfurt"]="Frankfurt", @@ -30859,7 +32385,7 @@ AIRBASE.GermanyCW={ ["Gatow"]="Gatow", ["Gelnhausen"]="Gelnhausen", ["Giebelstadt"]="Giebelstadt", -["Glindbruchkippe"]="Glindbruchkippe ", +["Glindbruchkippe"]="Glindbruchkippe", ["Gross_Mohrdorf"]="Gross Mohrdorf", ["Grosse_Wiese"]="Grosse Wiese", ["Guetersloh"]="Gutersloh", @@ -30977,43 +32503,60 @@ AIRBASE.GermanyCW={ ["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", ["Hannover"]="Hannover", ["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", ["Herrenteich"]="Herrenteich", ["Hildesheim"]="Hildesheim", ["Hockenheim"]="Hockenheim", ["Holzdorf"]="Holzdorf", ["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", ["Koethen"]="Kothen", ["Laage"]="Laage", +["Landstuhl"]="Landstuhl", ["Langenselbold"]="Langenselbold", ["Laerz"]="Larz", -["Leipzig_Halle"]="Leipzig Halle", ["Leipzig_Mockau"]="Leipzig Mockau", ["Luebeck"]="Lubeck", ["Lueneburg"]="Luneburg", ["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", ["Mendig"]="Mendig", ["Merseburg"]="Merseburg", ["Neubrandenburg"]="Neubrandenburg", ["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", ["Northeim"]="Northeim", +["Noervenich"]="Norvenich", ["Ober_Moerlen"]="Ober-Morlen", ["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", ["Parchim"]="Parchim", ["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", ["Pferdsfeld"]="Pferdsfeld", ["Pinnow"]="Pinnow", ["Pottschutthoehe"]="Pottschutthohe", ["Ramstein"]="Ramstein", +["Revinge"]="Revinge", ["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", ["Schoenefeld"]="Schonefeld", ["Schweinfurt"]="Schweinfurt", ["Sembach"]="Sembach", +["Sittensen"]="Sittensen", ["Spangdahlem"]="Spangdahlem", ["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", ["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", ["Tegel"]="Tegel", ["Tempelhof"]="Tempelhof", ["Templin"]="Templin", +["Thurland"]="Thurland", ["Tutow"]="Tutow", ["Uelzen"]="Uelzen", ["Uetersen"]="Uetersen", @@ -31029,6 +32572,7 @@ AIRBASE.GermanyCW={ ["Worms"]="Worms", ["Wunstorf"]="Wunstorf", ["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", ["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ @@ -31096,9 +32640,19 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -31345,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -32036,16 +33617,55 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end if self.SceneryObject and self.SceneryObject.getLife then -self.Life0=self.SceneryObject:getLife()or 0 +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) @@ -32064,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 +local life=1 if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -32104,14 +33746,16 @@ end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -32130,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -32154,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -32183,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -33036,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -33508,7 +35167,7 @@ self:SetLiquid(lqno,lqam) end end else -self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end if self:IsLimitedAircraft()then @@ -33526,7 +35185,7 @@ self:SetAmount(acname,acnumber) end end else -self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end if self:IsLimitedWeapons()then @@ -33555,7 +35214,7 @@ end end end else -self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end return self @@ -33626,6 +35285,9 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33634,8 +35296,26 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.1.0" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -33858,13 +35538,29 @@ end end self.LastPosition=pos else -if self.timer and self.timer:IsRunning()then self.timer:Stop()end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") self.CargoState=DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end return self end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end function DYNAMICCARGO._FilterHeloTypes(client) if not client then return false end local typename=client:GetTypeName() @@ -35200,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -35213,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -35235,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -35247,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -35274,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -35306,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -35313,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -35452,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -35562,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -35588,6 +37412,7 @@ local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -35601,7 +37426,6 @@ 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 @@ -35613,14 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName -if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then TargetCategory=Unit.Category.STRUCTURE TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") end 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) @@ -35629,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -35640,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -35712,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -35730,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -35819,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -35918,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -35935,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -36173,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -36196,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -36204,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -36272,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -36293,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -43849,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -43860,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -43886,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -43916,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -43939,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -43954,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -43963,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -44575,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -44607,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -44626,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -46820,7 +48690,7 @@ maxrange=7000, reloadtime=nil, }, } -ARTY.version="1.3.3" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -47500,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -51029,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -51326,7 +53220,6 @@ 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) @@ -51430,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -51632,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -52441,7 +54334,6 @@ 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 @@ -54931,7 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", -version="0.9.34", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -54982,6 +56874,9 @@ SmokeDecoyColor=SMOKECOLOR.White, checkcounter=1, DLinkCacheTime=120, logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -55027,10 +56922,14 @@ MANTIS.SamData={ ["SA-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, ["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"}, -["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, ["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, ["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, ["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, @@ -55109,13 +57008,14 @@ MANTIS.SamDataCH={ ["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} @@ -55227,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -55268,6 +57273,69 @@ self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -55557,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -55566,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -55579,7 +57661,7 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end if self.checkforfriendlies==true and self.friendlyset==nil then self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() @@ -55635,9 +57717,27 @@ self:T(self.lid.."Starting Intel Detection") local groupset=self.EWR_Group local samset=self.SAM_Group self.intelset={} -local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne") +local IntelOne=INTEL:New(groupset,self.coalition,self.name.." IntelOne") +IntelOne.DetectAccoustic=self.DetectAccoustic +IntelOne.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelOne.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelOne:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelOne:Start() -local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo") +local IntelTwo=INTEL:New(samset,self.coalition,self.name.." IntelTwo") +IntelTwo.DetectAccoustic=self.DetectAccoustic +IntelTwo.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelTwo.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelTwo:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelTwo:Start() local CacheTime=self.DLinkCacheTime or 120 local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) @@ -55893,6 +57993,22 @@ self:T(self.lid.."RemoveShorad") self.ShoradLink=false return self end +function MANTIS:_SmokeUnits(Group) +self:T("Smoking") +local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME")or 0 +local TNow=timer.getTime() +if TNow-LastSmoketime>290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 @@ -55906,7 +58022,7 @@ local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 -local shortsam=(_data[6]==MANTIS.SamType.SHORT)and true or false +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false if not shortsam then shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false end @@ -55931,17 +58047,10 @@ switch=true end if self.SamStateTracker[name]~="RED"and switch then self:__RedState(1,samgroup) -self.SamStateTracker[name]="RED" end -if shortsam==true and self.SmokeDecoy==true then +if shortsam==true and self.SmokeDecoy==true and 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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -60798,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -62764,7 +65158,7 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then @@ -62784,13 +65178,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -62820,7 +65214,7 @@ return aoa end function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -62830,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -63463,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -63826,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -64092,7 +66487,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -64114,6 +66514,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -64856,77 +67278,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -65214,19 +67568,19 @@ end if 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" +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" +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)" +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)" +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 @@ -65841,7 +68195,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true) text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) local unitClient=Unit.getByName(unit:GetName()) local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early if hornet then local nozzlePosL=0 local burnerPosL=unitClient:getDrawArgumentValue(28) @@ -65957,7 +68311,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89) else nozzlePosL=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosL=unitClient:getDrawArgumentValue(434) end return nozzlePosL @@ -65974,7 +68328,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90) else nozzlePosR=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosR=unitClient:getDrawArgumentValue(433) end return nozzlePosR @@ -66025,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -66126,7 +68478,7 @@ 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=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -66317,7 +68669,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -66332,6 +68684,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66439,14 +68792,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66494,6 +68841,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66510,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -67557,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -70463,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -70509,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -70707,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -70726,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -70736,14 +73093,12 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) return self @@ -72802,6 +75157,9 @@ self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false self.ResourceMap=nil self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end self.StaticShape=nil self.TypeNames=nil self.StaticCategory="Cargos" @@ -72809,6 +75167,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -72865,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -72890,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -73181,7 +75544,9 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, @@ -73196,8 +75561,9 @@ CTLD.FixedWingTypes={ ["Hercules"]="Hercules", ["Bronco"]="Bronco", ["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", } -CTLD.version="1.3.37" +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -73240,12 +75606,15 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") self:AddTransition("*","CratesPacked","*") self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") @@ -73286,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -73299,7 +75670,10 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false self.enableFixedWing=false @@ -73326,6 +75700,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -73338,6 +75713,8 @@ self.filename=string.format("CTLD_%s_Persist.csv",AliaS) self.allowcratepickupagain=true self.enableslingload=false self.basetype="container_cargo" +self.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do @@ -73418,6 +75795,15 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end if _unit:IsHelicopter()or _group:IsHelicopter()then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil @@ -73453,6 +75839,7 @@ end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end elseif event.id==EVENTS.NewDynamicCargo then self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo @@ -73478,6 +75865,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73514,6 +75902,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73521,6 +75910,13 @@ self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -73548,6 +75944,12 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end function CTLD:AddAllowedFixedWingType(typename) @@ -73697,6 +76099,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -73792,7 +76195,7 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) local hassecondaries=false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!",10,false,Group) @@ -73921,18 +76324,340 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -73961,7 +76686,7 @@ 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 +if not self.debug then return false end end end end @@ -73971,13 +76696,16 @@ local loaddist=self.CrateDistance or 35 local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false +end +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -73992,6 +76720,7 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 @@ -74034,8 +76763,12 @@ self.CrateCounter=self.CrateCounter+1 local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() local basetype=CType or self.basetype or"container_cargo" CCat=CCat or"Cargos" -if isstatic then -basetype=cratetemplate +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -74081,8 +76814,8 @@ 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) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -74094,23 +76827,35 @@ realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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) else +if not quiet then self:_SendMessage(text,10,false,Group) end +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -74209,6 +76954,52 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis end return self end +function CTLD:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +end +return self +end function CTLD:_RemoveCratesNearby(_group,_unit) self:T(self.lid.." _RemoveCratesNearby") local finddist=self.CrateDistance or 35 @@ -74221,8 +77012,10 @@ for _,_entry in pairs(crates)do local entry=_entry local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -if entry:GetPositionable()then -entry:GetPositionable():Destroy(false) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end table.insert(removedIDs,entry:GetID()) end @@ -74231,8 +77024,17 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -74258,7 +77060,7 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() @@ -74294,8 +77096,16 @@ local cando=cargo:UnitCanCarry(_unit) if ignoretype==true then cando=true end self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distance0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -74527,6 +77347,18 @@ end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -74670,7 +77502,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -74766,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74776,6 +77611,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74864,6 +77700,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74873,6 +77710,9 @@ end end return self end +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then @@ -74889,11 +77729,19 @@ self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end -local finddist=self.CrateDistance or 35 +local baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -74903,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -74925,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -74953,19 +77812,55 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end self:__CratesBuildStarted(1,Group,Unit,build.Name) -self:_RefreshDropTroopsMenu(Group,Unit) else self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end +end end end end @@ -74978,24 +77873,32 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) self:_RefreshLoadCratesMenu(Group,Unit) self:__CratesPacked(1,Group,Unit,_entry) -return true +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end +if not packedAny then self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end +return true +end function CTLD:_RepairCrates(Group,Unit,Engineering) self:T(self.lid.." _RepairCrates") local finddist=self.CrateDistance or 35 @@ -75163,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -75210,13 +78114,57 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -75225,6 +78173,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -75349,85 +78530,112 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu,subcatmenus) +if cargoObj.DontShowInMenu then +return +end +local isStaticCargo=false +if(cargoObj.GetType and cargoObj:GetType()==CTLD_CARGO.Enum.STATIC)or cargoObj.CargoType==CTLD_CARGO.Enum.STATIC then +isStaticCargo=true +end +if isStaticCargo and cargoObj.UnitCanCarry and not cargoObj:UnitCanCarry(_unit)then +return +end +local parent=parentMenu +if subcatmenus and cargoObj.Subcategory then +parent=subcatmenus[cargoObj.Subcategory] +if not parent then +parent=MENU_GROUP:New(_group,cargoObj.Subcategory,cratesmenu) +subcatmenus[cargoObj.Subcategory]=parent +end +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for catName,_ in pairs(self.subcats)do -subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) -end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} -for catName,_ in pairs(self.subcats)do +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end +return subcatmenus[catName] +end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75435,8 +78643,14 @@ MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,c end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75485,6 +78699,43 @@ MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,sel end end end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end +else +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) @@ -75507,6 +78758,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75518,6 +78771,10 @@ end function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then @@ -75551,7 +78808,7 @@ lineIndex=lineIndex+1 end end end -function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) local grounded=not self:IsUnitInAir(Unit) local hover=self:CanHoverLoad(Unit) @@ -75582,18 +78839,23 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) local capacity=capabilities.cratelimit or 0 if loadedData.Cratesloaded>=capacity then self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress return self end local spaceLeft=capacity-loadedData.Cratesloaded local toLoad=math.min(found,needed,spaceLeft) if toLoad<1 then self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress return self end local crateIDsLoaded={} @@ -75626,6 +78888,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if details or(not batch)then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75643,8 +78906,29 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75738,6 +79022,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -75781,29 +79066,62 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75820,33 +79138,98 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) -MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then -MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) -end i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75860,7 +79243,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end 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) @@ -75968,6 +79351,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -75998,6 +79382,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -76070,7 +79455,45 @@ table.insert(self.Cargo_Troops,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnits") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Units for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock or nil +entry.Stock0=Stock or nil +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=true +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddUnitsNoMove(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnitsNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."UnitsNoMove for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock +entry.Stock0=Stock +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=false +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) self:T(self.lid.." AddCratesCargo") if not self:_CheckTemplates(Templates)then self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") @@ -76085,11 +79508,41 @@ cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end +cargo.C130TypeName=C130TypeName table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) +self:T(self.lid.." AddCratesCargoNoMove") +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) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +cargo.C130TypeName=C130TypeName +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 local type=CTLD_CARGO.Enum.STATIC @@ -76100,6 +79553,9 @@ if unittemplate and unittemplate.resourcePayload then ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) end local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end @@ -76241,7 +79697,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -76264,9 +79725,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -76600,6 +80097,7 @@ return self end function CTLD:IsCorrectHover(Unit) self:T(self.lid.." IsCorrectHover") +if self:IsFixedWing(Unit)then return false end local outcome=false if self:IsUnitInAir(Unit)then local uspeed=Unit:GetVelocityMPS() @@ -76777,6 +80275,23 @@ Troopstable[genname].GenericCargo=generic end end end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end for _id,_cargo in pairs(self.Cargo_Troops)do local generic=_cargo local genname=generic:GetName() @@ -76807,10 +80322,31 @@ Troopstable[genname].Infield=Troopstable[genname].Infield+1 Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo end else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) end end end +end for _unitname,_loaded in pairs(self.Loaded_Cargo)do local _unit=UNIT:FindByName(_unitname) if _unit and _unit:IsAlive()then @@ -76819,12 +80355,12 @@ local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} for _,_cgo in pairs(loadedcargo)do local cargo=_cgo local type=cargo.CargoType -local gname=cargo.Name +local gname=cargo:GetName() local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) -self:T("Looking at "..gname.." in the helo - type = "..type) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then if gcargo and gcargo:GetStock0()>0 then -self:T("Adding "..gname.." in the helo - type = "..type) +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 end @@ -76892,6 +80428,47 @@ end end return Troopstable end +function CTLD:AddStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock+number +end +break +end +end +return self +end +function CTLD:SetStockUnits(Name,Number) +local name=Name or"none" +local number=Number +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +if number==nil or number==-1 then +_unit.Stock=-1 +else +_unit.Stock=number +end +break +end +end +return self +end +function CTLD:GetStockUnits() +local Stock={} +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +Stock[_unit.Name]=_unit.Stock or-1 +end +return Stock +end function CTLD:AddStockTroops(Name,Number) local name=Name or"none" local number=Number or 1 @@ -76899,6 +80476,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76911,6 +80489,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76923,6 +80502,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76935,6 +80515,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76947,6 +80528,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76959,6 +80541,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -77009,6 +80592,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -77020,6 +80604,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -77031,6 +80616,27 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() +end +end +return self +end +function CTLD:RemoveStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock-number +if _unit.Stock<0 then +_unit.Stock=0 +end +end +break end end return self @@ -77480,11 +81086,44 @@ 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) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +for _,_cargo in pairs(Cargotable)do +local Vehicle=_cargo.Positionable +if Vehicle then +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 +end +end +return self +end +function CTLD:OnAfterGetCrates(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:OnAfterRemoveCratesNearby(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) return self end function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) @@ -77512,9 +81151,13 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -77761,11 +81404,22 @@ 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) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +if self.C130GetUnits then +for _,_unit in pairs(self.C130GetUnits)do +if _unit.Name==cargoname then +if type(_unit.Stock)=="number"and _unit.Stock~=-1 then +_unit.Stock0=_unit.Stock0 or _unit.Stock +_unit.Stock=math.max(0,(_unit.Stock or 0)-1) +end +break +end +end +end 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,timestamp) end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +elseif self.loadSavedCrates and(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic=nil if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then @@ -77777,8 +81431,12 @@ injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) 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) injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) -local map=cargotype:GetStaticResourceMap() -injectstatic:SetStaticResourceMap(map) +local unittemplate=_DATABASE:GetStaticUnitTemplate(cargoname) +local ResourceMap=nil +if unittemplate and unittemplate.resourcePayload then +ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) +end +injectstatic:SetStaticResourceMap(ResourceMap) end if injectstatic then self:InjectStatics(dropzone,injectstatic,false,true) @@ -78247,6 +81905,8 @@ CreateRadioBeacons=true, UserSetGroup=nil, AllowIRStrobe=false, IRStrobeRuntime=300, +FARPRescueDistance=500, +EnableMenuSmokeMASH=true, } CSAR.AircraftType={} CSAR.AircraftType["SA342Mistral"]=2 @@ -78260,13 +81920,14 @@ CSAR.AircraftType["Mi-24P"]=8 CSAR.AircraftType["Mi-24V"]=8 CSAR.AircraftType["Bell-47"]=2 CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 CSAR.AircraftType["AH-64D_BLK_II"]=2 CSAR.AircraftType["Bronco-OV-10A"]=2 CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.35" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -78346,12 +82007,13 @@ self.loadtimemax=135 self.radioSound="beacon.ogg" self.beaconRefresher=29 self.allowFARPRescue=true -self.FARPRescueDistance=1000 +self.FARPRescueDistance=500 self.max_units=6 self.useprefix=true self.csarPrefix={"helicargo","MEDEVAC"} self.template=Template or"generic" self.mashprefix={"MASH"} +self.EnableMenuSmokeMASH=true self.autosmoke=false self.autosmokedistance=2000 self.limitmaxdownedpilots=true @@ -78529,6 +82191,9 @@ BeaconName=_unitName..math.random(1,10000) else BeaconName="Ghost-1-1"..math.random(1,10000) end +if _playerName==nil or _playerName==""then +_playerName="AI MIA" +end if(_freq and _freq~=0)then self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) end @@ -78712,11 +82377,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78771,7 +82436,7 @@ end self.takenOff[_event.IniUnitName]=nil local _place=_event.Place if _place==nil then -self:T(self.lid.." Landing Place Nil") +self:T(self.lid.." Landing Place nil") return self end if self.inTransitGroups[_event.IniUnitName]==nil then @@ -78779,7 +82444,10 @@ 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) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -78796,7 +82464,7 @@ 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:T({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") Unit.destroy(_event.initiator) end @@ -79134,7 +82802,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -79152,7 +82820,13 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_distself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end end for _,_mashes in pairs(MashSets)do for _,_mashUnit in pairs(_mashes or{})do @@ -79472,11 +83186,12 @@ _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance,MashName +return _shortestDistance,MashName,Coordinate else return-1 end @@ -79531,6 +83246,9 @@ local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -79545,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -79616,7 +83335,7 @@ local name=_radioUnit:GetName() local Frequency=_freq local Sound="l10n/DEFAULT/"..self.radioSound local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end @@ -79926,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -79935,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -80005,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -80081,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -81010,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -81351,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -81465,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -81743,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -81766,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -81813,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -82322,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -82434,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -82442,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -82476,6 +86216,20 @@ mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.mi mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -82611,33 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) elseif MissionType==AUFTRAG.Type.STRAFING then -mission=self:NewSTRAFING(Target,Altitude) +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -82698,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -83261,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -83465,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -83711,12 +87476,18 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 -if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) return false end return true @@ -83981,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -84523,6 +88294,15 @@ param.action="Wedge" param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 +DCStask.params=param +table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then local DCStask={} DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY @@ -84783,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.72", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -85400,6 +89180,11 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end function AWACS:SetMaxMissionRange(NM) self.MaxMissionRange=NM or 125 return self @@ -85659,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -85672,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -87150,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -87183,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -87208,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -87300,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -87843,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -88486,24 +92324,36 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) @@ -89518,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -89547,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.1" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -90577,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -91014,8 +94925,11 @@ end end return self end -function COHORT:RemoveAssets(N) +function COHORT:RemoveAssets(N,Delay) self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else N=N or 1 local n=0 for i=#self.assets,1,-1 do @@ -91023,6 +94937,7 @@ local asset=self.assets[i] self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) if not(asset.requested or asset.spawned or asset.isReserved)then self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) table.remove(self.assets,i) n=n+1 else @@ -91033,6 +94948,7 @@ break end end self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end return self end function COHORT:GetName() @@ -96967,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -97191,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -97198,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -97251,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -97258,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -97290,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -97456,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -97497,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -97711,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -98560,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98615,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -99106,7 +103124,7 @@ text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsR for _,mtype in pairs(AUFTRAG.Type)do local n=self:CountMissionsInQueue(mtype) if n>0 then -local N=self:CountMissionsInQueue(mtype) +local N=self:CountMissionsInQueue(mtype,true) text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) end end @@ -99201,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -99963,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.3" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -100330,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -100554,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -100562,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -100578,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -100596,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -102626,6 +106648,12 @@ end function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +function OPSGROUP:IsOutOfA2GAmmo() +if(self.outofMissilesAG and self.outofBombs and self.outofGuns)or self.outofAmmo then +return true +end +return false +end function OPSGROUP:IsLasing() return self.spot.On end @@ -103405,14 +107433,32 @@ end function OPSGROUP:_UpdateTask(Task,Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +if Mission.type==AUFTRAG.Type.RESCUEHELO then +self:T("**********") +self:T("** RESCUEHELO USED") +self:T("**********") +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +local helogroupname=self:GetGroup():GetName() +Task.formation=RESCUEHELO:New(followUnit,helogroupname) +Task.formation:SetRespawnOnOff(false) +Task.formation.respawninair=false +Task.formation:SetTakeoffCold() +Task.formation:SetHomeBase(followUnit) +Task.formation.helo=self:GetGroup() +Task.formation:Start() +if self:IsFlightgroup()then +self:SetDespawnAfterLanding() +end +else 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=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION) 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() +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then local zone=Task.dcstask.params.zone local surfacetypes=nil @@ -103496,6 +107542,15 @@ end if target then self:EngageTarget(target,speed,Task.dcstask.params.formation) end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT 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=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.altitude) +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then if self.isFlightgroup then self:T("We are Special Auftrag Patrol Race Track, starting now ...") @@ -103555,7 +107610,12 @@ 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())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -103603,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -103679,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -104059,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -104339,10 +108399,12 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then @@ -104922,7 +108984,7 @@ 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:T(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() @@ -105069,7 +109131,6 @@ function OPSGROUP:_Spawn(Delay,Template) if Delay and Delay>0 then self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) else -self:T2({Template=Template}) if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then UTILS.ValidateAndRepositionGroundUnits(Template.units) end @@ -105137,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -105195,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -106710,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -107478,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -107843,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -107858,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -107878,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -107908,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -107953,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -110002,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -110129,6 +114211,13 @@ end end) return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -110165,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110562,10 +114661,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -110608,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -110668,6 +114770,12 @@ Scoring=nil, MenuNoTask=nil, InformationMenu=false, TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -110696,6 +114804,12 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.CAP]=100, [AUFTRAG.Type.CAPTUREZONE]=100, } +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, +} PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, AAA=GROUP.Attribute.GROUND_AAA, @@ -110724,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -110808,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -110871,7 +114991,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -110942,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -111297,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -111849,17 +115982,24 @@ end PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end +self:__TaskAdded(10,PlayerTask,Silent) else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end return self end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) self:T({Force,Group,Client}) self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end local force=false if type(Force)=="boolean"then force=Force @@ -111944,7 +116084,7 @@ 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 +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) @@ -111987,8 +116127,9 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -112004,7 +116145,12 @@ 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) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -112106,8 +116252,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -112160,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -112169,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -112577,6 +116738,49 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -112708,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -116174,6 +120524,33 @@ self.ConflictZoneSet:AddZone(Zone) self.GoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) self:T("_TryAssignIntercept for size "..WingSize or 1) local assigned=false @@ -116298,6 +120675,8 @@ end if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then success=false end +else +success=true end return success end, @@ -116306,6 +120685,13 @@ nogozoneset, conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -116331,6 +120717,12 @@ BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -116403,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -116413,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -116446,6 +120839,477 @@ _wing:Stop() end return self end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) +return self +end AI_BALANCER={ ClassName="AI_BALANCER", PatrolZones={}, @@ -132120,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -3303,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3860,7 +4407,57 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +4475,61 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4558,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -4417,6 +5045,185 @@ end end return nil end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -7703,7 +8510,7 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName=Event.TgtDCSUnitName Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) @@ -7726,10 +8533,12 @@ 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -9543,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -9553,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9600,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9918,6 +10737,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9979,6 +10808,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -10607,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10629,7 +11468,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true self.ScanData.Units[ZoneObject]=ZoneObject end end -if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then +if ObjectCategory==Object.Category.SCENERY then local SceneryType=ZoneObject:getTypeName() local SceneryName=ZoneObject:getName() self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} @@ -11392,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -12071,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -12385,6 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -12426,6 +13283,20 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +end +return self +end function SET_BASE:_Find(ObjectName) local ObjectFound=self.Set[ObjectName] return ObjectFound @@ -12738,7 +13609,8 @@ return true end function SET_BASE:IsInSet(Object) local outcome=false -local name=Object:GetName() +if Object==nil then return false end +local name=(Object~=nil and Object.GetName)and Object:GetName()or"none" self:ForEach( function(object) if object:GetName()==name then @@ -12909,16 +13781,6 @@ self.Filter.Zones[zonename]=Zone end return self end -function SET_GROUP:FilterCoalitions(Coalitions,Clear) -if Clear or(not self.Filter.Coalitions)then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_GROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -13373,16 +14235,6 @@ function SET_UNIT:FindUnit(UnitName) local UnitFound=self.Set[UnitName] return UnitFound end -function SET_UNIT:FilterCoalitions(Coalitions) -self.Filter.Coalitions={} -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_UNIT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14027,18 +14879,6 @@ 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={} @@ -14465,18 +15305,6 @@ self.Filter.Playernames[playername]=playername end return self end -function SET_CLIENT: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_CLIENT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14650,8 +15478,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -14866,18 +15693,6 @@ function SET_PLAYER:FindClient(PlayerName) local ClientFound=self.Set[PlayerName] return ClientFound end -function SET_PLAYER: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_PLAYER:FilterZones(Zones) if not self.Filter.Zones then self.Filter.Zones={} @@ -15131,18 +15946,6 @@ function SET_AIRBASE:GetRandomAirbase() local RandomAirbase=self:GetRandom() return RandomAirbase end -function SET_AIRBASE: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_AIRBASE:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -15303,18 +16106,6 @@ function SET_CARGO:FindCargo(CargoName) local CargoFound=self.Set[CargoName] return CargoFound end -function SET_CARGO: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_CARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -15934,16 +16725,6 @@ self.Filter.Prefixes[Prefix]=Prefix end return self end -function SET_OPSZONE:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_OPSZONE:FilterOnce() for ObjectName,Object in pairs(self.Database)do self:Remove(ObjectName,true) @@ -16160,6 +16941,18 @@ self:Remove(RemoveGroupName) end return self end +function SET_OPSGROUP:CountAlive() +local CountG=0 +local CountU=0 +local Set=self:GetSet() +for GroupID,GroupData in pairs(Set)do +if GroupData and GroupData:IsAlive()then +CountG=CountG+1 +CountU=CountU+GroupData:GetGroup():CountAliveUnits() +end +end +return CountG,CountU +end function SET_OPSGROUP:FindGroup(GroupName) local GroupFound=self.Set[GroupName] return GroupFound @@ -16176,18 +16969,6 @@ function SET_OPSGROUP:FindNavyGroup(GroupName) local GroupFound=self:FindGroup(GroupName) return GroupFound end -function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear) -if Clear or 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_OPSGROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -16669,18 +17450,6 @@ DCargoInclude=DCargoInclude and MClientFunc end return DCargoInclude end -function SET_DYNAMICCARGO: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_DYNAMICCARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -17606,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -17814,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -18693,41 +19480,6 @@ return nil 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 -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 -end -do VELOCITY={ ClassName="VELOCITY", } @@ -20107,6 +20859,9 @@ SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-Curren end end if self.SpawnRandomizeUnits then +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -21032,6 +21787,11 @@ min=9 max=18 ctable=CALLSIGN.F15E end +if SpawnTemplate.units[1].type=="A6E"then +min=4 +max=18 +ctable=CALLSIGN.Intruder +end local callsignnr=math.random(min,max) local callsignname="Enfield" for name,value in pairs(ctable)do @@ -21547,6 +22307,18 @@ function SPAWNSTATIC:InitCargo(IsCargo) self.InitStaticCargo=IsCargo return self end +function SPAWNSTATIC:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWNSTATIC:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWNSTATIC:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead return self @@ -21640,6 +22412,15 @@ end if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end +if self.SpawnHiddenOnPlanner then +Template.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +Template.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +Template.hidden=self.SpawnHiddenOnMap +end if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -21705,7 +22486,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21713,8 +22494,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -21962,20 +22741,17 @@ MARKEROPS_BASE={ ClassName="MARKEROPS", Tag="mytag", Keywords={}, -version="0.1.4", +version="0.1.5", debug=false, Casesensitive=true, } -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) 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","*") @@ -22038,9 +22814,9 @@ end end function MARKEROPS_BASE:_MatchTag(Eventtext) local matches=false -if not self.Casesensitive then +if self.Casesensitive==false then local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then +if string.find(string.lower(Eventtext),type,1,true)then matches=true end else @@ -22076,6 +22852,14 @@ self:UnHandleEvent(EVENTS.MarkAdded) self:UnHandleEvent(EVENTS.MarkChange) self:UnHandleEvent(EVENTS.MarkRemoved) end +function MARKEROPS_BASE:SwitchCaseSensitiveOff() +self.Casesensitive=false +return self +end +function MARKEROPS_BASE:SwitchCaseSensitiveOn() +self.Casesensitive=true +return self +end TEXTANDSOUND={ ClassName="TEXTANDSOUND", version="0.0.1", @@ -22180,7 +22964,7 @@ ClassName="PATHLINE", lid=nil, points={}, } -PATHLINE.version="0.1.1" +PATHLINE.version="0.2.0" function PATHLINE:New(Name) local self=BASE:Inherit(self,BASE:New()) self.name=Name or"Unknown Path" @@ -22279,12 +23063,26 @@ return point.vec2 end return nil end +function PATHLINE:GetLength(Project2D) +local l=0 +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +if Project2D then +l=l+UTILS.VecDist2D(p1.vec2,p2.vec2) +else +l=l+UTILS.VecDist3D(p1.vec3,p2.vec3) +end +end +return l +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) +UTILS.RemoveMark(point.markerID) end else if point.markerID then @@ -22295,6 +23093,31 @@ local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m trigger.action.markToAll(point.markerID,text,point.vec3,"") end end +return self +end +function PATHLINE:DrawLine(Recipient,Color,LineType) +Recipient=Recipient or-1 +Color=Color or{1,0,0,1.0} +LineType=LineType or 1 +local ReadOnly=false +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +p1.lineID=UTILS.GetMarkID() +trigger.action.lineToAll(Recipient,p1.lineID,p1.vec3,p2.vec3,Color,LineType,ReadOnly,"") +end +return self +end +function PATHLINE:UnDrawLine(Delay) +local np=#self.points +for _,_point in pairs(self.points)do +local p=_point +if p.lineID then +UTILS.RemoveMark(p.lineID,Delay) +end +end +return self end function PATHLINE:_CreatePoint(Vec) local point={} @@ -22833,6 +23656,529 @@ end end return self end +VECTOR={ +ClassName="VECTOR", +verbose=0, +} +VECTOR.version="0.1.0" +_VECTORID=0 +VECTOR.__index=VECTOR +function VECTOR:New(x,y,z) +if z==nil then +self=setmetatable({x=x or 0,y=0,z=y or 0},VECTOR) +else +self=setmetatable({x=x or 0,y=y or 0,z=z or 0},VECTOR) +end +_VECTORID=_VECTORID+1 +self.uid=_VECTORID +return self +end +function VECTOR:NewFromVec(Vec) +local vector=VECTOR:New(Vec.x,Vec.y,Vec.z) +return vector +end +function VECTOR:NewFromPolar(r,phi) +local Phi=math.rad(phi) +local x=r*math.cos(phi) +local y=r*math.sin(phi) +local v=VECTOR:New(x,y) +return self +end +function VECTOR:NewFromSpherical(r,theta,phi) +local sinPhi=math.sin(math.rad(phi)) +local cosPhi=math.cos(math.rad(phi)) +local sinTheta=math.sin(math.rad(theta)) +local cosTheta=math.cos(math.rad(theta)) +local x=r*sinTheta*cosPhi +local y=r*sinTheta*sinPhi +local z=r*cosTheta +local v=VECTOR:New(x,y,z) +return self +end +function VECTOR:NewDirectionalVector(a,b) +local x=b.x-a.x +local y +local z +if a.z and b.z then +y=b.y-a.y +z=b.z-a.z +elseif b.z then +y=b.y-0 +z=b.z-a.y +elseif a.z then +y=0-a.y +z=b.y-a.z +else +y=b.y-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:NewFromLLDD(Latitude,Longitude,Altitude) +local vec3=coord.LLtoLO(Latitude,Longitude) +self=VECTOR:NewFromVec(vec3) +if Altitude then +self.y=Altitude +end +return self +end +function VECTOR:NewFromLLDMS(Latitude,Longitude,Altitude) +local lat=UTILS.LLDMSstringToDD(Latitude) +local lon=UTILS.LLDMSstringToDD(Longitude) +self=VECTOR:NewFromLLDD(lat,lon,Altitude) +return self +end +function VECTOR:GetVec2() +local vec={x=self.x,y=self.z} +return vec +end +function VECTOR:GetVec3(OnSurface) +local x=self.x +local y=OnSurface and land.getHeight({x=self.x,y=self.z})or self.y +local z=self.z +local vec={x=x,y=y,z=z} +return vec +end +function VECTOR:GetCoordinate(OnSurface) +local vec3=self:GetVec3(OnSurface) +local coordinate=COORDINATE:NewFromVec3(vec3) +return coordinate +end +function VECTOR:GetDistance(Vector,Only2D) +local dx=self.x-Vector.x +local dy=0 +local dz=0 +if Vector.z then +if not Only2D then +dy=self.y-Vector.y +end +dz=self.z-Vector.z +else +dy=0 +dz=self.z-Vector.y +end +local dist=math.sqrt(dx*dx+dy*dy+dz*dz) +return dist +end +function VECTOR:GetDirectionalVectorTo(a) +local x=a.x-self.x +local y=0 +local z=nil +if a.z then +y=a.y-self.y +z=a.z-self.z +else +y=a.y-self.z +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetDirectionalVectorFrom(a) +local x=self.x-a.x +local y +local z +if a.z then +y=self.y-a.y +z=self.z-a.z +else +y=self.z-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetLength() +local l=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) +return l +end +function VECTOR:GetHeading(To360) +local heading=math.atan2(self.z,self.x) +heading=math.deg(heading) +if To360==nil or To360==true then +heading=UTILS.AdjustHeading360(heading) +else +if heading==360.0 then +heading=0.0 +end +end +return heading +end +function VECTOR:GetHeadingTo(Vector) +local a=self:GetDirectionalVectorTo(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetHeadingFrom(Vector) +local a=self:GetDirectionalVectorFrom(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetLatitudeLongitude() +local vec3=self:GetVec3() +local latitude,longitude,altitude=coord.LOtoLL(vec3) +return latitude,longitude +end +function VECTOR:GetMGRS() +local lat,long=self:GetLatitudeLongitude() +local mgrs=coord.LLtoMGRS(lat,long) +return mgrs +end +function VECTOR:GetHeadingDelta(Vector) +local h1=self:GetHeading(false) +local h2=Vector:GetHeading(false) +local delta=h2-h1 +return delta +end +function VECTOR:GetIntermediateVector(Vector,Fraction) +local f=Fraction or 0.5 +local vec=self:GetDirectionalVectorTo(Vector) +local length=vec:GetLength() +vec:SetLength(f*length) +vec=self+vec +return vec +end +function VECTOR:SetLength(Length) +self:Normalize() +local v=self*Length +self:Replace(v) +return self +end +function VECTOR:SetX(x) +self.x=x or 0 +return self +end +function VECTOR:SetY(y) +if y==nil then +y=self:GetSurfaceHeight() +end +self.y=y +return self +end +function VECTOR:SetZ(z) +self.z=z or 0 +return self +end +function VECTOR:AddVec(Vec) +self.x=self.x+Vec.x +if Vec.z then +self.y=self.y+Vec.y +self.z=self.z+Vec.z +else +self.z=self.z+Vec.y +end +return self +end +function VECTOR:SubVec(Vec) +self.x=self.x-Vec.x +if Vec.z then +self.y=self.y-Vec.y +self.z=self.z-Vec.z +else +self.z=self.z-Vec.y +end +return self +end +function VECTOR:Dot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Rot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Copy() +local c=VECTOR:New(self.x,self.y,self.z) +return c +end +function VECTOR:Replace(Vector,Project2D) +self.x=Vector.x +if Vector.z then +self.y=Vector.y +self.z=Vector.z +else +if Project2D then +self.y=0 +end +self.z=Vector.y +end +return self +end +function VECTOR:Normalize() +local l=self:GetLength() +if l~=0 then +self:Replace(self/l) +end +return self +end +function VECTOR:Translate(Distance,Heading,Copy) +Distance=Distance or 1000 +local alpha=math.rad(Heading or 0) +local vector=Copy and self:Copy()or self +vector.x=Distance*math.cos(alpha)+vector.x +vector.z=Distance*math.sin(alpha)+vector.z +return vector +end +function VECTOR:Rotate2D(Angle,Copy) +local phi=-math.rad(Angle or 0) +local sinPhi=math.sin(phi) +local cosPhi=math.cos(phi) +local X=self.z +local Y=self.x +local z=X*cosPhi-Y*sinPhi +local x=X*sinPhi+Y*cosPhi +if Copy then +local vector=VECTOR:New(x,self.y,z) +return vector +else +self:SetX(x) +self:SetZ(z) +return self +end +end +function VECTOR: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) +local text="MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +return text +end +function VECTOR:GetSurfaceType() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +return s +end +function VECTOR:GetSurfaceTypeName() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +for name,id in land.SurfaceType()do +if id==s then +return name +end +end +return"unknown" +end +function VECTOR:IsVisible(Vec) +local vec1=self:GetVec3() +local vec2={x=Vec.x,Vec.y,Vec.z} +local los=land.isVisible(vec1,vec2) +return los +end +function VECTOR:GetClosestRoad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('roads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetClosestRailroad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('railroads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetPathOnRoad(Vec) +local vec1=self:GetVec2() +local vec2=Vec:GetVec2() +local vec2points=land.findPathOnRoads("roads",vec1.x,vec1.y,vec2.x,vec2.y) +local path=nil +if vec2points then +path=PATHLINE:NewFromVec2Array("Road",vec2points) +end +return path +end +function VECTOR:GetProfile(Vec3) +local vec3=self:GetVec3() +local vec3s=land.profile(vec3,Vec3) +local profile=nil +if vec3s then +profile=PATHLINE:NewFromVec3Array("Profile",vec3s) +end +return profile +end +function VECTOR:GetInterceptPoint(DirectionVector,Distance) +local vec3=self:GetVec3() +local ip3=land.getIP(vec3,DirectionVector,Distance or 1000) +local ipvector=nil +if ip3 then +ipvector=VECTOR:New(ip3.x,ip3.y,ip3.z) +end +return ipvector +end +function VECTOR:GetSurfaceHeight() +local vec2=self:GetVec2() +local h=land.getHeight(vec2) +return h +end +function VECTOR:GetSurfaceHeightAndDepth() +local vec2=self:GetVec2() +local h,d=land.getSurfaceHeightWithSeabed(vec2) +return h,d +end +function VECTOR:GetWindVector(WithTurbulence) +local vec3=self:GetVec3() +local wind=nil +if WithTurbulence then +wind=atmosphere.getWindWithTurbulence(vec3) +else +wind=atmosphere.getWind(vec3) +end +local vector=VECTOR:New(wind) +return vector +end +function VECTOR:GetTemperaturAndPressure() +local vec3=self:GetVec3() +local t,p=atmosphere.getTemperatureAndPressure(vec3) +return t,p +end +function VECTOR:Smoke(Color,Duration) +local vec3=self:GetVec3() +Color=Color or 0 +local name=string.format("Vector-Smoke-%d",self.uid) +trigger.action.smoke(vec3,Color,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -22932,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -23079,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23116,6 +24483,16 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetPointVec2() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23147,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -23154,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -23314,12 +24693,18 @@ end BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -23439,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -23934,6 +25329,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -26196,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -27852,33 +29288,45 @@ end function GROUP:GetTypeName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +end return nil end function GROUP:GetNatoReportingName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +end return"Bogey" end function GROUP:GetPlayerName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +end return nil end function GROUP:GetCallsign() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -27898,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -28549,7 +30006,7 @@ function GROUP:GetTaskMission() return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) end function GROUP:GetTaskRoute() -if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) else return{} @@ -28563,7 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -28585,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -28828,6 +30285,10 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +return self +end function GROUP:SetCommandInvisible(switch) return self:CommandSetInvisible(switch) end @@ -29954,6 +31415,20 @@ end function UNIT:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits=Enabled end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -30261,6 +31736,7 @@ self.Life0=Life0 else self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -30273,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -30338,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30348,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30357,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30446,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -30454,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -30476,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -30486,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -30504,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -30511,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -30524,18 +32028,15 @@ AIRBASE.Normandy={ ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -["Hawkinge"]="Hawkinge", } 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -30578,13 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -30600,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -30612,16 +32115,17 @@ AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", -["Marka"]="Marka", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -30638,17 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Ben_Gurion"]="Ben Gurion", -["Hatzor"]="Hatzor", -["Palmachim"]="Palmachim", -["Tel_Nof"]="Tel Nof", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -30656,8 +32157,7 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } -AIRBASE.MarianaIslandsWWII= -{ +AIRBASE.MarianaIslandsWWII={ ["Agana"]="Agana", ["Airfield_3"]="Airfield 3", ["Charon_Kanoa"]="Charon Kanoa", @@ -30720,6 +32220,7 @@ AIRBASE.Sinai={ ["Cairo_West"]="Cairo West", ["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["El_Minya"]="El Minya", @@ -30731,8 +32232,11 @@ AIRBASE.Sinai={ ["Inshas_Airbase"]="Inshas Airbase", ["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", ["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", ["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", @@ -30746,45 +32250,50 @@ AIRBASE.Sinai={ ["Sde_Dov"]="Sde Dov", ["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", ["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", ["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", ["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", ["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", ["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", ["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", ["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", ["Severomorsk_1"]="Severomorsk-1", ["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", ["Vidsel"]="Vidsel", ["Vuojarvi"]="Vuojarvi", -["Andoya"]="Andoya", -["Alakurtti"]="Alakurtti", -["Kittila"]="Kittila", -["Bardufoss"]="Bardufoss", -["Alta"]="Alta", -["Sodankyla"]="Sodankyla", -["Enontekio"]="Enontekio", -["Evenes"]="Evenes", -["Hosio"]="Hosio", -["Kilpyavr"]="Kilpyavr", -["Afrikanda"]="Afrikanda", -["Kalevala"]="Kalevala", -["Koshka_Yavr"]="Koshka Yavr", -["Poduzhemye"]="Poduzhemye", -["Luostari_Pechenga"]="Luostari Pechenga", } AIRBASE.Afghanistan={ ["Bagram"]="Bagram", @@ -30794,6 +32303,10 @@ AIRBASE.Afghanistan={ ["Camp_Bastion_Heliport"]="Camp Bastion Heliport", ["Chaghcharan"]="Chaghcharan", ["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", ["Farah"]="Farah", ["Gardez"]="Gardez", ["Ghazni_Heliport"]="Ghazni Heliport", @@ -30803,7 +32316,6 @@ AIRBASE.Afghanistan={ ["Kandahar"]="Kandahar", ["Kandahar_Heliport"]="Kandahar Heliport", ["Khost"]="Khost", -["Khost_Heliport"]="Khost Heliport", ["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", ["Nimroz"]="Nimroz", ["Qala_i_Naw"]="Qala i Naw", @@ -30814,21 +32326,28 @@ AIRBASE.Afghanistan={ ["Urgoon_Heliport"]="Urgoon Heliport", } AIRBASE.Iraq={ -["Baghdad_International_Airport"]="Baghdad International Airport", -["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", -["Al_Sahra_Airport"]="Al-Sahra Airport", -["Erbil_International_Airport"]="Erbil International Airport", -["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", ["Al_Salam_Airbase"]="Al-Salam Airbase", -["Balad_Airbase"]="Balad Airbase", -["Kirkuk_International_Airport"]="Kirkuk International Airport", -["Bashur_Airport"]="Bashur Airport", +["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Taquddum_Airport"]="Al-Taquddum Airport", -["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", ["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", } AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", ["Airracing_Frankfurt"]="Airracing Frankfurt", ["Airracing_Koblenz"]="Airracing Koblenz", ["Airracing_Luebeck"]="Airracing Lubeck", @@ -30839,16 +32358,23 @@ AIRBASE.GermanyCW={ ["Bienenfarm"]="Bienenfarm", ["Bindersleben"]="Bindersleben", ["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", ["Braunschweig"]="Braunschweig", ["Bremen"]="Bremen", ["Briest"]="Briest", ["Buechel"]="Buchel", ["Bueckeburg"]="Buckeburg", ["Celle"]="Celle", +["Chojna"]="Chojna", ["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", ["Damgarten"]="Damgarten", ["Dedelow"]="Dedelow", ["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", ["Fassberg"]="Fassberg", ["Finow"]="Finow", ["Frankfurt"]="Frankfurt", @@ -30859,7 +32385,7 @@ AIRBASE.GermanyCW={ ["Gatow"]="Gatow", ["Gelnhausen"]="Gelnhausen", ["Giebelstadt"]="Giebelstadt", -["Glindbruchkippe"]="Glindbruchkippe ", +["Glindbruchkippe"]="Glindbruchkippe", ["Gross_Mohrdorf"]="Gross Mohrdorf", ["Grosse_Wiese"]="Grosse Wiese", ["Guetersloh"]="Gutersloh", @@ -30977,43 +32503,60 @@ AIRBASE.GermanyCW={ ["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", ["Hannover"]="Hannover", ["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", ["Herrenteich"]="Herrenteich", ["Hildesheim"]="Hildesheim", ["Hockenheim"]="Hockenheim", ["Holzdorf"]="Holzdorf", ["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", ["Koethen"]="Kothen", ["Laage"]="Laage", +["Landstuhl"]="Landstuhl", ["Langenselbold"]="Langenselbold", ["Laerz"]="Larz", -["Leipzig_Halle"]="Leipzig Halle", ["Leipzig_Mockau"]="Leipzig Mockau", ["Luebeck"]="Lubeck", ["Lueneburg"]="Luneburg", ["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", ["Mendig"]="Mendig", ["Merseburg"]="Merseburg", ["Neubrandenburg"]="Neubrandenburg", ["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", ["Northeim"]="Northeim", +["Noervenich"]="Norvenich", ["Ober_Moerlen"]="Ober-Morlen", ["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", ["Parchim"]="Parchim", ["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", ["Pferdsfeld"]="Pferdsfeld", ["Pinnow"]="Pinnow", ["Pottschutthoehe"]="Pottschutthohe", ["Ramstein"]="Ramstein", +["Revinge"]="Revinge", ["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", ["Schoenefeld"]="Schonefeld", ["Schweinfurt"]="Schweinfurt", ["Sembach"]="Sembach", +["Sittensen"]="Sittensen", ["Spangdahlem"]="Spangdahlem", ["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", ["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", ["Tegel"]="Tegel", ["Tempelhof"]="Tempelhof", ["Templin"]="Templin", +["Thurland"]="Thurland", ["Tutow"]="Tutow", ["Uelzen"]="Uelzen", ["Uetersen"]="Uetersen", @@ -31029,6 +32572,7 @@ AIRBASE.GermanyCW={ ["Worms"]="Worms", ["Wunstorf"]="Wunstorf", ["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", ["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ @@ -31096,9 +32640,19 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -31345,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -32036,16 +33617,55 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end if self.SceneryObject and self.SceneryObject.getLife then -self.Life0=self.SceneryObject:getLife()or 0 +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) @@ -32064,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 +local life=1 if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -32104,14 +33746,16 @@ end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -32130,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -32154,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -32183,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -33036,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -33508,7 +35167,7 @@ self:SetLiquid(lqno,lqam) end end else -self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end if self:IsLimitedAircraft()then @@ -33526,7 +35185,7 @@ self:SetAmount(acname,acnumber) end end else -self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end if self:IsLimitedWeapons()then @@ -33555,7 +35214,7 @@ end end end else -self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end return self @@ -33626,6 +35285,9 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33634,8 +35296,26 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.1.0" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -33858,13 +35538,29 @@ end end self.LastPosition=pos else -if self.timer and self.timer:IsRunning()then self.timer:Stop()end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") self.CargoState=DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end return self end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end function DYNAMICCARGO._FilterHeloTypes(client) if not client then return false end local typename=client:GetTypeName() @@ -35200,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -35213,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -35235,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -35247,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -35274,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -35306,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -35313,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -35452,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -35562,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -35588,6 +37412,7 @@ local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -35601,7 +37426,6 @@ 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 @@ -35613,14 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName -if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then TargetCategory=Unit.Category.STRUCTURE TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") end 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) @@ -35629,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -35640,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -35712,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -35730,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -35819,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -35918,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -35935,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -36173,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -36196,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -36204,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -36272,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -36293,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -43849,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -43860,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -43886,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -43916,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -43939,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -43954,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -43963,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -44575,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -44607,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -44626,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -46820,7 +48690,7 @@ maxrange=7000, reloadtime=nil, }, } -ARTY.version="1.3.3" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -47500,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -51029,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -51326,7 +53220,6 @@ 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) @@ -51430,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -51632,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -52441,7 +54334,6 @@ 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 @@ -54931,7 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", -version="0.9.34", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -54982,6 +56874,9 @@ SmokeDecoyColor=SMOKECOLOR.White, checkcounter=1, DLinkCacheTime=120, logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -55027,10 +56922,14 @@ MANTIS.SamData={ ["SA-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, ["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"}, -["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, ["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, ["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, ["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, @@ -55109,13 +57008,14 @@ MANTIS.SamDataCH={ ["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} @@ -55227,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -55268,6 +57273,69 @@ self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -55557,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -55566,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -55579,7 +57661,7 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end if self.checkforfriendlies==true and self.friendlyset==nil then self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() @@ -55635,9 +57717,27 @@ self:T(self.lid.."Starting Intel Detection") local groupset=self.EWR_Group local samset=self.SAM_Group self.intelset={} -local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne") +local IntelOne=INTEL:New(groupset,self.coalition,self.name.." IntelOne") +IntelOne.DetectAccoustic=self.DetectAccoustic +IntelOne.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelOne.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelOne:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelOne:Start() -local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo") +local IntelTwo=INTEL:New(samset,self.coalition,self.name.." IntelTwo") +IntelTwo.DetectAccoustic=self.DetectAccoustic +IntelTwo.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelTwo.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelTwo:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelTwo:Start() local CacheTime=self.DLinkCacheTime or 120 local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) @@ -55893,6 +57993,22 @@ self:T(self.lid.."RemoveShorad") self.ShoradLink=false return self end +function MANTIS:_SmokeUnits(Group) +self:T("Smoking") +local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME")or 0 +local TNow=timer.getTime() +if TNow-LastSmoketime>290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 @@ -55906,7 +58022,7 @@ local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 -local shortsam=(_data[6]==MANTIS.SamType.SHORT)and true or false +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false if not shortsam then shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false end @@ -55931,17 +58047,10 @@ switch=true end if self.SamStateTracker[name]~="RED"and switch then self:__RedState(1,samgroup) -self.SamStateTracker[name]="RED" end -if shortsam==true and self.SmokeDecoy==true then +if shortsam==true and self.SmokeDecoy==true and 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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -60798,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -62764,7 +65158,7 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then @@ -62784,13 +65178,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -62820,7 +65214,7 @@ return aoa end function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -62830,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -63463,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -63826,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -64092,7 +66487,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -64114,6 +66514,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -64856,77 +67278,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -65214,19 +67568,19 @@ end if 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" +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" +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)" +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)" +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 @@ -65841,7 +68195,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true) text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) local unitClient=Unit.getByName(unit:GetName()) local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early if hornet then local nozzlePosL=0 local burnerPosL=unitClient:getDrawArgumentValue(28) @@ -65957,7 +68311,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89) else nozzlePosL=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosL=unitClient:getDrawArgumentValue(434) end return nozzlePosL @@ -65974,7 +68328,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90) else nozzlePosR=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosR=unitClient:getDrawArgumentValue(433) end return nozzlePosR @@ -66025,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -66126,7 +68478,7 @@ 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=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -66317,7 +68669,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -66332,6 +68684,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66439,14 +68792,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66494,6 +68841,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66510,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -67557,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -70463,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -70509,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -70707,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -70726,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -70736,14 +73093,12 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) return self @@ -72802,6 +75157,9 @@ self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false self.ResourceMap=nil self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end self.StaticShape=nil self.TypeNames=nil self.StaticCategory="Cargos" @@ -72809,6 +75167,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -72865,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -72890,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -73181,7 +75544,9 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, @@ -73196,8 +75561,9 @@ CTLD.FixedWingTypes={ ["Hercules"]="Hercules", ["Bronco"]="Bronco", ["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", } -CTLD.version="1.3.37" +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -73240,12 +75606,15 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") self:AddTransition("*","CratesPacked","*") self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") @@ -73286,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -73299,7 +75670,10 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false self.enableFixedWing=false @@ -73326,6 +75700,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -73338,6 +75713,8 @@ self.filename=string.format("CTLD_%s_Persist.csv",AliaS) self.allowcratepickupagain=true self.enableslingload=false self.basetype="container_cargo" +self.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do @@ -73418,6 +75795,15 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end if _unit:IsHelicopter()or _group:IsHelicopter()then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil @@ -73453,6 +75839,7 @@ end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end elseif event.id==EVENTS.NewDynamicCargo then self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo @@ -73478,6 +75865,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73514,6 +75902,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73521,6 +75910,13 @@ self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -73548,6 +75944,12 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end function CTLD:AddAllowedFixedWingType(typename) @@ -73697,6 +76099,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -73792,7 +76195,7 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) local hassecondaries=false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!",10,false,Group) @@ -73921,18 +76324,340 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -73961,7 +76686,7 @@ 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 +if not self.debug then return false end end end end @@ -73971,13 +76696,16 @@ local loaddist=self.CrateDistance or 35 local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false +end +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -73992,6 +76720,7 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 @@ -74034,8 +76763,12 @@ self.CrateCounter=self.CrateCounter+1 local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() local basetype=CType or self.basetype or"container_cargo" CCat=CCat or"Cargos" -if isstatic then -basetype=cratetemplate +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -74081,8 +76814,8 @@ 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) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -74094,23 +76827,35 @@ realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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) else +if not quiet then self:_SendMessage(text,10,false,Group) end +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -74209,6 +76954,52 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis end return self end +function CTLD:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +end +return self +end function CTLD:_RemoveCratesNearby(_group,_unit) self:T(self.lid.." _RemoveCratesNearby") local finddist=self.CrateDistance or 35 @@ -74221,8 +77012,10 @@ for _,_entry in pairs(crates)do local entry=_entry local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -if entry:GetPositionable()then -entry:GetPositionable():Destroy(false) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end table.insert(removedIDs,entry:GetID()) end @@ -74231,8 +77024,17 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -74258,7 +77060,7 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() @@ -74294,8 +77096,16 @@ local cando=cargo:UnitCanCarry(_unit) if ignoretype==true then cando=true end self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distance0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -74527,6 +77347,18 @@ end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -74670,7 +77502,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -74766,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74776,6 +77611,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74864,6 +77700,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74873,6 +77710,9 @@ end end return self end +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then @@ -74889,11 +77729,19 @@ self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end -local finddist=self.CrateDistance or 35 +local baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -74903,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -74925,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -74953,19 +77812,55 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end self:__CratesBuildStarted(1,Group,Unit,build.Name) -self:_RefreshDropTroopsMenu(Group,Unit) else self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end +end end end end @@ -74978,24 +77873,32 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) self:_RefreshLoadCratesMenu(Group,Unit) self:__CratesPacked(1,Group,Unit,_entry) -return true +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end +if not packedAny then self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end +return true +end function CTLD:_RepairCrates(Group,Unit,Engineering) self:T(self.lid.." _RepairCrates") local finddist=self.CrateDistance or 35 @@ -75163,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -75210,13 +78114,57 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -75225,6 +78173,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -75349,85 +78530,112 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu,subcatmenus) +if cargoObj.DontShowInMenu then +return +end +local isStaticCargo=false +if(cargoObj.GetType and cargoObj:GetType()==CTLD_CARGO.Enum.STATIC)or cargoObj.CargoType==CTLD_CARGO.Enum.STATIC then +isStaticCargo=true +end +if isStaticCargo and cargoObj.UnitCanCarry and not cargoObj:UnitCanCarry(_unit)then +return +end +local parent=parentMenu +if subcatmenus and cargoObj.Subcategory then +parent=subcatmenus[cargoObj.Subcategory] +if not parent then +parent=MENU_GROUP:New(_group,cargoObj.Subcategory,cratesmenu) +subcatmenus[cargoObj.Subcategory]=parent +end +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for catName,_ in pairs(self.subcats)do -subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) -end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} -for catName,_ in pairs(self.subcats)do +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end +return subcatmenus[catName] +end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75435,8 +78643,14 @@ MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,c end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75485,6 +78699,43 @@ MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,sel end end end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end +else +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) @@ -75507,6 +78758,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75518,6 +78771,10 @@ end function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then @@ -75551,7 +78808,7 @@ lineIndex=lineIndex+1 end end end -function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) local grounded=not self:IsUnitInAir(Unit) local hover=self:CanHoverLoad(Unit) @@ -75582,18 +78839,23 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) local capacity=capabilities.cratelimit or 0 if loadedData.Cratesloaded>=capacity then self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress return self end local spaceLeft=capacity-loadedData.Cratesloaded local toLoad=math.min(found,needed,spaceLeft) if toLoad<1 then self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress return self end local crateIDsLoaded={} @@ -75626,6 +78888,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if details or(not batch)then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75643,8 +78906,29 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75738,6 +79022,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -75781,29 +79066,62 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75820,33 +79138,98 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) -MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then -MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) -end i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75860,7 +79243,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end 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) @@ -75968,6 +79351,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -75998,6 +79382,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -76070,7 +79455,45 @@ table.insert(self.Cargo_Troops,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnits") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Units for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock or nil +entry.Stock0=Stock or nil +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=true +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddUnitsNoMove(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnitsNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."UnitsNoMove for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock +entry.Stock0=Stock +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=false +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) self:T(self.lid.." AddCratesCargo") if not self:_CheckTemplates(Templates)then self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") @@ -76085,11 +79508,41 @@ cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end +cargo.C130TypeName=C130TypeName table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) +self:T(self.lid.." AddCratesCargoNoMove") +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) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +cargo.C130TypeName=C130TypeName +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 local type=CTLD_CARGO.Enum.STATIC @@ -76100,6 +79553,9 @@ if unittemplate and unittemplate.resourcePayload then ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) end local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end @@ -76241,7 +79697,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -76264,9 +79725,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -76600,6 +80097,7 @@ return self end function CTLD:IsCorrectHover(Unit) self:T(self.lid.." IsCorrectHover") +if self:IsFixedWing(Unit)then return false end local outcome=false if self:IsUnitInAir(Unit)then local uspeed=Unit:GetVelocityMPS() @@ -76777,6 +80275,23 @@ Troopstable[genname].GenericCargo=generic end end end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end for _id,_cargo in pairs(self.Cargo_Troops)do local generic=_cargo local genname=generic:GetName() @@ -76807,10 +80322,31 @@ Troopstable[genname].Infield=Troopstable[genname].Infield+1 Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo end else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) end end end +end for _unitname,_loaded in pairs(self.Loaded_Cargo)do local _unit=UNIT:FindByName(_unitname) if _unit and _unit:IsAlive()then @@ -76819,12 +80355,12 @@ local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} for _,_cgo in pairs(loadedcargo)do local cargo=_cgo local type=cargo.CargoType -local gname=cargo.Name +local gname=cargo:GetName() local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) -self:T("Looking at "..gname.." in the helo - type = "..type) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then if gcargo and gcargo:GetStock0()>0 then -self:T("Adding "..gname.." in the helo - type = "..type) +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 end @@ -76892,6 +80428,47 @@ end end return Troopstable end +function CTLD:AddStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock+number +end +break +end +end +return self +end +function CTLD:SetStockUnits(Name,Number) +local name=Name or"none" +local number=Number +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +if number==nil or number==-1 then +_unit.Stock=-1 +else +_unit.Stock=number +end +break +end +end +return self +end +function CTLD:GetStockUnits() +local Stock={} +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +Stock[_unit.Name]=_unit.Stock or-1 +end +return Stock +end function CTLD:AddStockTroops(Name,Number) local name=Name or"none" local number=Number or 1 @@ -76899,6 +80476,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76911,6 +80489,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76923,6 +80502,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76935,6 +80515,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76947,6 +80528,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76959,6 +80541,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -77009,6 +80592,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -77020,6 +80604,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -77031,6 +80616,27 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() +end +end +return self +end +function CTLD:RemoveStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock-number +if _unit.Stock<0 then +_unit.Stock=0 +end +end +break end end return self @@ -77480,11 +81086,44 @@ 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) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +for _,_cargo in pairs(Cargotable)do +local Vehicle=_cargo.Positionable +if Vehicle then +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 +end +end +return self +end +function CTLD:OnAfterGetCrates(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:OnAfterRemoveCratesNearby(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) return self end function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) @@ -77512,9 +81151,13 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -77761,11 +81404,22 @@ 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) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +if self.C130GetUnits then +for _,_unit in pairs(self.C130GetUnits)do +if _unit.Name==cargoname then +if type(_unit.Stock)=="number"and _unit.Stock~=-1 then +_unit.Stock0=_unit.Stock0 or _unit.Stock +_unit.Stock=math.max(0,(_unit.Stock or 0)-1) +end +break +end +end +end 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,timestamp) end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +elseif self.loadSavedCrates and(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic=nil if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then @@ -77777,8 +81431,12 @@ injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) 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) injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) -local map=cargotype:GetStaticResourceMap() -injectstatic:SetStaticResourceMap(map) +local unittemplate=_DATABASE:GetStaticUnitTemplate(cargoname) +local ResourceMap=nil +if unittemplate and unittemplate.resourcePayload then +ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) +end +injectstatic:SetStaticResourceMap(ResourceMap) end if injectstatic then self:InjectStatics(dropzone,injectstatic,false,true) @@ -78247,6 +81905,8 @@ CreateRadioBeacons=true, UserSetGroup=nil, AllowIRStrobe=false, IRStrobeRuntime=300, +FARPRescueDistance=500, +EnableMenuSmokeMASH=true, } CSAR.AircraftType={} CSAR.AircraftType["SA342Mistral"]=2 @@ -78260,13 +81920,14 @@ CSAR.AircraftType["Mi-24P"]=8 CSAR.AircraftType["Mi-24V"]=8 CSAR.AircraftType["Bell-47"]=2 CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 CSAR.AircraftType["AH-64D_BLK_II"]=2 CSAR.AircraftType["Bronco-OV-10A"]=2 CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.35" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -78346,12 +82007,13 @@ self.loadtimemax=135 self.radioSound="beacon.ogg" self.beaconRefresher=29 self.allowFARPRescue=true -self.FARPRescueDistance=1000 +self.FARPRescueDistance=500 self.max_units=6 self.useprefix=true self.csarPrefix={"helicargo","MEDEVAC"} self.template=Template or"generic" self.mashprefix={"MASH"} +self.EnableMenuSmokeMASH=true self.autosmoke=false self.autosmokedistance=2000 self.limitmaxdownedpilots=true @@ -78529,6 +82191,9 @@ BeaconName=_unitName..math.random(1,10000) else BeaconName="Ghost-1-1"..math.random(1,10000) end +if _playerName==nil or _playerName==""then +_playerName="AI MIA" +end if(_freq and _freq~=0)then self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) end @@ -78712,11 +82377,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78771,7 +82436,7 @@ end self.takenOff[_event.IniUnitName]=nil local _place=_event.Place if _place==nil then -self:T(self.lid.." Landing Place Nil") +self:T(self.lid.." Landing Place nil") return self end if self.inTransitGroups[_event.IniUnitName]==nil then @@ -78779,7 +82444,10 @@ 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) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -78796,7 +82464,7 @@ 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:T({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") Unit.destroy(_event.initiator) end @@ -79134,7 +82802,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -79152,7 +82820,13 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_distself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end end for _,_mashes in pairs(MashSets)do for _,_mashUnit in pairs(_mashes or{})do @@ -79472,11 +83186,12 @@ _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance,MashName +return _shortestDistance,MashName,Coordinate else return-1 end @@ -79531,6 +83246,9 @@ local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -79545,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -79616,7 +83335,7 @@ local name=_radioUnit:GetName() local Frequency=_freq local Sound="l10n/DEFAULT/"..self.radioSound local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end @@ -79926,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -79935,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -80005,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -80081,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -81010,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -81351,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -81465,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -81743,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -81766,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -81813,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -82322,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -82434,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -82442,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -82476,6 +86216,20 @@ mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.mi mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -82611,33 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) elseif MissionType==AUFTRAG.Type.STRAFING then -mission=self:NewSTRAFING(Target,Altitude) +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -82698,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -83261,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -83465,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -83711,12 +87476,18 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 -if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) return false end return true @@ -83981,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -84523,6 +88294,15 @@ param.action="Wedge" param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 +DCStask.params=param +table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then local DCStask={} DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY @@ -84783,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.72", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -85400,6 +89180,11 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end function AWACS:SetMaxMissionRange(NM) self.MaxMissionRange=NM or 125 return self @@ -85659,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -85672,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -87150,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -87183,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -87208,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -87300,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -87843,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -88486,24 +92324,36 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) @@ -89518,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -89547,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.1" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -90577,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -91014,8 +94925,11 @@ end end return self end -function COHORT:RemoveAssets(N) +function COHORT:RemoveAssets(N,Delay) self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else N=N or 1 local n=0 for i=#self.assets,1,-1 do @@ -91023,6 +94937,7 @@ local asset=self.assets[i] self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) if not(asset.requested or asset.spawned or asset.isReserved)then self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) table.remove(self.assets,i) n=n+1 else @@ -91033,6 +94948,7 @@ break end end self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end return self end function COHORT:GetName() @@ -96967,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -97191,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -97198,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -97251,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -97258,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -97290,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -97456,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -97497,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -97711,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -98560,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98615,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -99106,7 +103124,7 @@ text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsR for _,mtype in pairs(AUFTRAG.Type)do local n=self:CountMissionsInQueue(mtype) if n>0 then -local N=self:CountMissionsInQueue(mtype) +local N=self:CountMissionsInQueue(mtype,true) text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) end end @@ -99201,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -99963,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.3" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -100330,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -100554,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -100562,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -100578,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -100596,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -102626,6 +106648,12 @@ end function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +function OPSGROUP:IsOutOfA2GAmmo() +if(self.outofMissilesAG and self.outofBombs and self.outofGuns)or self.outofAmmo then +return true +end +return false +end function OPSGROUP:IsLasing() return self.spot.On end @@ -103405,14 +107433,32 @@ end function OPSGROUP:_UpdateTask(Task,Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +if Mission.type==AUFTRAG.Type.RESCUEHELO then +self:T("**********") +self:T("** RESCUEHELO USED") +self:T("**********") +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +local helogroupname=self:GetGroup():GetName() +Task.formation=RESCUEHELO:New(followUnit,helogroupname) +Task.formation:SetRespawnOnOff(false) +Task.formation.respawninair=false +Task.formation:SetTakeoffCold() +Task.formation:SetHomeBase(followUnit) +Task.formation.helo=self:GetGroup() +Task.formation:Start() +if self:IsFlightgroup()then +self:SetDespawnAfterLanding() +end +else 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=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION) 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() +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then local zone=Task.dcstask.params.zone local surfacetypes=nil @@ -103496,6 +107542,15 @@ end if target then self:EngageTarget(target,speed,Task.dcstask.params.formation) end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT 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=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.altitude) +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then if self.isFlightgroup then self:T("We are Special Auftrag Patrol Race Track, starting now ...") @@ -103555,7 +107610,12 @@ 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())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -103603,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -103679,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -104059,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -104339,10 +108399,12 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then @@ -104922,7 +108984,7 @@ 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:T(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() @@ -105069,7 +109131,6 @@ function OPSGROUP:_Spawn(Delay,Template) if Delay and Delay>0 then self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) else -self:T2({Template=Template}) if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then UTILS.ValidateAndRepositionGroundUnits(Template.units) end @@ -105137,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -105195,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -106710,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -107478,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -107843,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -107858,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -107878,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -107908,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -107953,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -110002,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -110129,6 +114211,13 @@ end end) return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -110165,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110562,10 +114661,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -110608,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -110668,6 +114770,12 @@ Scoring=nil, MenuNoTask=nil, InformationMenu=false, TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -110696,6 +114804,12 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.CAP]=100, [AUFTRAG.Type.CAPTUREZONE]=100, } +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, +} PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, AAA=GROUP.Attribute.GROUND_AAA, @@ -110724,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -110808,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -110871,7 +114991,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -110942,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -111297,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -111849,17 +115982,24 @@ end PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end +self:__TaskAdded(10,PlayerTask,Silent) else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end return self end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) self:T({Force,Group,Client}) self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end local force=false if type(Force)=="boolean"then force=Force @@ -111944,7 +116084,7 @@ 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 +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) @@ -111987,8 +116127,9 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -112004,7 +116145,12 @@ 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) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -112106,8 +116252,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -112160,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -112169,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -112577,6 +116738,49 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -112708,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -116174,6 +120524,33 @@ self.ConflictZoneSet:AddZone(Zone) self.GoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) self:T("_TryAssignIntercept for size "..WingSize or 1) local assigned=false @@ -116298,6 +120675,8 @@ end if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then success=false end +else +success=true end return success end, @@ -116306,6 +120685,13 @@ nogozoneset, conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -116331,6 +120717,12 @@ BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -116403,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -116413,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -116446,6 +120839,477 @@ _wing:Stop() end return self end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) +return self +end AI_BALANCER={ ClassName="AI_BALANCER", PatrolZones={}, @@ -132120,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"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_DAP"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 @@ -3303,6 +3840,16 @@ elseif Heading>=292 and Heading<=338 then return"North-West" elseif Heading>=339 then return"North" end end +function UTILS.AdjustHeading360(Heading) +while Heading>=360 or Heading<0 do +if Heading>=360 then +Heading=Heading-360 +elseif Heading<0 then +Heading=Heading+360 +end +end +return Heading +end function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO="Merged." local GroupNumber=ToGrp:GetSize() @@ -3860,7 +4407,57 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if newWH then +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +4475,61 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +if id>1 then UnitTemplate.name=Name.."-"..id end +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +end +PopulateStorage(Name,liquids,equip,airframes) local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4558,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -4417,6 +5045,185 @@ end end return nil end +function UTILS.CreateAirbaseEnum() +local function _savefile(filename,data) +local file=lfs.writedir()..filename +local f=io.open(file,"wb") +if f then +f:write(data) +f:close() +env.info(string.format("Saving to file %s",tostring(file))) +else +env.info(string.format("ERROR: Could not save results to file %s",tostring(file))) +end +end +local airbases=world.getAirbases() +local mapname=env.mission.theatre +local myab={} +for i,_airbase in pairs(airbases)do +local airbase=_airbase +local cat=airbase:getDesc().category +if cat==Airbase.Category.AIRDROME then +local name=airbase:getName() +local key=name +if name=="Airracing Lubeck"then +key="Airracing_Luebeck" +elseif name=="Bad Durkheim"then +key="Bad_Duerkheim" +elseif name=="Buchel"then +key="Buechel" +elseif name=="Buckeburg"then +key="Bueckeburg" +elseif name=="Dusseldorf"then +key="Duesseldorf" +elseif name=="Gutersloh"then +key="Guetersloh" +elseif name=="Kothen"then +key="Koethen" +elseif name=="Larz"then +key="Laerz" +elseif name=="Lubeck"then +key="Luebeck" +elseif name=="Luneburg"then +key="Lueneburg" +elseif name=="Norvenich"then +key="Noervenich" +elseif name=="Ober-Morlen"then +key="Ober_Moerlen" +elseif name=="Peenemunde"then +key="Peenemuende" +elseif name=="Pottschutthohe"then +key="Pottschutthoehe" +elseif name=="Schonefeld"then +key="Schoenefeld" +elseif name=="Weser Wumme"then +key="Weser_Wuemme" +elseif name=="Zollschen"then +key="Zoellschen" +elseif name=="Zweibrucken"then +key="Zweibruecken" +end +key=key:gsub(" ","_") +key=key:gsub("-","_") +key=key:gsub("'","_") +key=UTILS.ReplaceIllegalCharacters(key,"_") +local entry={} +entry.key=key +entry.name=name +table.insert(myab,entry) +end +end +table.sort(myab,function(a,b)return a.namemaxDist then +maxDist=dist +end +end +return center,maxDist +end +function UTILS.GetMinimumBoundingCircle(points) +if#points==0 then +return nil,nil +end +local function distance(p1,p2) +local dx=p2.x-p1.x +local dy=p2.y-p1.y +return math.sqrt(dx*dx+dy*dy) +end +local function circleFrom2Points(p1,p2) +local center={ +x=(p1.x+p2.x)/2, +y=(p1.y+p2.y)/2 +} +local radius=distance(p1,p2)/2 +return center,radius +end +local function circleFrom3Points(p1,p2,p3) +local ax,ay=p1.x,p1.y +local bx,by=p2.x,p2.y +local cx,cy=p3.x,p3.y +local d=2*(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by)) +if math.abs(d)<0.0001 then +return circleFrom2Points(p1,p3) +end +local aSq=ax*ax+ay*ay +local bSq=bx*bx+by*by +local cSq=cx*cx+cy*cy +local ux=(aSq*(by-cy)+bSq*(cy-ay)+cSq*(ay-by))/d +local uy=(aSq*(cx-bx)+bSq*(ax-cx)+cSq*(bx-ax))/d +local center={x=ux,y=uy} +local radius=distance(center,p1) +return center,radius +end +local function isInside(center,radius,point,tolerance) +tolerance=tolerance or 0.0001 +return distance(center,point)<=radius+tolerance +end +local function welzlHelper(pts,n,boundary) +if n==0 or#boundary==3 then +if#boundary==0 then +return{x=0,y=0},0 +elseif#boundary==1 then +return{x=boundary[1].x,y=boundary[1].y},0 +elseif#boundary==2 then +return circleFrom2Points(boundary[1],boundary[2]) +else +return circleFrom3Points(boundary[1],boundary[2],boundary[3]) +end +end +local p=pts[n] +local center,radius=welzlHelper(pts,n-1,boundary) +if isInside(center,radius,p)then +return center,radius +end +local newBoundary={} +for i=1,#boundary do +newBoundary[i]=boundary[i] +end +table.insert(newBoundary,p) +return welzlHelper(pts,n-1,newBoundary) +end +local pts={} +for i,p in ipairs(points)do +pts[i]={x=p.x,y=p.y} +end +for i=#pts,2,-1 do +local j=math.random(1,i) +pts[i],pts[j]=pts[j],pts[i] +end +return welzlHelper(pts,#pts,{}) +end PROFILER={ ClassName="PROFILER", Counters={}, @@ -7703,7 +8510,7 @@ end end elseif Event.TgtObjectCategory==Object.Category.SCENERY then Event.TgtDCSUnit=Event.target -Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName()or nil +Event.TgtDCSUnitName=Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName=Event.TgtDCSUnitName Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) @@ -7726,10 +8533,12 @@ 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) +if Event.Place then Event.PlaceName=Event.Place:GetName() end end end +end if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos @@ -9543,7 +10352,7 @@ self.ScanData.Coalitions={} self.ScanData.Scenery={} self.ScanData.SceneryTable={} self.ScanData.Units={} -local ZoneCoord=self:GetCoordinate() +local ZoneCoord=self:GetCoordinate():SetAlt() local ZoneRadius=self:GetRadius() local SphereSearch={ id=world.VolumeType.SPHERE, @@ -9553,10 +10362,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -9600,35 +10408,46 @@ end function ZONE_RADIUS:GetScannedUnits() return self.ScanData.Units end -function ZONE_RADIUS:GetScannedSetUnit() -local SetUnit=SET_UNIT:New() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) +self.SetUnit=self.SetUnit or SET_UNIT:New() +self.SetUnit:Clear(false) +self.SetUnit.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 -SetUnit:AddUnit(FoundUnit) +local FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then +self.SetUnit:AddUnit(FoundUnit) else local FoundStatic=STATIC:FindByName(UnitObject:getName(),false) if FoundStatic then -SetUnit:AddUnit(FoundStatic) +self.SetUnit:AddUnit(FoundStatic) end end end end end -return SetUnit +return self.SetUnit end -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup:Clear(false) 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 FoundCoalition=FoundUnit and FoundUnit:GetCoalition()or nil +local includeoncoalition=true +if Coalition~=nil and FoundCoalition==Coalition then includeoncoalition=true else includeoncoalition=false end +if Coalition==nil then includeoncoalition=true end +if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -9918,6 +10737,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9979,6 +10808,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -10607,10 +11447,9 @@ radius=ZoneRadius, } } local function EvaluateZone(ZoneObject) -if ZoneObject then +if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint())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 @@ -10629,7 +11468,7 @@ self.ScanData.Coalitions[CoalitionDCSUnit]=true self.ScanData.Units[ZoneObject]=ZoneObject end end -if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())then +if ObjectCategory==Object.Category.SCENERY then local SceneryType=ZoneObject:getTypeName() local SceneryName=ZoneObject:getName() self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} @@ -11392,11 +12231,17 @@ 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} +local rotation=UTILS.ToRadian(objectData.angle or 0) +local sinRot=math.sin(rotation) +local cosRot=math.cos(rotation) +local dx=h/2 +local dy=w/2 +local points={ +{x=-dx*cosRot-(-dy*sinRot)+vec2.x,y=-dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(-dy*sinRot)+vec2.x,y=dx*sinRot+(-dy*cosRot)+vec2.y}, +{x=dx*cosRot-(dy*sinRot)+vec2.x,y=dx*sinRot+(dy*cosRot)+vec2.y}, +{x=-dx*cosRot-(dy*sinRot)+vec2.x,y=-dx*sinRot+(dy*cosRot)+vec2.y}, +} 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) @@ -12071,7 +12916,7 @@ end function DATABASE:_EventOnPlayerEnterUnit(Event) self:F2({Event}) if Event.IniDCSUnit then -if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then +if Event.IniObjectCategory==1 and Event.IniUnit and Event.IniUnit: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))) @@ -12385,6 +13230,18 @@ List={}, Index={}, Database=nil, CallScheduler=nil, +FilterCoalitionNumbers={ +[coalition.side.RED+1]="red", +[coalition.side.BLUE+1]="blue", +[coalition.side.NEUTRAL+1]="neutral", +}, +FilterMeta={ +Coalitions={ +["red"]=coalition.side.RED, +["blue"]=coalition.side.BLUE, +["neutral"]=coalition.side.NEUTRAL, +}, +}, } function SET_BASE:New(Database) local self=BASE:Inherit(self,FSM:New()) @@ -12426,6 +13283,20 @@ self:Remove(Name,not TriggerEvent) end return self end +function SET_BASE:FilterCoalitions(Coalitions,Clear) +if Clear or(not self.Filter.Coalitions)then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then Coalitions={Coalitions}end +for CoalitionID,Coalition in pairs(Coalitions)do +local coalition=Coalition +if type(Coalition)=="number"then +coalition=self.FilterCoalitionNumbers[Coalition+1]or"unknown" +end +self.Filter.Coalitions[coalition]=coalition +end +return self +end function SET_BASE:_Find(ObjectName) local ObjectFound=self.Set[ObjectName] return ObjectFound @@ -12738,7 +13609,8 @@ return true end function SET_BASE:IsInSet(Object) local outcome=false -local name=Object:GetName() +if Object==nil then return false end +local name=(Object~=nil and Object.GetName)and Object:GetName()or"none" self:ForEach( function(object) if object:GetName()==name then @@ -12909,16 +13781,6 @@ self.Filter.Zones[zonename]=Zone end return self end -function SET_GROUP:FilterCoalitions(Coalitions,Clear) -if Clear or(not self.Filter.Coalitions)then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_GROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -13373,16 +14235,6 @@ function SET_UNIT:FindUnit(UnitName) local UnitFound=self.Set[UnitName] return UnitFound end -function SET_UNIT:FilterCoalitions(Coalitions) -self.Filter.Coalitions={} -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_UNIT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14027,18 +14879,6 @@ 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={} @@ -14465,18 +15305,6 @@ self.Filter.Playernames[playername]=playername end return self end -function SET_CLIENT: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_CLIENT:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -14650,8 +15478,7 @@ end return self end function SET_CLIENT:HandleCASlots() -self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) -self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) +self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventPlayerEnterUnit) self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end) return self end @@ -14866,18 +15693,6 @@ function SET_PLAYER:FindClient(PlayerName) local ClientFound=self.Set[PlayerName] return ClientFound end -function SET_PLAYER: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_PLAYER:FilterZones(Zones) if not self.Filter.Zones then self.Filter.Zones={} @@ -15131,18 +15946,6 @@ function SET_AIRBASE:GetRandomAirbase() local RandomAirbase=self:GetRandom() return RandomAirbase end -function SET_AIRBASE: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_AIRBASE:FilterCategories(Categories) if not self.Filter.Categories then self.Filter.Categories={} @@ -15303,18 +16106,6 @@ function SET_CARGO:FindCargo(CargoName) local CargoFound=self.Set[CargoName] return CargoFound end -function SET_CARGO: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_CARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -15934,16 +16725,6 @@ self.Filter.Prefixes[Prefix]=Prefix end return self end -function SET_OPSZONE:FilterCoalitions(Coalitions) -if not self.Filter.Coalitions then -self.Filter.Coalitions={} -end -Coalitions=UTILS.EnsureTable(Coalitions,false) -for CoalitionID,Coalition in pairs(Coalitions)do -self.Filter.Coalitions[Coalition]=Coalition -end -return self -end function SET_OPSZONE:FilterOnce() for ObjectName,Object in pairs(self.Database)do self:Remove(ObjectName,true) @@ -16160,6 +16941,18 @@ self:Remove(RemoveGroupName) end return self end +function SET_OPSGROUP:CountAlive() +local CountG=0 +local CountU=0 +local Set=self:GetSet() +for GroupID,GroupData in pairs(Set)do +if GroupData and GroupData:IsAlive()then +CountG=CountG+1 +CountU=CountU+GroupData:GetGroup():CountAliveUnits() +end +end +return CountG,CountU +end function SET_OPSGROUP:FindGroup(GroupName) local GroupFound=self.Set[GroupName] return GroupFound @@ -16176,18 +16969,6 @@ function SET_OPSGROUP:FindNavyGroup(GroupName) local GroupFound=self:FindGroup(GroupName) return GroupFound end -function SET_OPSGROUP:FilterCoalitions(Coalitions,Clear) -if Clear or 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_OPSGROUP:FilterCategories(Categories,Clear) if Clear or not self.Filter.Categories then self.Filter.Categories={} @@ -16669,18 +17450,6 @@ DCargoInclude=DCargoInclude and MClientFunc end return DCargoInclude end -function SET_DYNAMICCARGO: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_DYNAMICCARGO:FilterTypes(Types) if not self.Filter.Types then self.Filter.Types={} @@ -17606,6 +18375,7 @@ local AirbaseCategory=airbase:GetAirbaseCategory() if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then RoutePoint.linkUnit=AirbaseID RoutePoint.helipadId=AirbaseID +RoutePoint.airdromeId=airbase:IsAirdrome()and AirbaseID or nil elseif AirbaseCategory==Airbase.Category.AIRDROME then RoutePoint.airdromeId=AirbaseID else @@ -17814,6 +18584,23 @@ return nil,nil,false end return Path,Way,GotPath end +function COORDINATE:GetPathlineOnRoad(ToCoord,IncludeEndpoints,Railroad) +local RoadType="roads" +if Railroad==true then +RoadType="railroads" +end +local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z) +if IncludeEndpoints then +path=path or{} +table.insert(path,1,self:GetVec2()) +table.insert(path,ToCoord:GetVec2()) +end +local pathline=nil +if path then +pathline=PATHLINE:NewFromVec2Array(RoadType,path) +end +return pathline +end function COORDINATE:GetSurfaceType() local vec2=self:GetVec2() local surface=land.getSurfaceType(vec2) @@ -18693,41 +19480,6 @@ return nil 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 -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 -end -do VELOCITY={ ClassName="VELOCITY", } @@ -20107,6 +20859,9 @@ SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-Curren end end if self.SpawnRandomizeUnits then +if self.SpawnRandomizePosition then +PointVec3=COORDINATE:New(SpawnTemplate.x,SpawnTemplate.route.points[1].alt,SpawnTemplate.y) +end for UnitID=1,#SpawnTemplate.units do local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) if(SpawnZone)then @@ -21032,6 +21787,11 @@ min=9 max=18 ctable=CALLSIGN.F15E end +if SpawnTemplate.units[1].type=="A6E"then +min=4 +max=18 +ctable=CALLSIGN.Intruder +end local callsignnr=math.random(min,max) local callsignname="Enfield" for name,value in pairs(ctable)do @@ -21547,6 +22307,18 @@ function SPAWNSTATIC:InitCargo(IsCargo) self.InitStaticCargo=IsCargo return self end +function SPAWNSTATIC:InitHiddenOnMap(OnOff) +self.SpawnHiddenOnMap=OnOff==false and false or true +return self +end +function SPAWNSTATIC:InitHiddenOnMFD() +self.SpawnHiddenOnMFD=true +return self +end +function SPAWNSTATIC:InitHiddenOnPlanner() +self.SpawnHiddenOnPlanner=true +return self +end function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead return self @@ -21640,6 +22412,15 @@ end if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end +if self.SpawnHiddenOnPlanner then +Template.hiddenOnPlanner=true +end +if self.SpawnHiddenOnMFD then +Template.hiddenOnMFD=true +end +if self.SpawnHiddenOnMap then +Template.hidden=self.SpawnHiddenOnMap +end if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -21705,7 +22486,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21713,8 +22494,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -21962,20 +22741,17 @@ MARKEROPS_BASE={ ClassName="MARKEROPS", Tag="mytag", Keywords={}, -version="0.1.4", +version="0.1.5", debug=false, Casesensitive=true, } -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) 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","*") @@ -22038,9 +22814,9 @@ end end function MARKEROPS_BASE:_MatchTag(Eventtext) local matches=false -if not self.Casesensitive then +if self.Casesensitive==false then local type=string.lower(self.Tag) -if string.find(string.lower(Eventtext),type)then +if string.find(string.lower(Eventtext),type,1,true)then matches=true end else @@ -22076,6 +22852,14 @@ self:UnHandleEvent(EVENTS.MarkAdded) self:UnHandleEvent(EVENTS.MarkChange) self:UnHandleEvent(EVENTS.MarkRemoved) end +function MARKEROPS_BASE:SwitchCaseSensitiveOff() +self.Casesensitive=false +return self +end +function MARKEROPS_BASE:SwitchCaseSensitiveOn() +self.Casesensitive=true +return self +end TEXTANDSOUND={ ClassName="TEXTANDSOUND", version="0.0.1", @@ -22180,7 +22964,7 @@ ClassName="PATHLINE", lid=nil, points={}, } -PATHLINE.version="0.1.1" +PATHLINE.version="0.2.0" function PATHLINE:New(Name) local self=BASE:Inherit(self,BASE:New()) self.name=Name or"Unknown Path" @@ -22279,12 +23063,26 @@ return point.vec2 end return nil end +function PATHLINE:GetLength(Project2D) +local l=0 +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +if Project2D then +l=l+UTILS.VecDist2D(p1.vec2,p2.vec2) +else +l=l+UTILS.VecDist3D(p1.vec3,p2.vec3) +end +end +return l +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) +UTILS.RemoveMark(point.markerID) end else if point.markerID then @@ -22295,6 +23093,31 @@ local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m trigger.action.markToAll(point.markerID,text,point.vec3,"") end end +return self +end +function PATHLINE:DrawLine(Recipient,Color,LineType) +Recipient=Recipient or-1 +Color=Color or{1,0,0,1.0} +LineType=LineType or 1 +local ReadOnly=false +local np=#self.points +for i=1,np-1 do +local p1=self.points[i] +local p2=self.points[i+1] +p1.lineID=UTILS.GetMarkID() +trigger.action.lineToAll(Recipient,p1.lineID,p1.vec3,p2.vec3,Color,LineType,ReadOnly,"") +end +return self +end +function PATHLINE:UnDrawLine(Delay) +local np=#self.points +for _,_point in pairs(self.points)do +local p=_point +if p.lineID then +UTILS.RemoveMark(p.lineID,Delay) +end +end +return self end function PATHLINE:_CreatePoint(Vec) local point={} @@ -22833,6 +23656,529 @@ end end return self end +VECTOR={ +ClassName="VECTOR", +verbose=0, +} +VECTOR.version="0.1.0" +_VECTORID=0 +VECTOR.__index=VECTOR +function VECTOR:New(x,y,z) +if z==nil then +self=setmetatable({x=x or 0,y=0,z=y or 0},VECTOR) +else +self=setmetatable({x=x or 0,y=y or 0,z=z or 0},VECTOR) +end +_VECTORID=_VECTORID+1 +self.uid=_VECTORID +return self +end +function VECTOR:NewFromVec(Vec) +local vector=VECTOR:New(Vec.x,Vec.y,Vec.z) +return vector +end +function VECTOR:NewFromPolar(r,phi) +local Phi=math.rad(phi) +local x=r*math.cos(phi) +local y=r*math.sin(phi) +local v=VECTOR:New(x,y) +return self +end +function VECTOR:NewFromSpherical(r,theta,phi) +local sinPhi=math.sin(math.rad(phi)) +local cosPhi=math.cos(math.rad(phi)) +local sinTheta=math.sin(math.rad(theta)) +local cosTheta=math.cos(math.rad(theta)) +local x=r*sinTheta*cosPhi +local y=r*sinTheta*sinPhi +local z=r*cosTheta +local v=VECTOR:New(x,y,z) +return self +end +function VECTOR:NewDirectionalVector(a,b) +local x=b.x-a.x +local y +local z +if a.z and b.z then +y=b.y-a.y +z=b.z-a.z +elseif b.z then +y=b.y-0 +z=b.z-a.y +elseif a.z then +y=0-a.y +z=b.y-a.z +else +y=b.y-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:NewFromLLDD(Latitude,Longitude,Altitude) +local vec3=coord.LLtoLO(Latitude,Longitude) +self=VECTOR:NewFromVec(vec3) +if Altitude then +self.y=Altitude +end +return self +end +function VECTOR:NewFromLLDMS(Latitude,Longitude,Altitude) +local lat=UTILS.LLDMSstringToDD(Latitude) +local lon=UTILS.LLDMSstringToDD(Longitude) +self=VECTOR:NewFromLLDD(lat,lon,Altitude) +return self +end +function VECTOR:GetVec2() +local vec={x=self.x,y=self.z} +return vec +end +function VECTOR:GetVec3(OnSurface) +local x=self.x +local y=OnSurface and land.getHeight({x=self.x,y=self.z})or self.y +local z=self.z +local vec={x=x,y=y,z=z} +return vec +end +function VECTOR:GetCoordinate(OnSurface) +local vec3=self:GetVec3(OnSurface) +local coordinate=COORDINATE:NewFromVec3(vec3) +return coordinate +end +function VECTOR:GetDistance(Vector,Only2D) +local dx=self.x-Vector.x +local dy=0 +local dz=0 +if Vector.z then +if not Only2D then +dy=self.y-Vector.y +end +dz=self.z-Vector.z +else +dy=0 +dz=self.z-Vector.y +end +local dist=math.sqrt(dx*dx+dy*dy+dz*dz) +return dist +end +function VECTOR:GetDirectionalVectorTo(a) +local x=a.x-self.x +local y=0 +local z=nil +if a.z then +y=a.y-self.y +z=a.z-self.z +else +y=a.y-self.z +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetDirectionalVectorFrom(a) +local x=self.x-a.x +local y +local z +if a.z then +y=self.y-a.y +z=self.z-a.z +else +y=self.z-a.y +z=nil +end +local c=VECTOR:New(x,y,z) +return c +end +function VECTOR:GetLength() +local l=math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) +return l +end +function VECTOR:GetHeading(To360) +local heading=math.atan2(self.z,self.x) +heading=math.deg(heading) +if To360==nil or To360==true then +heading=UTILS.AdjustHeading360(heading) +else +if heading==360.0 then +heading=0.0 +end +end +return heading +end +function VECTOR:GetHeadingTo(Vector) +local a=self:GetDirectionalVectorTo(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetHeadingFrom(Vector) +local a=self:GetDirectionalVectorFrom(Vector) +local heading=math.deg(math.atan2(a.z,a.x)) +heading=UTILS.AdjustHeading360(heading) +return heading +end +function VECTOR:GetLatitudeLongitude() +local vec3=self:GetVec3() +local latitude,longitude,altitude=coord.LOtoLL(vec3) +return latitude,longitude +end +function VECTOR:GetMGRS() +local lat,long=self:GetLatitudeLongitude() +local mgrs=coord.LLtoMGRS(lat,long) +return mgrs +end +function VECTOR:GetHeadingDelta(Vector) +local h1=self:GetHeading(false) +local h2=Vector:GetHeading(false) +local delta=h2-h1 +return delta +end +function VECTOR:GetIntermediateVector(Vector,Fraction) +local f=Fraction or 0.5 +local vec=self:GetDirectionalVectorTo(Vector) +local length=vec:GetLength() +vec:SetLength(f*length) +vec=self+vec +return vec +end +function VECTOR:SetLength(Length) +self:Normalize() +local v=self*Length +self:Replace(v) +return self +end +function VECTOR:SetX(x) +self.x=x or 0 +return self +end +function VECTOR:SetY(y) +if y==nil then +y=self:GetSurfaceHeight() +end +self.y=y +return self +end +function VECTOR:SetZ(z) +self.z=z or 0 +return self +end +function VECTOR:AddVec(Vec) +self.x=self.x+Vec.x +if Vec.z then +self.y=self.y+Vec.y +self.z=self.z+Vec.z +else +self.z=self.z+Vec.y +end +return self +end +function VECTOR:SubVec(Vec) +self.x=self.x-Vec.x +if Vec.z then +self.y=self.y-Vec.y +self.z=self.z-Vec.z +else +self.z=self.z-Vec.y +end +return self +end +function VECTOR:Dot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Rot(Vec) +local dot=self.x*Vec.x +if Vec.z then +dot=dot+self.y*Vec.y+self.z*Vec.z +else +dot=dot+self.z*Vec.y +end +return dot +end +function VECTOR:Copy() +local c=VECTOR:New(self.x,self.y,self.z) +return c +end +function VECTOR:Replace(Vector,Project2D) +self.x=Vector.x +if Vector.z then +self.y=Vector.y +self.z=Vector.z +else +if Project2D then +self.y=0 +end +self.z=Vector.y +end +return self +end +function VECTOR:Normalize() +local l=self:GetLength() +if l~=0 then +self:Replace(self/l) +end +return self +end +function VECTOR:Translate(Distance,Heading,Copy) +Distance=Distance or 1000 +local alpha=math.rad(Heading or 0) +local vector=Copy and self:Copy()or self +vector.x=Distance*math.cos(alpha)+vector.x +vector.z=Distance*math.sin(alpha)+vector.z +return vector +end +function VECTOR:Rotate2D(Angle,Copy) +local phi=-math.rad(Angle or 0) +local sinPhi=math.sin(phi) +local cosPhi=math.cos(phi) +local X=self.z +local Y=self.x +local z=X*cosPhi-Y*sinPhi +local x=X*sinPhi+Y*cosPhi +if Copy then +local vector=VECTOR:New(x,self.y,z) +return vector +else +self:SetX(x) +self:SetZ(z) +return self +end +end +function VECTOR: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) +local text="MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy) +return text +end +function VECTOR:GetSurfaceType() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +return s +end +function VECTOR:GetSurfaceTypeName() +local vec2=self:GetVec2() +local s=land.getSurfaceType(vec2) +for name,id in land.SurfaceType()do +if id==s then +return name +end +end +return"unknown" +end +function VECTOR:IsVisible(Vec) +local vec1=self:GetVec3() +local vec2={x=Vec.x,Vec.y,Vec.z} +local los=land.isVisible(vec1,vec2) +return los +end +function VECTOR:GetClosestRoad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('roads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetClosestRailroad() +local vec2=self:GetVec2() +local x,y=land.getClosestPointOnRoads('railroads',vec2.x,vec2.y) +local road=nil +if x and y then +road=VECTOR:New(x,y) +end +return road +end +function VECTOR:GetPathOnRoad(Vec) +local vec1=self:GetVec2() +local vec2=Vec:GetVec2() +local vec2points=land.findPathOnRoads("roads",vec1.x,vec1.y,vec2.x,vec2.y) +local path=nil +if vec2points then +path=PATHLINE:NewFromVec2Array("Road",vec2points) +end +return path +end +function VECTOR:GetProfile(Vec3) +local vec3=self:GetVec3() +local vec3s=land.profile(vec3,Vec3) +local profile=nil +if vec3s then +profile=PATHLINE:NewFromVec3Array("Profile",vec3s) +end +return profile +end +function VECTOR:GetInterceptPoint(DirectionVector,Distance) +local vec3=self:GetVec3() +local ip3=land.getIP(vec3,DirectionVector,Distance or 1000) +local ipvector=nil +if ip3 then +ipvector=VECTOR:New(ip3.x,ip3.y,ip3.z) +end +return ipvector +end +function VECTOR:GetSurfaceHeight() +local vec2=self:GetVec2() +local h=land.getHeight(vec2) +return h +end +function VECTOR:GetSurfaceHeightAndDepth() +local vec2=self:GetVec2() +local h,d=land.getSurfaceHeightWithSeabed(vec2) +return h,d +end +function VECTOR:GetWindVector(WithTurbulence) +local vec3=self:GetVec3() +local wind=nil +if WithTurbulence then +wind=atmosphere.getWindWithTurbulence(vec3) +else +wind=atmosphere.getWind(vec3) +end +local vector=VECTOR:New(wind) +return vector +end +function VECTOR:GetTemperaturAndPressure() +local vec3=self:GetVec3() +local t,p=atmosphere.getTemperatureAndPressure(vec3) +return t,p +end +function VECTOR:Smoke(Color,Duration) +local vec3=self:GetVec3() +Color=Color or 0 +local name=string.format("Vector-Smoke-%d",self.uid) +trigger.action.smoke(vec3,Color,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:SmokeAndFire(Preset,Density,Duration) +Preset=Preset or BIGSMOKEPRESET.LargeSmokeAndFire +Density=Density or 0.5 +local vec3=self:GetVec3() +local name=string.format("Vector-Fire-%d",self.uid) +trigger.action.effectSmokeBig(vec3,Preset,Density,name) +if Duration and Duration>0 then +self:StopSmoke(name,Duration) +end +return name +end +function VECTOR:StopSmoke(Name,Delay) +if Delay and Delay>0 then +TIMER:New(VECTOR.StopSmoke,self,Name):Start(Delay) +else +if Name then +trigger.action.effectSmokeStop(Name) +else +env.error(string.format("No name provided in VECTOR.StopSmoke function!")) +end +end +return self +end +function VECTOR:IlluminationBomb(Power,Altitude) +local vec3=self:GetVec3() +if Altitude then +vec3.y=Altitude +end +trigger.action.illuminationBomb(vec3,Power or 1000) +return self +end +function VECTOR:Explosion(Power) +local vec3=self:GetVec3() +trigger.action.explosion(vec3,Power or 100) +return self +end +function VECTOR:Flare(Color,Azimuth) +local vec3=self:GetVec3() +trigger.action.signalFlare(vec3,Color or 0,math.rad(Azimuth or 0)) +return self +end +function VECTOR:ArrowTo(Vector,Coalition,Color,FillColor,LineType) +local vec3End=self:GetVec3() +local vec3Start=Vector:GetVec3() +local id=UTILS.GetMarkID() +Coalition=Coalition or-1 +Color=Color or{1,0,0,0.7} +FillColor=FillColor or{1,0,0,0.5} +LineType=LineType or 1 +local readOnly=false +trigger.action.arrowToAll(Coalition,id,vec3Start,vec3End,Color,FillColor,LineType,readOnly,"") +return id +end +function VECTOR:Mark(MarkText,Recipient,ReadOnly) +Recipient=Recipient or-1 +if type(Recipient)=="number"then +local MarkID=UTILS.GetMarkID() +if Recipient==-1 then +trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,"") +elseif Recipient==0 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.NEUTRAL,ReadOnly,"") +elseif Recipient==1 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.RED,ReadOnly,"") +elseif Recipient==2 then +trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),coalition.side.BLUE,ReadOnly,"") +end +return MarkID +elseif type(Recipient)=="table"then +local MarkID=UTILS.GetMarkID() +local group=Recipient +trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),group:GetID(),ReadOnly,"") +return MarkID +end +return nil +end +function VECTOR._IsVector(t) +return getmetatable(t)==VECTOR +end +function VECTOR.__add(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__add: wrong argument types! (expected and )") +local c=VECTOR:New(a.x+b.x,a.y+b.y,a.z+b.z) +return c +end +function VECTOR.__sub(a,b) +assert(VECTOR._IsVector(a)and VECTOR._IsVector(b),"ERROR in VECTOR.__sub: wrong argument types: (expected and )") +local c=VECTOR:New(a.x-b.x,a.y-b.y,a.z-b.z) +return c +end +function VECTOR.__mul(a,b) +local c=nil +if type(a)=='number'then +c=VECTOR:New(a*b.x,a*b.y,a*b.z) +elseif type(b)=='number'then +c=VECTOR:New(b*a.x,b*a.y,b*a.z) +else +c=VECTOR:New(a.x*b.x,a.y*b.y,a.z*b.z) +end +return c +end +function VECTOR.__div(a,b) +assert(VECTOR._IsVector(a)and(type(b)=="number"or VECTOR._IsVector(b)),"div: wrong argument types (expected and ( or ))") +local c=nil +if type(b)=="number"then +c=VECTOR:New(a.x/b,a.y/b,a.z/b) +else +c=VECTOR:New(a.x/b.x,a.y/b.y,a.z/b.z) +end +return c +end +function VECTOR.__unm(v) +local c=VECTOR:New(-v.x,-v.y,-v.z) +return c +end +function VECTOR.__eq(a,b) +return a.x==b.x and a.y==b.y and a.z==b.z +end +function VECTOR:__tostring() +local text=string.format("VECTOR: x=%.1f, y=%.1f, z=%.1f |v|=%.1f Phi=%4.1f°",self.x,self.y,self.z,self:GetLength(),self:GetHeading(false)) +return text +end OBJECT={ ClassName="OBJECT", ObjectName="", @@ -22932,6 +24278,15 @@ end self:F(self.ClassName.." "..self.IdentifiableName.." not found!") return nil end +function IDENTIFIABLE:IsRed() +return self:GetCoalition()==coalition.side.RED +end +function IDENTIFIABLE:IsBlue() +return self:GetCoalition()==coalition.side.BLUE +end +function IDENTIFIABLE:IsNeutral() +return self:GetCoalition()==coalition.side.NEUTRAL +end function IDENTIFIABLE:GetCoalitionName() self:F2(self.IdentifiableName) local DCSIdentifiable=self:GetDCSObject() @@ -23079,6 +24434,18 @@ BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()}) return nil end end +function POSITIONABLE:GetOrientationVectors() +local position=self:GetPosition() +if position then +local vecx=VECTOR:NewFromVec(position.x) +local vecy=VECTOR:NewFromVec(position.y) +local vecz=VECTOR:NewFromVec(position.z) +return vecx,vecy,vecz +else +BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()}) +return nil,nil,nil +end +end function POSITIONABLE:GetPositionVec3() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23116,6 +24483,16 @@ end self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local Vec3=DCSPositionable:getPoint() +local vector=VECTOR:NewFromVec(Vec3) +return vector +end +self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetPointVec2() self:F2(self.PositionableName) local DCSPositionable=self:GetDCSObject() @@ -23147,6 +24524,7 @@ function POSITIONABLE:GetCoord() local DCSPositionable=self:GetDCSObject() if DCSPositionable then local PositionableVec3=self:GetVec3() +if PositionableVec3 then if self.coordinate then self.coordinate:UpdateFromVec3(PositionableVec3) else @@ -23154,6 +24532,7 @@ self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) end return self.coordinate end +end BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -23314,12 +24693,18 @@ end BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()}) return nil end -function POSITIONABLE:GetAltitude() +function POSITIONABLE:GetAltitude(FromGround) self:F2() local DCSPositionable=self:GetDCSObject() if DCSPositionable then -local PositionablePointVec3=DCSPositionable:getPoint() -return PositionablePointVec3.y +local altitude=0 +local point=DCSPositionable: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 BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()}) return nil @@ -23439,6 +24824,16 @@ end BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()}) return nil end +function POSITIONABLE:GetVelocityVector() +local DCSPositionable=self:GetDCSObject() +if DCSPositionable and DCSPositionable:isExist()then +local vec3=DCSPositionable:getVelocity() +local vector=VECTOR:NewFromVec(vec3) +return vector +end +BASE:E({"Cannot GetVelocityVector",Positionable=self,Alive=self:IsAlive()}) +return nil +end function POSITIONABLE:GetRelativeVelocity(Positionable) self:F2(self.PositionableName) local v1=self:GetVelocityVec3() @@ -23934,6 +25329,7 @@ POSITIONABLE.CargoBayCapacityValues={ ["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight, ["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight, +["Sd_Kfz_251"]=10*POSITIONABLE.DefaultInfantryWeight, } } function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) @@ -26196,6 +27592,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -27852,33 +29288,45 @@ end function GROUP:GetTypeName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return(GroupTypeName) end +end return nil end function GROUP:GetNatoReportingName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupTypeName=unit:getTypeName() return UTILS.GetReportingName(GroupTypeName) end +end return"Bogey" end function GROUP:GetPlayerName() local DCSGroup=self:GetDCSObject() if DCSGroup then -local PlayerName=DCSGroup:getUnit(1):getPlayerName() +local unit=DCSGroup:getUnit(1) +if unit then +local PlayerName=unit:getPlayerName() return(PlayerName) end +end return nil end function GROUP:GetCallsign() local DCSGroup=self:GetDCSObject() if DCSGroup then -local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +local unit=DCSGroup:getUnit(1) +if unit then +local GroupCallSign=unit:getCallsign() return GroupCallSign end +end BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) return nil end @@ -27898,6 +29346,15 @@ end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) return nil end +function GROUP:GetVector() +local unit=self:GetUnit(1) +if unit then +local vector=unit:GetVector() +return vector +end +self:E("ERROR: Cannot get Vector 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 @@ -28549,7 +30006,7 @@ function GROUP:GetTaskMission() return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) end function GROUP:GetTaskRoute() -if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then +if _DATABASE.Templates.Groups[self.GroupName]and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) else return{} @@ -28563,7 +30020,7 @@ GroupName=GroupName:sub(1,-2) else GroupName=self:GetName() end -local Template=_DATABASE.Templates.Groups[GroupName].Template +local Template=_DATABASE.Templates.Groups[GroupName]and _DATABASE.Templates.Groups[GroupName].Template or nil if Template then if not Begin then Begin=0 @@ -28585,7 +30042,7 @@ end end return Points else -error("Template not found for Group : "..GroupName) +BASE:E("Template not found for Group : "..GroupName) end return nil end @@ -28828,6 +30285,10 @@ DCSUnit:enableEmission(switch) end return self end +function GROUP:SetFormation(Formation) +self:SetOption(AI.Option.Air.id.FORMATION,Formation) +return self +end function GROUP:SetCommandInvisible(switch) return self:CommandSetInvisible(switch) end @@ -29954,6 +31415,20 @@ end function UNIT:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits=Enabled end +function UNIT:GetFuelMassMax() +local Desc=self:GetDesc()or{} +local massFuelMax=Desc.fuelMassMax or 0 +local relFuel=math.min(self:GetFuel()or 1.0,1.0) +local massFuel=massFuelMax*relFuel +return massFuel,massFuelMax +end +function UNIT:GetCurrentFuelKgs() +local fuel,maxfuel=self:GetFuelMassMax() +local relfuel=self:GetFuel() +local maxfilling=math.max(fuel,maxfuel) +local mass=maxfilling*relfuel +return mass +end CLIENT={ ClassName="CLIENT", ClientName=nil, @@ -30261,6 +31736,7 @@ self.Life0=Life0 else self:E(string.format("Static object %s does not exist!",tostring(self.StaticName))) end +self._vec3=self:GetVec3() return self end function STATIC:GetLife0() @@ -30273,6 +31749,20 @@ return DCSStatic:getLife()or 1 end return nil end +function STATIC:GetVec2Cached() +local vec2=self:GetVec2() +if not vec2 and self._vec3 then +vec2={x=self._vec3.x,y=self._vec3.z} +end +return vec2 +end +function STATIC:GetVec3Cached() +local vec3=self:GetVec3() +if not vec3 and self._vec3 then +vec3=self._vec3 +end +return vec3 +end function STATIC:Find(DCSStatic) local StaticName=DCSStatic:getName() local StaticFound=_DATABASE:FindStatic(StaticName) @@ -30338,6 +31828,7 @@ SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30348,6 +31839,7 @@ else CountryID=CountryID or self:GetCountry() local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) SpawnStatic:Spawn(nil,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30357,6 +31849,7 @@ SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) else local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +self._vec3=self:GetVec3() end return self end @@ -30446,6 +31939,7 @@ AIRBASE.Nevada={ } AIRBASE.Normandy={ ["Abbeville_Drucat"]="Abbeville Drucat", +["Alderney"]="Alderney", ["Amiens_Glisy"]="Amiens-Glisy", ["Argentan"]="Argentan", ["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere", @@ -30454,6 +31948,7 @@ AIRBASE.Normandy={ ["Bazenville"]="Bazenville", ["Beaumont_le_Roger"]="Beaumont-le-Roger", ["Beauvais_Tille"]="Beauvais-Tille", +["Bembridg"]="Bembridg", ["Beny_sur_Mer"]="Beny-sur-Mer", ["Bernay_Saint_Martin"]="Bernay Saint Martin", ["Beuzeville"]="Beuzeville", @@ -30476,6 +31971,7 @@ AIRBASE.Normandy={ ["Deux_Jumeaux"]="Deux Jumeaux", ["Dinan_Trelivan"]="Dinan-Trelivan", ["Dunkirk_Mardyck"]="Dunkirk-Mardyck", +["Eastchurch"]="Eastchurch", ["Essay"]="Essay", ["Evreux"]="Evreux", ["Farnborough"]="Farnborough", @@ -30486,12 +31982,18 @@ AIRBASE.Normandy={ ["Funtington"]="Funtington", ["Goulet"]="Goulet", ["Gravesend"]="Gravesend", +["Guernsey"]="Guernsey", ["Guyancourt"]="Guyancourt", ["Hauterive"]="Hauterive", +["Hawkinge"]="Hawkinge", +["Headcorn"]="Headcorn", ["Heathrow"]="Heathrow", ["High_Halden"]="High Halden", +["Holmsley_South"]="Holmsley South", +["Jersey"]="Jersey", ["Kenley"]="Kenley", ["Lantheuil"]="Lantheuil", +["Lashenden"]="Lashenden", ["Le_Molay"]="Le Molay", ["Lessay"]="Lessay", ["Lignerolles"]="Lignerolles", @@ -30504,6 +32006,7 @@ AIRBASE.Normandy={ ["Meautis"]="Meautis", ["Merville_Calonne"]="Merville Calonne", ["Needs_Oar_Point"]="Needs Oar Point", +["Northolt"]="Northolt", ["Odiham"]="Odiham", ["Orly"]="Orly", ["Picauville"]="Picauville", @@ -30511,10 +32014,11 @@ AIRBASE.Normandy={ ["Ronai"]="Ronai", ["Rouen_Boos"]="Rouen-Boos", ["Rucqueville"]="Rucqueville", +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", ["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", +["Saint_Pol_Bryas"]="Saint-Pol-Bryas", ["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", ["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", ["Sommervieu"]="Sommervieu", @@ -30524,18 +32028,15 @@ AIRBASE.Normandy={ ["Villacoublay"]="Villacoublay", ["Vrigny"]="Vrigny", ["West_Malling"]="West Malling", -["Eastchurch"]="Eastchurch", -["Headcorn"]="Headcorn", -["Hawkinge"]="Hawkinge", } 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", +["Al_Bateen"]="Al-Bateen", ["Bandar_Abbas_Intl"]="Bandar Abbas Intl", ["Bandar_Lengeh"]="Bandar Lengeh", ["Bandar_e_Jask"]="Bandar-e-Jask", @@ -30578,13 +32079,14 @@ AIRBASE.Syria={ ["Abu_al_Duhur"]="Abu al-Duhur", ["Adana_Sakirpasa"]="Adana Sakirpasa", ["Akrotiri"]="Akrotiri", -["Al_Dumayr"]="Al-Dumayr", ["Al_Qusayr"]="Al Qusayr", +["Al_Dumayr"]="Al-Dumayr", ["Aleppo"]="Aleppo", ["An_Nasiriyah"]="An Nasiriyah", ["At_Tanf"]="At Tanf", ["Bassel_Al_Assad"]="Bassel Al-Assad", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["Ben_Gurion"]="Ben Gurion", ["Damascus"]="Damascus", ["Deir_ez_Zor"]="Deir ez-Zor", ["Ercan"]="Ercan", @@ -30600,6 +32102,7 @@ AIRBASE.Syria={ ["Haifa"]="Haifa", ["Hama"]="Hama", ["Hatay"]="Hatay", +["Hatzor"]="Hatzor", ["Herzliya"]="Herzliya", ["Incirlik"]="Incirlik", ["Jirah"]="Jirah", @@ -30612,16 +32115,17 @@ AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Lakatamia"]="Lakatamia", ["Larnaca"]="Larnaca", -["Marka"]="Marka", ["Marj_Ruhayyil"]="Marj Ruhayyil", ["Marj_as_Sultan_North"]="Marj as Sultan North", ["Marj_as_Sultan_South"]="Marj as Sultan South", +["Marka"]="Marka", ["Megiddo"]="Megiddo", ["Mezzeh"]="Mezzeh", ["Minakh"]="Minakh", ["Muwaffaq_Salti"]="Muwaffaq Salti", ["Naqoura"]="Naqoura", ["Nicosia"]="Nicosia", +["Palmachim"]="Palmachim", ["Palmyra"]="Palmyra", ["Paphos"]="Paphos", ["Pinarbashi"]="Pinarbashi", @@ -30638,17 +32142,14 @@ AIRBASE.Syria={ ["Tabqa"]="Tabqa", ["Taftanaz"]="Taftanaz", ["Tal_Siman"]="Tal Siman", +["Tel_Nof"]="Tel Nof", ["Tha_lah"]="Tha'lah", ["Tiyas"]="Tiyas", ["Wujah_Al_Hajar"]="Wujah Al Hajar", -["Ben_Gurion"]="Ben Gurion", -["Hatzor"]="Hatzor", -["Palmachim"]="Palmachim", -["Tel_Nof"]="Tel Nof", } AIRBASE.MarianaIslands={ ["Andersen_AFB"]="Andersen AFB", -["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", +["Antonio_B._Won_Pat_Intl"]="Antonio B. Won Pat Intl", ["North_West_Field"]="North West Field", ["Olf_Orote"]="Olf Orote", ["Pagan_Airstrip"]="Pagan Airstrip", @@ -30656,8 +32157,7 @@ AIRBASE.MarianaIslands={ ["Saipan_Intl"]="Saipan Intl", ["Tinian_Intl"]="Tinian Intl", } -AIRBASE.MarianaIslandsWWII= -{ +AIRBASE.MarianaIslandsWWII={ ["Agana"]="Agana", ["Airfield_3"]="Airfield 3", ["Charon_Kanoa"]="Charon Kanoa", @@ -30720,6 +32220,7 @@ AIRBASE.Sinai={ ["Cairo_West"]="Cairo West", ["Damascus_Intl"]="Damascus Intl", ["Difarsuwar_Airfield"]="Difarsuwar Airfield", +["Ein_Shamer"]="Ein Shamer", ["El_Arish"]="El Arish", ["El_Gora"]="El Gora", ["El_Minya"]="El Minya", @@ -30731,8 +32232,11 @@ AIRBASE.Sinai={ ["Inshas_Airbase"]="Inshas Airbase", ["Jiyanklis_Air_Base"]="Jiyanklis Air Base", ["Kedem"]="Kedem", +["Khalkhalah_Air_Base"]="Khalkhalah Air Base", ["Kibrit_Air_Base"]="Kibrit Air Base", +["King_Feisal_Air_Base"]="King Feisal Air Base", ["Kom_Awshim"]="Kom Awshim", +["Megiddo"]="Megiddo", ["Melez"]="Melez", ["Mezzeh_Air_Base"]="Mezzeh Air Base", ["Nevatim"]="Nevatim", @@ -30746,45 +32250,50 @@ AIRBASE.Sinai={ ["Sde_Dov"]="Sde Dov", ["Sharm_El_Sheikh_International_Airport"]="Sharm El Sheikh International Airport", ["St_Catherine"]="St Catherine", +["Taba_International_Airport"]="Taba International Airport", ["Tabuk"]="Tabuk", +["TabukHeliBase"]="TabukHeliBase", ["Tel_Nof"]="Tel Nof", ["Wadi_Abu_Rish"]="Wadi Abu Rish", ["Wadi_al_Jandali"]="Wadi al Jandali", } AIRBASE.Kola={ +["Afrikanda"]="Afrikanda", +["Alakurtti"]="Alakurtti", +["Alta"]="Alta", +["Andoya"]="Andoya", +["Arvidsjaur"]="Arvidsjaur", ["Banak"]="Banak", +["Bardufoss"]="Bardufoss", +["Boden_Heli_Base"]="Boden Heli Base", ["Bodo"]="Bodo", +["Enontekio"]="Enontekio", +["Evenes"]="Evenes", +["Hemavan"]="Hemavan", +["Hosio"]="Hosio", ["Ivalo"]="Ivalo", ["Jokkmokk"]="Jokkmokk", +["Kalevala"]="Kalevala", ["Kalixfors"]="Kalixfors", ["Kallax"]="Kallax", ["Kemi_Tornio"]="Kemi Tornio", +["Kilpyavr"]="Kilpyavr", ["Kirkenes"]="Kirkenes", ["Kiruna"]="Kiruna", +["Kittila"]="Kittila", +["Koshka_Yavr"]="Koshka Yavr", ["Kuusamo"]="Kuusamo", +["Luostari_Pechenga"]="Luostari Pechenga", ["Monchegorsk"]="Monchegorsk", ["Murmansk_International"]="Murmansk International", ["Olenya"]="Olenya", +["Poduzhemye"]="Poduzhemye", ["Rovaniemi"]="Rovaniemi", ["Severomorsk_1"]="Severomorsk-1", ["Severomorsk_3"]="Severomorsk-3", +["Sodankyla"]="Sodankyla", ["Vidsel"]="Vidsel", ["Vuojarvi"]="Vuojarvi", -["Andoya"]="Andoya", -["Alakurtti"]="Alakurtti", -["Kittila"]="Kittila", -["Bardufoss"]="Bardufoss", -["Alta"]="Alta", -["Sodankyla"]="Sodankyla", -["Enontekio"]="Enontekio", -["Evenes"]="Evenes", -["Hosio"]="Hosio", -["Kilpyavr"]="Kilpyavr", -["Afrikanda"]="Afrikanda", -["Kalevala"]="Kalevala", -["Koshka_Yavr"]="Koshka Yavr", -["Poduzhemye"]="Poduzhemye", -["Luostari_Pechenga"]="Luostari Pechenga", } AIRBASE.Afghanistan={ ["Bagram"]="Bagram", @@ -30794,6 +32303,10 @@ AIRBASE.Afghanistan={ ["Camp_Bastion_Heliport"]="Camp Bastion Heliport", ["Chaghcharan"]="Chaghcharan", ["Dwyer"]="Dwyer", +["FOB_Camp_Dubs"]="FOB Camp Dubs", +["FOB_Clark"]="FOB Clark", +["FOB_Salerno"]="FOB Salerno", +["FOB_Thunder"]="FOB Thunder", ["Farah"]="Farah", ["Gardez"]="Gardez", ["Ghazni_Heliport"]="Ghazni Heliport", @@ -30803,7 +32316,6 @@ AIRBASE.Afghanistan={ ["Kandahar"]="Kandahar", ["Kandahar_Heliport"]="Kandahar Heliport", ["Khost"]="Khost", -["Khost_Heliport"]="Khost Heliport", ["Maymana_Zahiraddin_Faryabi"]="Maymana Zahiraddin Faryabi", ["Nimroz"]="Nimroz", ["Qala_i_Naw"]="Qala i Naw", @@ -30814,21 +32326,28 @@ AIRBASE.Afghanistan={ ["Urgoon_Heliport"]="Urgoon Heliport", } AIRBASE.Iraq={ -["Baghdad_International_Airport"]="Baghdad International Airport", -["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", -["Al_Sahra_Airport"]="Al-Sahra Airport", -["Erbil_International_Airport"]="Erbil International Airport", -["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Asad_Airbase"]="Al-Asad Airbase", +["Al_Kut_Airport"]="Al-Kut Airport", +["Al_Sahra_Airport"]="Al-Sahra Airport", ["Al_Salam_Airbase"]="Al-Salam Airbase", -["Balad_Airbase"]="Balad Airbase", -["Kirkuk_International_Airport"]="Kirkuk International Airport", -["Bashur_Airport"]="Bashur Airport", +["Al_Taji_Airport"]="Al-Taji Airport", ["Al_Taquddum_Airport"]="Al-Taquddum Airport", -["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Baghdad_International_Airport"]="Baghdad International Airport", +["Balad_Airbase"]="Balad Airbase", +["Bashur_Airport"]="Bashur Airport", +["Erbil_International_Airport"]="Erbil International Airport", +["H_2_Airbase"]="H-2 Airbase", +["H_3_Main_Airbase"]="H-3 Main Airbase", +["H_3_Northwest_Airbase"]="H-3 Northwest Airbase", +["H_3_Southwest_Airbase"]="H-3 Southwest Airbase", ["K1_Base"]="K1 Base", +["Kirkuk_International_Airport"]="Kirkuk International Airport", +["Mosul_International_Airport"]="Mosul International Airport", +["Qayyarah_Airfield_West"]="Qayyarah Airfield West", +["Sulaimaniyah_International_Airport"]="Sulaimaniyah International Airport", } AIRBASE.GermanyCW={ +["Adelsheim"]="Adelsheim", ["Airracing_Frankfurt"]="Airracing Frankfurt", ["Airracing_Koblenz"]="Airracing Koblenz", ["Airracing_Luebeck"]="Airracing Lubeck", @@ -30839,16 +32358,23 @@ AIRBASE.GermanyCW={ ["Bienenfarm"]="Bienenfarm", ["Bindersleben"]="Bindersleben", ["Bitburg"]="Bitburg", +["Bornholm"]="Bornholm", +["Brand"]="Brand", +["Brandis"]="Brandis", ["Braunschweig"]="Braunschweig", ["Bremen"]="Bremen", ["Briest"]="Briest", ["Buechel"]="Buchel", ["Bueckeburg"]="Buckeburg", ["Celle"]="Celle", +["Chojna"]="Chojna", ["Cochstedt"]="Cochstedt", +["Cologne"]="Cologne", ["Damgarten"]="Damgarten", ["Dedelow"]="Dedelow", ["Dessau"]="Dessau", +["Duesseldorf"]="Dusseldorf", +["Falkenberg"]="Falkenberg", ["Fassberg"]="Fassberg", ["Finow"]="Finow", ["Frankfurt"]="Frankfurt", @@ -30859,7 +32385,7 @@ AIRBASE.GermanyCW={ ["Gatow"]="Gatow", ["Gelnhausen"]="Gelnhausen", ["Giebelstadt"]="Giebelstadt", -["Glindbruchkippe"]="Glindbruchkippe ", +["Glindbruchkippe"]="Glindbruchkippe", ["Gross_Mohrdorf"]="Gross Mohrdorf", ["Grosse_Wiese"]="Grosse Wiese", ["Guetersloh"]="Gutersloh", @@ -30977,43 +32503,60 @@ AIRBASE.GermanyCW={ ["Hamburg_Finkenwerder"]="Hamburg Finkenwerder", ["Hannover"]="Hannover", ["Hasselfelde"]="Hasselfelde", +["Heidelberg"]="Heidelberg", ["Herrenteich"]="Herrenteich", ["Hildesheim"]="Hildesheim", ["Hockenheim"]="Hockenheim", ["Holzdorf"]="Holzdorf", ["Kammermark"]="Kammermark", +["Kastrup"]="Kastrup", +["Kiel"]="Kiel", ["Koethen"]="Kothen", ["Laage"]="Laage", +["Landstuhl"]="Landstuhl", ["Langenselbold"]="Langenselbold", ["Laerz"]="Larz", -["Leipzig_Halle"]="Leipzig Halle", ["Leipzig_Mockau"]="Leipzig Mockau", ["Luebeck"]="Lubeck", ["Lueneburg"]="Luneburg", ["Mahlwinkel"]="Mahlwinkel", +["Mainz_Finthen"]="Mainz Finthen", +["Marxwalde"]="Marxwalde", ["Mendig"]="Mendig", ["Merseburg"]="Merseburg", ["Neubrandenburg"]="Neubrandenburg", ["Neuruppin"]="Neuruppin", +["Nordholz"]="Nordholz", ["Northeim"]="Northeim", +["Noervenich"]="Norvenich", ["Ober_Moerlen"]="Ober-Morlen", ["Obermehler_Schlotheim"]="Obermehler Schlotheim", +["Oranienburg"]="Oranienburg", ["Parchim"]="Parchim", ["Peenemuende"]="Peenemunde", +["Perwenitz"]="Perwenitz", ["Pferdsfeld"]="Pferdsfeld", ["Pinnow"]="Pinnow", ["Pottschutthoehe"]="Pottschutthohe", ["Ramstein"]="Ramstein", +["Revinge"]="Revinge", ["Rinteln"]="Rinteln", +["Schkeuditz"]="Schkeuditz", ["Schoenefeld"]="Schonefeld", ["Schweinfurt"]="Schweinfurt", ["Sembach"]="Sembach", +["Sittensen"]="Sittensen", ["Spangdahlem"]="Spangdahlem", ["Sperenberg"]="Sperenberg", +["Sprendlingen"]="Sprendlingen", ["Stendal"]="Stendal", +["Sturup"]="Sturup", +["Szczecin_Goleniow"]="Szczecin-Goleniow", +["Tagra"]="Tagra", ["Tegel"]="Tegel", ["Tempelhof"]="Tempelhof", ["Templin"]="Templin", +["Thurland"]="Thurland", ["Tutow"]="Tutow", ["Uelzen"]="Uelzen", ["Uetersen"]="Uetersen", @@ -31029,6 +32572,7 @@ AIRBASE.GermanyCW={ ["Worms"]="Worms", ["Wunstorf"]="Wunstorf", ["Zerbst"]="Zerbst", +["Zoellschen"]="Zollschen", ["Zweibruecken"]="Zweibrucken", } AIRBASE.TerminalType={ @@ -31096,9 +32640,19 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName)) end +if Nrunways>0 then +self:GetMinimumBoundingCircleFromParkingSpots() +end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -31345,6 +32899,33 @@ end end return spots end +function AIRBASE:GetParkingSpotsVec2s(termtype) +local parkingdata=self:GetParkingData(false) +local spots={} +for _,parkingspot in ipairs(parkingdata)do +if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then +local vec2={x=parkingspot.vTerminalPos.x,y=parkingspot.vTerminalPos.z} +table.insert(spots,vec2) +end +end +return spots +end +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) +if self.isAirdrome then +if not self.parkingCircle then +local spots=self:GetParkingSpotsVec2s() +if#spots==0 then return self.AirbaseZone end +local center,radius=UTILS.GetMinimumBoundingCircle(spots) +self.parkingCircle=ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) +if mark==true then +self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) +end +end +return self.parkingCircle +else +return self.AirbaseZone +end +end function AIRBASE:_InitParkingSpots() local parkingdata=self:GetParkingData(false) self.parking={} @@ -32036,16 +33617,55 @@ end SCENERY={ ClassName="SCENERY", } -function SCENERY:Register(SceneryName,SceneryObject) +_SCENERY={} +function SCENERY:Register(SceneryName,SceneryObject,SceneryZone) +local ID=(SceneryObject and SceneryObject.getID)and SceneryObject:getID()or SceneryName +if _SCENERY[ID]and _SCENERY[ID].SceneryObject==nil then +_SCENERY[ID].SceneryObject=SceneryObject +SCENERY._UpdateFromDCSObject(_SCENERY[ID]) +end +if _SCENERY[ID]then return _SCENERY[ID]end local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) self.SceneryName=tostring(SceneryName) +self.ID=ID self.SceneryObject=SceneryObject +self.SceneryZone=SceneryZone +if SceneryZone then +self.Vec3=SceneryZone:GetVec3() +self.Vec2=SceneryZone:GetVec2() +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end +if SceneryObject and SceneryObject.getPoint then +local vec3=SceneryObject:getPoint() +self.Vec3={x=vec3.x,y=vec3.y,z=vec3.z} +self.Vec2={x=vec3.x,y=vec3.z} +self.Vector=(self.Vec3 and VECTOR)and VECTOR:NewFromVec(self.Vec3)or nil +end if self.SceneryObject and self.SceneryObject.getLife then -self.Life0=self.SceneryObject:getLife()or 0 +self.Life0=self.SceneryObject:getLife()or 1 else -self.Life0=0 +self.Life0=1 end self.Properties={} +_SCENERY[self.ID]=self +return self +end +function SCENERY._UpdateFromDCSObject(Scenery) +env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) +local self=Scenery +if self.Vec2==nil and self.SceneryObject~=nil then +self.Vec3=self.SceneryObject:getPoint() +if self.Vec3 then +self.Vec2={x=self.Vec3.x,y=self.Vec3.z} +self.Vector=VECTOR:NewFromVec(self.Vec3) +end +end +if not self.Life0 or self.Life0==1 then +if self.SceneryObject and self.SceneryObject.getLife()then +self.Life=self.SceneryObject:getLife()or 1 +self.Life0=self.Life +end +end return self end function SCENERY:GetProperty(PropertyName) @@ -32064,16 +33684,38 @@ end function SCENERY:GetName() return self.SceneryName end +function SCENERY:GetCoordinate() +if self.Coordinate then +return self.Coordinate +elseif self.Vec3 then +self.Coordinate=COORDINATE:NewFromVec3(self.Vec3):SetAlt() +end +return self.Coordinate +end +function SCENERY:GetVec3() +return self.Vec3 +end +function SCENERY:GetVec2() +return self.Vec2 +end +function SCENERY:GetVector() +return self.Vector +end function SCENERY:GetDCSObject() return self.SceneryObject end +function SCENERY:GetID() +return self.ID +end function SCENERY:GetLife() -local life=0 +local life=1 if self.SceneryObject and self.SceneryObject.getLife then life=self.SceneryObject:getLife() if life>self.Life0 then self.Life0=math.floor(life*1.2) end +elseif self.Life then +life=self.Life end return life end @@ -32104,14 +33746,16 @@ end function SCENERY:GetThreatLevel() return 0,"Scenery" end -function SCENERY:FindByName(Name,Coordinate,Radius,Role) +function SCENERY:FindByName(Name,Coordinate,Radius,Role,Zone) +local findme=self:_FindByName(Name) +if findme then return findme end 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) +local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned=scanzone:GetScannedSceneryObjects() local rscenery=nil @@ -32130,16 +33774,29 @@ end if Coordinate then scenery=SceneryScan(Coordinate,radius,name) end +if not scenery then scenery=SCENERY:Register(Name,nil,Zone)end return scenery end +function SCENERY:FindByID(ID) +return _SCENERY[ID] +end +function SCENERY:_FindByName(Name) +for _id,_object in pairs(_SCENERY)do +if _object and _object.GetName and _object:GetName()then +local name=_object:GetName() +if Name==name then return _object end +end +end +return nil +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")) +local coordinate=Zone:GetCoordinate():SetAlt() +return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end function SCENERY:FindByZoneName(ZoneName) local zone=ZoneName @@ -32154,18 +33811,20 @@ 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")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end else -return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local coordinate=zone:GetCoordinate() +coordinate:SetAlt() +return self:FindByName(_id,coordinate,nil,zone:GetProperty("ROLE"),zone) end end function SCENERY:FindAllByZoneName(ZoneName) @@ -32183,7 +33842,7 @@ else return nil end else -local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) +local obj=self:FindByName(_id,zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"),zone) if obj then return{obj} else @@ -33036,7 +34695,7 @@ 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) +local playerName=net.get_name(playerList[i]) if playerName==Name then return playerList[i] end @@ -33508,7 +35167,7 @@ self:SetLiquid(lqno,lqam) end end else -self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) +self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end if self:IsLimitedAircraft()then @@ -33526,7 +35185,7 @@ self:SetAmount(acname,acnumber) end end else -self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) +self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end if self:IsLimitedWeapons()then @@ -33555,7 +35214,7 @@ end end end else -self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) +self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end return self @@ -33626,6 +35285,9 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="Mi-8MTV2", +["Mi-8MT"]="Mi-8MT", +["C-130J-30"]="C-130J-30", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33634,8 +35296,26 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["C-130J-30"]={ +["width"]=4, +["height"]=12, +["length"]=35, +["ropelength"]=0, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.1.0" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -33858,13 +35538,29 @@ end end self.LastPosition=pos else -if self.timer and self.timer:IsRunning()then self.timer:Stop()end +if self.timer and self.timer:IsRunning()then +self.timer:Stop() +self.timer=nil +end self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED") self.CargoState=DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end return self end +function DYNAMICCARGO:Destroy(GenerateEvent) +local DCSObject=self:GetDCSObject() +if DCSObject then +local GenerateEvent=(GenerateEvent~=nil and GenerateEvent==false)and false or true +if GenerateEvent and GenerateEvent==true then +self:CreateEventDead(timer.getTime(),DCSObject) +end +DCSObject:destroy() +self:_UpdatePosition() +return true +end +return nil +end function DYNAMICCARGO._FilterHeloTypes(client) if not client then return false end local typename=client:GetTypeName() @@ -35200,7 +36896,10 @@ ClassName="SCORING", ClassID=0, Players={}, AutoSave=true, -version="1.18.4" +version="1.18.5", +ScoringScenery=nil, +SceneryHitsInZone=false, +LoadSave=false, } local _SCORINGCoalition={ [1]="Red", @@ -35213,12 +36912,12 @@ local _SCORINGCategory={ [Unit.Category.SHIP]="Ship", [Unit.Category.STRUCTURE]="Structure", } -function SCORING:New(GameName,SavePath,AutoSave) +function SCORING:New(GameName,SavePath,AutoSave,LoadSave) 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") +error("A game name must be given to register the scoring results!") end self.ScoringObjects={} self.ScoringZones={} @@ -35235,6 +36934,10 @@ self.penaltyonfratricide=true self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) self.penaltyoncoalitionchange=true self:SetDisplayMessagePrefix() +self.SceneryHitsInZone=false +if LoadSave then +self.LoadSave=LoadSave +end self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) self:HandleEvent(EVENTS.Hit,self._EventOnHit) @@ -35247,8 +36950,53 @@ self:SetScoringMenu(PlayerUnit:GetGroup()) end end) self.AutoSavePath=SavePath -self.AutoSave=AutoSave or true +self.AutoSave=(AutoSave==nil or AutoSave==true)and true or false +if self.AutoSavePath and self.AutoSave==true then self:OpenCSV(GameName) +end +self:I("SCORING "..tostring(GameName).." started! v"..self.version) +if LoadSave==true then +self:_LoadPlayerSummaryScore() +end +return self +end +function SCORING:_LoadPlayerSummaryScore() +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +if UTILS.CheckFileExists(path,filename)then +local ok,data=UTILS.LoadFromFile(path,filename) +table.remove(data,1) +for _,_data in pairs(data)do +local line=UTILS.Split(_data,";;") +local playername=tostring(line[1]) +local score=tonumber(line[2]) +local penalty=tonumber(line[3]) +self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) +local PlayerData=self.Players[playername] +if not PlayerData then +PlayerData={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=score +PlayerData.Penalty=penalty +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[playername]=PlayerData +else +PlayerData.Score=score +PlayerData.Penalty=penalty +self.Players[playername]=PlayerData +PlayerData.Goals[self.GameName]={Score=score,Penalty=penalty} +end +end +end +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -35274,10 +37022,44 @@ self.ScoringObjects[UnitName]=nil return self end function SCORING:AddStaticScore(ScoreStatic,Score) +return self:AddScoreStatic(ScoreStatic,Score) +end +function SCORING:AddScoreStatic(ScoreStatic,Score) +if ScoreStatic==nil then +BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") +return self +end local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=Score return self end +function SCORING:AddScoreScenery(ScoreScenery,Score) +if ScoreScenery==nil then +self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") +return self +end +if not self.ScoringScenery then +self.ScoringScenery=SET_SCENERY:New() +end +local StaticName=ScoreScenery:GetName() +self:T("Scenery name = "..StaticName) +self.ScoringScenery:AddScenery(ScoreScenery) +return self +end +function SCORING:RemoveSceneryScore(ScoreScenery) +local StaticName=ScoreScenery:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreSetScenery(Set,Score) +local set=Set.Set +for _,_static in pairs(set)do +if _static~=nil then +self:AddScoreScenery(_static,Score) +end +end +return self +end function SCORING:RemoveStaticScore(ScoreStatic) local StaticName=ScoreStatic:GetName() self.ScoringObjects[StaticName]=nil @@ -35306,6 +37088,21 @@ AddScore(Object) end return self end +function SCORING:AddScoreSetStatic(Set,Score) +local set=Set:GetSetObjects() +for _,_static in pairs(set)do +if _static and _static:IsAlive()then +self:AddStaticScore(_static,Score) +end +end +local function AddScore(static) +self:AddStaticScore(static,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]={} @@ -35313,6 +37110,20 @@ self.ScoringZones[ZoneName].ScoreZone=ScoreZone self.ScoringZones[ZoneName].Score=Score return self end +function SCORING:EnableSceneryHitsinZones() +self.SceneryHitsInZone=true +return self +end +function SCORING:DisableSceneryHitsinZones() +self.SceneryHitsInZone=false +return self +end +function SCORING:AddZoneScoreSet(ScoreZoneSet,Score) +for _,_zone in pairs(ScoreZoneSet.Set or{})do +self:AddZoneScore(_zone,Score) +end +return self +end function SCORING:RemoveZoneScore(ScoreZone) local ZoneName=ScoreZone:GetName() self.ScoringZones[ZoneName]=nil @@ -35452,6 +37263,20 @@ function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score) self:F({PlayerName,PlayerName,GoalTag,Text,Score}) if PlayerName then local PlayerData=self.Players[PlayerName] +if not PlayerData then +PlayerData={} +PlayerData.Goals={} +PlayerData.Hit={} +PlayerData.Destroy={} +PlayerData.Goals={} +PlayerData.Mission={} +PlayerData.HitPlayers={} +PlayerData.Score=0 +PlayerData.Penalty=0 +PlayerData.PenaltyCoalition=0 +PlayerData.PenaltyWarning=0 +self.Players[PlayerName]=PlayerData +end PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0} PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score PlayerData.Score=PlayerData.Score+Score @@ -35562,7 +37387,6 @@ end end end function SCORING:_EventOnHit(Event) -self:F({Event}) local InitUnit=nil local InitUNIT=nil local InitUnitName="" @@ -35588,6 +37412,7 @@ local TargetUnitCoalition=nil local TargetUnitCategory=nil local TargetUnitType=nil local TargetIsScenery=false +local TargetSceneryObject=nil if Event.IniDCSUnit then InitUnit=Event.IniDCSUnit InitUNIT=Event.IniUnit @@ -35601,7 +37426,6 @@ 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 @@ -35613,14 +37437,16 @@ TargetPlayerName=Event.TgtPlayerName TargetCoalition=Event.TgtCoalition TargetCategory=Event.TgtCategory TargetType=Event.TgtTypeName -if(not TargetCategory)and TargetUNIT~=nil and TargetUnit:IsInstanceOf("SCENERY")then +if TargetUNIT~=nil and TargetUNIT:IsInstanceOf("SCENERY")then TargetCategory=Unit.Category.STRUCTURE TargetIsScenery=true +TargetType="Scenery" +TargetSceneryObject=TargetUNIT +self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") end 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) @@ -35629,7 +37455,7 @@ if TargetPlayerName~=nil then self:_AddPlayerFromUnit(TargetUNIT) end self:T("Hitting Something") -if TargetCategory then +if(TargetCategory~=nil)and(TargetIsScenery==false)then local Player=self.Players[InitPlayerName] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} @@ -35640,7 +37466,7 @@ 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 +if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType==nil then PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then PlayerHit.ThreatLevel=1 @@ -35712,15 +37538,12 @@ 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] +if Event.WeaponPlayerName~=nil or TargetIsScenery==true then +local playername=Event.WeaponPlayerName or Event.IniPlayerName or"Ghost" +if self.Players[playername]then +self:T("Hitting Scenery or Static") +if Event.TgtObjectCategory then +local Player=self.Players[playername] Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{} Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{} local PlayerHit=Player.Hit[TargetCategory][TargetUnitName] @@ -35730,55 +37553,66 @@ 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 +if PlayerHit.UNIT and 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 +PlayerHit.ThreatLevel=PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 +PlayerHit.ThreatType=PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or"Unknown" 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 +local TgtName=Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName()or"Unknown" +if TargetIsScenery==true and self.ScoringScenery:IsInSet(TargetSceneryObject)then 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.." ) ".. +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit scenery 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.", +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +elseif TargetIsScenery==false and Event.TgtObjectCategory==Object.Category.STATIC and self.ScoringObjects[TgtName]then +Player.Score=Player.Score+self.ScoreIncrementOnHit +PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..playername.."' hit static target "..TargetUnitCategory.." ( "..TargetType.." ) ".. +"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()) -self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType) +:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition()) +self:ScoreCSV(playername,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +else +self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") end end end end +for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do +self:F({ScoringZone=ScoreZoneData}) +local hit=Event.TgtUnit +local ScoreZone=ScoreZoneData.ScoreZone +local Score=ScoreZoneData.Score +if TargetUNIT and ScoreZone:IsVec2InZone(TargetUNIT:GetVec2())then +local PlayerName=Event.IniPlayerName or"Ghost" +local Player=self.Players[PlayerName] +if Player then +Player.Score=Player.Score+Score +Player.Score=Player.Score+self.ScoreIncrementOnHit +MESSAGE:NewType(self.DisplayMessagePrefix.."hit 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,"","HIT_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Zone",TargetUnitType) +end +end +end end end function SCORING:_EventOnDeadOrCrash(Event) @@ -35819,7 +37653,7 @@ 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 +if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and TargetUnit 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{} @@ -35918,6 +37752,7 @@ end end end else +if self.SceneryHitsInZone==true then for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do self:F({ScoringZone=ScoreZoneData}) local ScoreZone=ScoreZoneData.ScoreZone @@ -35935,6 +37770,7 @@ Destroyed=true end end end +end if Destroyed then Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0 end @@ -36173,8 +38009,9 @@ MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup) end end end -function SCORING:ReportScoreAllSummary(PlayerGroup) +function SCORING:ReportScoreAllSummary(PlayerGroup,JustScore) local PlayerMessage="" +local ReportTable={} 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}) @@ -36196,6 +38033,7 @@ 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 +if JustScore~=true then PlayerMessage= string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )", PlayerName, @@ -36204,9 +38042,13 @@ PlayerScore, PlayerPenalty ) MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup) +else +ReportTable[PlayerName]={["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} end end end +return ReportTable +end function SCORING:SecondsToClock(sSeconds) local nSeconds=sSeconds if nSeconds==0 then @@ -36272,7 +38114,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -36293,6 +38135,17 @@ self.CSVFile:write( ) self.CSVFile:write("\n") end +if lfs and io and self.LoadSave==true then +local path=self.AutoSavePath or lfs.writedir()..[[Logs\]] +local filename=self.GameName or"PlayerScoresSummary" +filename=filename..".csv" +local data=self:ReportScoreAllSummary("",true) +local text="-- Playername;;Score;;Penalty\n" +for _playername,_data in pairs(data or{})do +text=text..string.format("%s;;%d;;%d\n") +end +UTILS.SaveToFile(path,filename,text) +end end function SCORING:CloseCSV() if lfs and io and os and self.AutoSave then @@ -43849,7 +45702,7 @@ 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)) +BASE:T(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest)) local flight={} flight.destination=dest flight.Tarrive=-1 @@ -43860,13 +45713,13 @@ 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)) +BASE:T(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)) +BASE:T(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 @@ -43886,11 +45739,12 @@ 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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.onfinal then -local Tfinal=Tnow-flight.Tonfinal +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-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) +BASE:T(RAT.id..text) elseif hold==RAT.ATC.unregistered then else BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") @@ -43916,11 +45770,11 @@ if not landing1 and not landing2 then flight.holding=Tnow-flight.Tarrive local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", airportname,flightname,qID,nqueue,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) else local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", airportname,flightname,flight.holding/60,flight.holding%60) -BASE:I(RAT.id..text) +BASE:T(RAT.id..text) RAT._ATCClearForLanding(airportname,flightname) end end @@ -43939,7 +45793,7 @@ airport.busy=true airport.onfinal[flightname]=flight airport.Nonfinal=airport.Nonfinal+1 airport.Tlastclearance=timer.getTime() -BASE:I(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s cleared for landing",airportname,flightname)) if string.find(flightname,"#")then flightname=string.match(flightname,"^(.+)#") end @@ -43954,8 +45808,9 @@ local flight=RAT.ATC.flight[name] if flight then local dest=flight.destination local Tnow=timer.getTime() -local Tfinal=Tnow-flight.Tonfinal -local Thold=flight.Tonfinal-flight.Tarrive +local Tonfinal=flight.Tonfinal or timer.getTime()-1 +local Tfinal=Tnow-Tonfinal +local Thold=Tonfinal-flight.Tarrive local airport=RAT.ATC.airport[dest] airport.busy=false airport.onfinal[name]=nil @@ -43963,9 +45818,9 @@ airport.Nonfinal=airport.Nonfinal-1 RAT._ATCDelFlight(RAT.ATC.flight,name) airport.traffic=airport.traffic+1 local TrafficPerHour=airport.traffic/(timer.getTime()-RAT.ATC.T0)*3600 -BASE:I(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) -BASE:I(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) -BASE:I(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) +BASE:T(RAT.id..string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.",dest,name,Thold/60,Thold%60,Tfinal/60,Tfinal%60)) +BASE:T(RAT.id..string.format("ATC %s: Number of flights still on final %d.",dest,airport.Nonfinal)) +BASE:T(RAT.id..string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.",dest,airport.traffic,TrafficPerHour)) if string.find(name,"#")then name=string.match(name,"^(.+)#") end @@ -44575,12 +46430,18 @@ 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) +if self.rangezone then +self.controlmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end 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) +if self.rangezone then +self.instructmsrs:SetCoordinate(self.rangezone:GetCoordinate()) +end self.instructsrsQ=MSRSQUEUE:New("INSTRUCT") if PathToGoogleKey then self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) @@ -44607,8 +46468,13 @@ self.controlmsrs:SetGender(gender or"female") self.rangecontrol=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.rangecontrolrelayname=relayunitname +self.controlmsrs:SetCoordinate(Coordinate) +else +MESSAGE:New("RANGE: Control Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -44626,9 +46492,13 @@ self.instructmsrs:SetGender(gender or"male") self.instructor=true if relayunitname then local unit=UNIT:FindByName(relayunitname) +if unit then local Coordinate=unit:GetCoordinate() self.instructmsrs:SetCoordinate(Coordinate) self.instructorrelayname=relayunitname +else +MESSAGE:New("RANGE: Instructor Relay Unit "..relayunitname.." not found!",15,"ERROR"):ToAllIf(self.Debug):ToLog() +end end return self end @@ -46820,7 +48690,7 @@ maxrange=7000, reloadtime=nil, }, } -ARTY.version="1.3.3" +ARTY.version="1.3.4" function ARTY:New(group,alias) local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) if type(group)=="string"then @@ -47500,7 +49370,7 @@ _destroyweapon=true end elseif target.weapontype==ARTY.WeaponType.SmokeShells then if _dist/Scripts`") +end +return self +end WAREHOUSE={ ClassName="WAREHOUSE", Debug=false, @@ -51029,7 +52924,6 @@ function WAREHOUSE:FindAssetInDB(group) local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then local asset=_WAREHOUSEDB.Assets[aid] -self:T2({asset=asset}) if asset==nil then self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!",group:GetName()),0) end @@ -51326,7 +53220,6 @@ 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) @@ -51430,9 +53323,10 @@ end return 0,0,0,0 end local templategroupname=group:GetName() -local Descriptors=group:GetUnit(1):GetDesc() +local unit=group:GetUnit(1) +local Descriptors=(unit and unit:IsAlive())and unit:GetDesc()or{} local Category=group:GetCategory() -local TypeName=group:GetTypeName() +local TypeName=group:GetTypeName()or"none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) @@ -51632,7 +53526,6 @@ self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),re 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 @@ -52441,7 +54334,6 @@ 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 @@ -54931,7 +56823,7 @@ end MANTIS={ ClassName="MANTIS", name="mymantis", -version="0.9.34", +version="0.9.42", SAM_Templates_Prefix="", SAM_Group=nil, EWR_Templates_Prefix="", @@ -54982,6 +56874,9 @@ SmokeDecoyColor=SMOKECOLOR.White, checkcounter=1, DLinkCacheTime=120, logsamstatus=false, +DetectAccoustic=false, +DetectAccousticRadius=2000, +DetectAccousticCategories={Unit.Category.HELICOPTER}, } MANTIS.AdvancedState={ GREEN=0, @@ -55027,10 +56922,14 @@ MANTIS.SamData={ ["SA-17"]={Range=50,Blindspot=3,Height=50,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"}, +["SA-21"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="92N6E"}, +["S-300VM"]={Range=200,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-300V4"]={Range=380,Blindspot=5,Height=30,Type="Long",Radar="9S32M"}, +["S-400"]={Range=250,Blindspot=5,Height=27,Type="Long",Radar="92N6E"}, ["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"}, ["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"}, -["NIKE"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, +["Nike"]={Range=155,Blindspot=6,Height=30,Type="Long",Radar="HIPAR"}, ["Dog Ear"]={Range=11,Blindspot=0,Height=9,Type="Point",Radar="Dog Ear",Point="true"}, ["Pantsir S1"]={Range=20,Blindspot=1.2,Height=15,Type="Point",Radar="PantsirS1",Point="true"}, ["Tor M2"]={Range=12,Blindspot=1,Height=10,Type="Point",Radar="TorM2",Point="true"}, @@ -55109,13 +57008,14 @@ MANTIS.SamDataCH={ ["Lvkv9040M CHM"]={Range=2,Blindspot=0.1,Height=1.2,Type="Point",Radar="LvKv9040",Point="true"}, } do -function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs,EmOnOff,Padding,Zones) +function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs,EmOnOff,Padding,Zones) local self=BASE:Inherit(self,FSM:New()) self.name=name or"mymantis" 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.Coalition=Coalition or"red" +self.coalition=Coalition=="blue"and coalition.side.BLUE or coalition.side.RED self.SAM_Table={} self.SAM_Table_Long={} self.SAM_Table_Medium={} @@ -55227,9 +57127,114 @@ self:AddTransition("*","ShoradActivated","*") self:AddTransition("*","SeadSuppressionStart","*") self:AddTransition("*","SeadSuppressionEnd","*") self:AddTransition("*","SeadSuppressionPlanned","*") +self:AddTransition("*","SAMUnitHit","*") +self:AddTransition("*","SAMUnitLost","*") self:AddTransition("*","Stop","Stopped") return self end +function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 2000 +self.DetectAccousticCategories=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function MANTIS:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end +function MANTIS:_EventHandler(EventData) +self:T(self.lid.."_EventHandler") +local function IsOneOfOurs(name) +for _,_name in pairs(self.ewr_templates)do +if string.find(name,_name,1,true)then +return true +end +end +return false +end +local function SwitchSAMOn(Name,Group) +local suppressed=self.SuppressedGroups[Name]or false +if not suppressed and self.SamStateTracker[Name]=="GREEN"then +self.SamStateTracker[Name]="RED" +if self.UseEmOnOff then +Group:EnableEmission(true) +elseif(not self.UseEmOnOff)then +Group:OptionAlarmStateRed() +end +self:__RedState(1,Group) +if self.SmokeDecoy==true then +self:_SmokeUnits(Group) +end +end +end +local coordinate +local Name +local Group +local lasthit=0 +local firsthit=false +local alerton=false +local data=EventData +if data.id==EVENTS.Hit then +if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName)then +self:T("Unit hit in group: "..data.TgtGroupName) +if data.TgtGroup then +lasthit=data.TgtGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.TgtGroup:GetCoordinate() +Name=data.TgtGroupName +Group=data.TgtGroup +if alerton==true then +self:__SAMUnitHit(1,Group,Name) +SwitchSAMOn(Name,Group) +end +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if data.id==EVENTS.UnitLost then +if data.IniGroupName and IsOneOfOurs(data.IniGroupName)then +self:T("Unit lost in group: "..data.IniGroupName) +if data.IniGroup then +lasthit=data.IniGroup:GetProperty("MANTIS_LASTHIT") +firsthit=(lasthit==nil)and true or false +if firsthit==true then alerton=true end +if lasthit~=nil and timer.getTime()-lasthit>self.ShoradTime then alerton=true end +coordinate=data.IniGroup:GetCoordinate() +Name=data.IniGroupName +Group=data.IniGroup +alerton=true +SwitchSAMOn(Name,Group) +self:__SAMUnitLost(1,Group,Name) +if coordinate and self.debug then +local text=coordinate:ToStringMGRS() +self:I("Location: "..text) +end +end +end +end +if firsthit==true or alerton==true then +Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) +end +if coordinate~=nil and Name~=nil and Group~=nil and alerton==true then +if self.ShoradLink then +self:T("Shorad activated for: "..Name) +local Shorad=self.Shorad +local radius=self.checkradius +local ontime=self.ShoradTime +Shorad:WakeUpShorad(Name,radius,ontime,nil,true) +self:__ShoradActivated(1,Name,radius,ontime) +end +if self.autorelocate and Group then +Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) +end +end +return self +end function MANTIS:_GetSAMTable() self:T(self.lid.."GetSAMTable") return self.SAM_Table @@ -55268,6 +57273,69 @@ self.usezones=true end return self end +function MANTIS:AddRejectZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.RejectZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddAcceptZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.AcceptZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:AddConflictZone(Zone) +if Zone and Zone:IsInstanceOf("ZONE_BASE")then +table.insert(self.ConflictZones,Zone) +self.usezones=true +end +return self +end +function MANTIS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorZones(self.corridorzones) +end +end +return self +end +function MANTIS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end +function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +if self.intelset then +for _,_intel in pairs(self.intelset)do +_intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end +return self +end function MANTIS:SetEWRRange(radius) self:T(self.lid.."SetEWRRange") return self @@ -55557,7 +57625,7 @@ end end return inzone end -function MANTIS:_PreFilterHeight(height) +function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set={} local dlink=self.Detection @@ -55566,8 +57634,22 @@ for _,_contact in pairs(detectedgroups)do local contact=_contact local grp=contact.group if grp:IsAlive()then -if grp:GetHeight(true)self.ShoradActDistance then include=false end +end +if self.debug then +local text="Looking at Group: "..grp:GetName()or"N/A" +text=text.." Include = "..tostring(include) +MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() +end +local grpalt=grp:GetHeight(true) +if grpalt10 and include==true then table.insert(set,coord) end end @@ -55579,7 +57661,7 @@ self:T(self.lid.."_CheckObjectInZone") local rad=radius or self.checkradius local set=dectset if dlink then -set=self:_PreFilterHeight(height) +set=self:_PreFilterHeight(height,samcoordinate) end if self.checkforfriendlies==true and self.friendlyset==nil then self.friendlyset=SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp)if grp and grp:InAir()then return true else return false end end):FilterStart() @@ -55635,9 +57717,27 @@ self:T(self.lid.."Starting Intel Detection") local groupset=self.EWR_Group local samset=self.SAM_Group self.intelset={} -local IntelOne=INTEL:New(groupset,self.Coalition,self.name.." IntelOne") +local IntelOne=INTEL:New(groupset,self.coalition,self.name.." IntelOne") +IntelOne.DetectAccoustic=self.DetectAccoustic +IntelOne.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelOne.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelOne:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelOne:Start() -local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo") +local IntelTwo=INTEL:New(samset,self.coalition,self.name.." IntelTwo") +IntelTwo.DetectAccoustic=self.DetectAccoustic +IntelTwo.DetectAccousticRadius=self.DetectAccousticRadius or 2000 +IntelTwo.DetectAccousticUnitTypes=self.DetectAccousticCategories or{Unit.Category.HELICOPTER} +if self.usecorridors==true then +IntelTwo:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end IntelTwo:Start() local CacheTime=self.DLinkCacheTime or 120 local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) @@ -55893,6 +57993,22 @@ self:T(self.lid.."RemoveShorad") self.ShoradLink=false return self end +function MANTIS:_SmokeUnits(Group) +self:T("Smoking") +local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME")or 0 +local TNow=timer.getTime() +if TNow-LastSmoketime>290 then +Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) +local units=Group:GetUnits()or{} +local smoke=self.SmokeDecoyColor or SMOKECOLOR.White +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +unit:GetCoordinate():Smoke(smoke) +end +end +end +return self +end function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid.."CheckLoop "..#detset.." Coordinates") local switchedon=0 @@ -55906,7 +58022,7 @@ local name=_data[1] local radius=_data[3] local height=_data[4] local blind=_data[5]*1.25+1 -local shortsam=(_data[6]==MANTIS.SamType.SHORT)and true or false +local shortsam=(_data[6]~=MANTIS.SamType.LONG)and true or false if not shortsam then shortsam=(_data[6]==MANTIS.SamType.POINT)and true or false end @@ -55931,17 +58047,10 @@ switch=true end if self.SamStateTracker[name]~="RED"and switch then self:__RedState(1,samgroup) -self.SamStateTracker[name]="RED" end -if shortsam==true and self.SmokeDecoy==true then +if shortsam==true and self.SmokeDecoy==true and 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 AIRBOSS={ ClassName="AIRBOSS", Debug=false, @@ -60798,6 +63191,7 @@ AV8B="AV8BNA", HORNET="FA-18C_hornet", A4EC="A-4E-C", F14A="F-14A-135-GR", +F14A_Early="F-14A-135-GR-Early", F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", @@ -62764,7 +65158,7 @@ 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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early local corsair=playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype==AIRBOSS.AircraftCarrier.CORSAIR_CW local aoa={} if hornet then @@ -62784,13 +65178,13 @@ aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.0) aoa.Fast=self:_AoAUnit2Deg(playerData,13.5) aoa.FAST=self:_AoAUnit2Deg(playerData,12.5) 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 +aoa.SLOW=9.5 +aoa.Slow=9.25 +aoa.OnSpeedMax=9.0 +aoa.OnSpeed=8.5 +aoa.OnSpeedMin=8.25 +aoa.Fast=7.75 +aoa.FAST=5.5 elseif skyhawk then aoa.SLOW=10.50 aoa.Slow=9.50 @@ -62820,7 +65214,7 @@ return aoa end function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits) local degrees=aoaunits -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then degrees=-10+50/30*aoaunits degrees=0.918*aoaunits-3.411 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -62830,7 +65224,7 @@ return degrees end function AIRBOSS:_AoADeg2Units(playerData,degrees) local aoaunits=degrees -if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early then aoaunits=(degrees+10)*30/50 aoaunits=1.089*degrees+3.715 elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then @@ -63463,6 +65857,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 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) @@ -63826,7 +66221,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -64092,7 +66487,12 @@ if playerData then local unit=playerData.unit if unit and unit:IsAlive()then if unit:IsInZone(self.zoneCCA)then -if playerData.step==AIRBOSS.PatternStep.WAKE then +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 tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then playerData.wrappedUpAtWakeLittle=true elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then @@ -64114,6 +66514,28 @@ playerData.AFU=true else end end +if playerData.step==AIRBOSS.PatternStep.WAKE and tomcat then +if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then +playerData.wrappedUpAtWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())>40 and math.abs(playerData.unit:GetRoll())<=45 then +playerData.wrappedUpAtWakeFull=true +elseif math.abs(playerData.unit:GetRoll())>45 then +playerData.wrappedUpAtWakeUnderline=true +elseif math.abs(playerData.unit:GetRoll())<12 and math.abs(playerData.unit:GetRoll())>=5 then +playerData.AAatWakeLittle=true +elseif math.abs(playerData.unit:GetRoll())<5 and math.abs(playerData.unit:GetRoll())>=2 then +playerData.AAatWakeFull=true +elseif math.abs(playerData.unit:GetRoll())<2 then +playerData.AAatWakeUnderline=true +else +end +if math.abs(playerData.unit:GetAoA())>=15 then +playerData.AFU=true +elseif math.abs(playerData.unit:GetAoA())<=5 then +playerData.AFU=true +else +end +end if playerData.attitudemonitor then self:_AttitudeMonitor(playerData) end @@ -64856,77 +67278,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -65214,19 +67568,19 @@ end if 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" +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" +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)" +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)" +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 @@ -65841,7 +68195,7 @@ local lueWire=self:_LineupWIRE(playerData.unit,true) text=text..string.format("\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°",lueWire or 0,lue or 0) local unitClient=Unit.getByName(unit:GetName()) local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET -local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B +local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B or playerData.actype==AIRBOSS.AircraftCarrier.F14A_Early if hornet then local nozzlePosL=0 local burnerPosL=unitClient:getDrawArgumentValue(28) @@ -65957,7 +68311,7 @@ nozzlePosL=unitClient:getDrawArgumentValue(89) else nozzlePosL=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosL=unitClient:getDrawArgumentValue(434) end return nozzlePosL @@ -65974,7 +68328,7 @@ nozzlePosR=unitClient:getDrawArgumentValue(90) else nozzlePosR=0 end -elseif typeName=="F-14A-135-GR"or typeName=="F-14B"then +elseif typeName=="F-14A-135-GR"or typeName=="F-14B"or typeName=="F-14A-135-GR-Early"then nozzlePosR=unitClient:getDrawArgumentValue(433) end return nozzlePosR @@ -66025,9 +68379,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -66126,7 +68478,7 @@ 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=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -66317,7 +68669,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -66332,6 +68684,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66439,14 +68792,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66494,6 +68841,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66510,7 +68858,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -67557,7 +69904,7 @@ 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 +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B or actype==AIRBOSS.AircraftCarrier.F14A_Early then nickname="Tomcat" elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then nickname="Hornet" @@ -70463,7 +72810,7 @@ modex=nil, dtFollow=nil, } _RESCUEHELOID=0 -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" function RESCUEHELO:New(carrierunit,helogroupname) local self=BASE:Inherit(self,FSM:New()) if type(carrierunit)=="string"then @@ -70509,6 +72856,7 @@ self:AddTransition("Running","Run","Running") self:AddTransition("Returned","Run","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") +self:I(self.lid.."Started.") return self end function RESCUEHELO:SetLowFuelThreshold(threshold) @@ -70707,16 +73055,24 @@ 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) +local UsesAliveGroup=false +local Spawn=GROUP:FindByName(self.helogroupname) +if Spawn and Spawn:IsAlive()then +self.helo=Spawn +UsesAliveGroup=true +delay=1 +else +Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) Spawn:InitModex(self.modex) -if self.takeoff==SPAWN.Takeoff.Air then +end +if UsesAliveGroup==false and 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 +elseif UsesAliveGroup==false and self.uncontrolledac then if self.uncontrolledac then self.helo=GROUP:FindByName(self.helogroupname) if self.helo and self.helo:IsAlive()then @@ -70726,7 +73082,8 @@ else self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) return end -else +end +elseif UsesAliveGroup==false then self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) if self.takeoff==SPAWN.Takeoff.Runway then delay=5 @@ -70736,14 +73093,12 @@ 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=FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier") 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) return self @@ -72802,6 +75157,9 @@ self.Subcategory=Subcategory or"Other" self.DontShowInMenu=DontShowInMenu or false self.ResourceMap=nil self.StaticType="container_cargo" +if self:IsStatic()then +self.StaticType=self.Templates +end self.StaticShape=nil self.TypeNames=nil self.StaticCategory="Cargos" @@ -72809,6 +75167,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -72865,7 +75224,10 @@ end function CTLD_CARGO:HasMoved() return self.HasBeenMoved end -function CTLD_CARGO:WasDropped() +function CTLD_CARGO:WasDropped(hercOnly) +if hercOnly then +return self.HasBeenDropped and self.IsHercDrop==true +end return self.HasBeenDropped end function CTLD_CARGO:CanLoadDirectly() @@ -72890,8 +75252,9 @@ else return false end end -function CTLD_CARGO:SetWasDropped(dropped) +function CTLD_CARGO:SetWasDropped(dropped,isHercDrop) self.HasBeenDropped=dropped or false +self.IsHercDrop=isHercDrop or false end function CTLD_CARGO:GetStock() if self.Stock then @@ -73181,7 +75544,9 @@ CTLD.UnitTypeCapabilities={ ["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}, +["C-130J-30"]={type="C-130J-30",crates=true,troops=true,cratelimit=7,trooplimit=64,length=35,cargoweightlimit=21500}, ["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500}, +["UH-60L_DAP"]={type="UH-60L_DAP",crates=false,troops=true,cratelimit=0,trooplimit=2,length=16,cargoweightlimit=500}, ["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}, @@ -73196,8 +75561,9 @@ CTLD.FixedWingTypes={ ["Hercules"]="Hercules", ["Bronco"]="Bronco", ["Mosquito"]="Mosquito", +["C-130J-30"]="C-130J-30", } -CTLD.version="1.3.37" +CTLD.version="1.3.42" function CTLD:New(Coalition,Prefixes,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Prefixes,Alias}) @@ -73240,12 +75606,15 @@ self:AddTransition("*","CratesPickedUp","*") self:AddTransition("*","TroopsDeployed","*") self:AddTransition("*","TroopsRTB","*") self:AddTransition("*","CratesDropped","*") +self:AddTransition("*","GetCrates","*") self:AddTransition("*","CratesBuild","*") +self:AddTransition("*","UnitsSpawn","*") self:AddTransition("*","CratesRepaired","*") self:AddTransition("*","CratesBuildStarted","*") self:AddTransition("*","CratesRepairStarted","*") self:AddTransition("*","CratesPacked","*") self:AddTransition("*","HelicopterLost","*") +self:AddTransition("*","RemoveCratesNearby","*") self:AddTransition("*","Load","*") self:AddTransition("*","Loaded","*") self:AddTransition("*","Save","*") @@ -73286,6 +75655,8 @@ self.EngineersInField={} self.EngineerSearch=2000 self.nobuildmenu=false self.CrateDistance=35 +self.UnitDistance=90 +self.maxUnitsNearby=3 self.PackDistance=35 self.ExtractFactor=3.33 self.prefixes=Prefixes or{"Cargoheli"} @@ -73299,7 +75670,10 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 +self.buildPairSeparation=25 +self.loadSavedCrates=true self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false self.enableFixedWing=false @@ -73326,6 +75700,7 @@ self.usesubcats=false self.subcats={} self.subcatsTroop={} self.showstockinmenuitems=false +self.maxCrateMenuQuantity=5 self.onestepmenu=false self.nobuildinloadzones=true self.movecratesbeforebuild=true @@ -73338,6 +75713,8 @@ self.filename=string.format("CTLD_%s_Persist.csv",AliaS) self.allowcratepickupagain=true self.enableslingload=false self.basetype="container_cargo" +self.C130basetype="cds_crate" +self.UseC130LoadAndUnload=false self.SmokeColor=SMOKECOLOR.Red self.FlareColor=FLARECOLOR.Red for i=1,100 do @@ -73418,6 +75795,15 @@ local unitname=event.IniUnitName or"none" self.MenusDone[unitname]=nil local _unit=event.IniUnit local _group=event.IniGroup +self.C130JUnits=self.C130JUnits or{} +local utype=_unit:GetTypeName() +if self.C130JTypes and self.C130JTypes[utype]then +self.C130JUnits[unitname]=true +elseif utype=="C-130J-30"then +self.C130JUnits[unitname]=true +else +self.C130JUnits[unitname]=false +end if _unit:IsHelicopter()or _group:IsHelicopter()then local unitname=event.IniUnitName or"none" self.Loaded_Cargo[unitname]=nil @@ -73453,6 +75839,7 @@ end self.CtldUnits[unitname]=nil self.Loaded_Cargo[unitname]=nil self.MenusDone[unitname]=nil +if self.C130JUnits then self.C130JUnits[unitname]=nil end elseif event.id==EVENTS.NewDynamicCargo then self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) self.DynamicCargo[event.IniDynamicCargoName]=event.IniDynamicCargo @@ -73478,6 +75865,7 @@ self.Loaded_Cargo[unitname]=loaded local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesPickedUp(1,Group,client,dcargo) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoUnloaded then self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) @@ -73514,6 +75902,7 @@ end local Group=client:GetGroup() self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName),10,false,Group) self:__CratesDropped(1,Group,client,{dcargo}) +self:_RefreshCrateQuantityMenus(Group,client,nil) end elseif event.id==EVENTS.DynamicCargoRemoved then self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) @@ -73521,6 +75910,13 @@ self.DynamicCargo[event.IniDynamicCargoName]=nil end return self end +function CTLD:IsC130J(Unit) +if not Unit then return false end +if not self.UseC130LoadAndUnload then return false end +self.C130JUnits=self.C130JUnits or{} +local unitname=Unit:GetName()or"none" +return self.C130JUnits[unitname]==true +end function CTLD:_SendMessage(Text,Time,Clearscreen,Group) self:T(self.lid.." _SendMessage") if not self.suppressmessages then @@ -73548,6 +75944,12 @@ if cargo.Name==Name then return cargo end end +for _,_cargo in pairs(self.Cargo_Statics)do +local cargo=_cargo +if cargo.Name==Name then +return cargo +end +end return nil end function CTLD:AddAllowedFixedWingType(typename) @@ -73697,6 +76099,7 @@ self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group,Unit,Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() +self:_RefreshTroopQuantityMenus(Group,Unit,Cargotype) end return self end @@ -73792,7 +76195,7 @@ end function CTLD:_ExtractTroops(Group,Unit) self:T(self.lid.." _ExtractTroops") local grounded=not self:IsUnitInAir(Unit) -local hoverload=self:CanHoverLoad(Unit) +local hoverload=self:IsCorrectHover(Unit) local hassecondaries=false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!",10,false,Group) @@ -73921,18 +76324,340 @@ end self:CleanDroppedTroops() return self end -function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack) +function CTLD:_LoadTroopsQuantity(Group,Unit,Cargo,quantity) +local n=math.max(1,tonumber(quantity)or 1) +local prevSuppress=self.suppressmessages +self.suppressmessages=true +for i=1,n do +timer.scheduleFunction(function()self:_LoadTroops(Group,Unit,Cargo,true)end,{},timer.getTime()+0.2*i) +end +timer.scheduleFunction(function() +self.suppressmessages=prevSuppress +local dname=Cargo:GetName() +self:_SendMessage(string.format("Loaded %d %s.",n,dname),10,false,Group) +end,{},timer.getTime()+0.2*n+0.05) +return self +end +function CTLD:_AddTroopQuantityMenus(Group,Unit,parentMenu,cargoObj) +local stock=cargoObj:GetStock() +local maxQuantity=self.maxCrateMenuQuantity or 1 +if type(stock)=="number"and stock>=0 and stock0 then +local space=trooplimit-onboard +if space=0 then +availableSets=math.floor(stock) +if availableSets<=0 then +MENU_GROUP_COMMAND:New(Group,"Out of stock",parentMenu,function()end) +return self +end +if availableSets0 then +local loadedData=nil +if self.Loaded_Cargo then +loadedData=self.Loaded_Cargo[Unit:GetName()] +end +local loadedCount=0 +if loadedData and type(loadedData.Cratesloaded)=="number"then +loadedCount=loadedData.Cratesloaded +end +local space=capacity-loadedCount +if space<0 then +space=0 +end +capacityCrates=space +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +end +end +local allowLoad=true +if type(capacitySets)=="number"then +if capacitySets>=1 then +if capacitySets0 and needed or 1) +if type(maxload)=="number"and maxload>0 and setMass>0 then +maxMassSets=math.floor(maxload/setMass) +if maxMassSets<1 then +maxQuantity=1 +allowLoad=false +elseif maxMassSets0 and perCrateMass>0 then +maxMassCrates=math.floor(maxload/perCrateMass) +end +end +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) +if maxQuantity<1 then +return self +end +if maxQuantity==1 then +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) +MENU_GROUP_COMMAND:New(Group,"Get",parentMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,1) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=1)and(not maxMassSets or maxMassSets>=1)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=1)and maxMassSets<1 then +msg="Weight limit reached" +else +msg="Crate limit reached" +end +MENU_GROUP_COMMAND:New(Group,msg,parentMenu,self._SendMessage,self,msg,10,false,Group) +if canPartiallyLoad and(cgotype~=CTLD_CARGO.Enum.STATIC)and(not suppressGetAndLoad)then +MENU_GROUP_COMMAND:New(Group,"Partially load",parentMenu,self._GetAndLoad,self,Group,Unit,cargoObj,1,true) +end +end +end +return self +end +for quantity=1,maxQuantity do +self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") +local label=tostring(quantity) +self:T("_AddCrateQuantityMenus Label "..label) +local qMenu=MENU_GROUP:New(Group,label,parentMenu) +MENU_GROUP_COMMAND:New(Group,"Get",qMenu,self._GetCrateQuantity,self,Group,Unit,cargoObj,quantity) +local canLoad=(allowLoad and(not capacitySets or capacitySets>=quantity)and(not maxMassSets or maxMassSets>=quantity)) +local isHerc=self:IsC130J(Unit) +local isHook=self:IsHook(Unit) +local cgotype=cargoObj:GetType()or nil +local suppressGetAndLoad=(self.enableChinookGCLoading==true)and isHook and(cgotype==CTLD_CARGO.Enum.STATIC) +local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) +if canLoad and not isHerc and not suppressGetAndLoad then +MENU_GROUP_COMMAND:New(Group,"Get and Load",qMenu,self._GetAndLoad,self,Group,Unit,cargoObj,quantity) +else +local msg +if not isHerc and not suppressGetAndLoad then +if maxMassSets and(not capacitySets or capacitySets>=quantity)and maxMassSetsinnerDist then +for _,ucfg in pairs(self.C130GetUnits or{})do +local templ=ucfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +local matched=false +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +nearbyCount=nearbyCount+1 +matched=true +break +end +end +if matched or nearbyCount>=maxUnitsNearby then break end +end +end +end +if nearbyCount>=maxUnitsNearby then break end +end +if nearbyCount>=maxUnitsNearby then +self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) +return self +end +local temptable=cfg.Templates or{} +if type(temptable)=="string"then +temptable={temptable} +end +local length=(capabilities.length+5)or 30 +local heading=(Unit:GetHeading()+180)%360 +local canmove=cfg.CanMove~=false +local spawnedUnits={} +local idx=1 +for _,_template in pairs(temptable)do +local cratedistance=(idx-1)*2.5+length +local spawncoord=coord:Translate(cratedistance,heading) +local randomcoord=spawncoord:GetVec2() +self.TroopCounter=self.TroopCounter+1 +local tc=self.TroopCounter +local alias=string.format("%s-%d",_template,math.random(1,100000)) +if canmove then +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:InitDelayOff() +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +else +SPAWN:NewWithAlias(_template,alias) +:InitRandomizeUnits(true,10,2) +:InitDelayOff() +:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) +:OnSpawnGroup(function(grp,TimeStamp) +grp.spawntime=TimeStamp or timer.getTime() +self.DroppedTroops[tc]=grp +table.insert(spawnedUnits,grp) +self:__UnitsSpawn(1,Group,Unit,spawnedUnits) +end) +:SpawnFromVec2(randomcoord) +end +idx=idx+1 +end +if type(stock)=="number"and stock~=-1 then +cfg.Stock=stock-1 +end +self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or"selection"),10,false,Group) +return self +end +function CTLD:CanGetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) +return true +end +function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack,quiet,suppressGetEvent) self:T(self.lid.." _GetCrates") +local perSet=Cargo:GetCratesNeeded()or 1 +if perSet<1 then perSet=1 end +local requestNumber=tonumber(number) +if requestNumber then +requestNumber=math.floor(requestNumber) +if requestNumber<1 then requestNumber=perSet end +else +requestNumber=perSet +end +local requestedSets=math.floor((requestNumber+perSet-1)/perSet) +if requestedSets<1 then requestedSets=1 end 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 +return false end end local inzone=false local drop=drop or false +local suppressGetEvent=suppressGetEvent or false local ship=nil local width=20 local distance=nil @@ -73961,7 +76686,7 @@ 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 +if not self.debug then return false end end end end @@ -73971,13 +76696,16 @@ local loaddist=self.CrateDistance or 35 local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,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 +return false +end +if not self:CanGetCrates(Group,Unit,Cargo,requestNumber,drop,pack,quiet,suppressGetEvent)then +return false end local IsHerc=self:IsFixedWing(Unit) local IsHook=self:IsHook(Unit) local IsTruck=Unit:IsGround() local cargotype=Cargo -local number=number or cargotype:GetCratesNeeded() +local number=requestNumber local cratesneeded=cargotype:GetCratesNeeded() local cratename=cargotype:GetName() local cratetemplate="Container" @@ -73992,6 +76720,7 @@ local position=Unit:GetCoordinate() local heading=Unit:GetHeading()+1 local height=Unit:GetHeight() local droppedcargo={} +local obtainedcargo={} local cratedistance=0 local rheading=0 local angleOffNose=0 @@ -74034,8 +76763,12 @@ self.CrateCounter=self.CrateCounter+1 local CCat,CType,CShape=Cargo:GetStaticTypeAndShape() local basetype=CType or self.basetype or"container_cargo" CCat=CCat or"Cargos" -if isstatic then -basetype=cratetemplate +if not isstatic and self:IsC130J(Unit)then +if Cargo.C130TypeName then +basetype=Cargo.C130TypeName +elseif self.C130basetype and(not CType or CType==self.basetype)then +basetype=self.C130basetype +end end if type(ship)=="string"then self:T("Spawning on ship "..ship) @@ -74081,8 +76814,8 @@ 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) local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +local CCat3,CType3,CShape3=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end @@ -74094,23 +76827,35 @@ realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames=UTILS.DeepCopy(cargotype.TypeNames) end +if self.UseC130LoadAndUnload and self:IsC130J(Unit)then +realcargo:SetWasDropped(true,true) end -local CCat,CType,CShape=cargotype:GetStaticTypeAndShape() -realcargo:SetStaticTypeAndShape(CCat,CType,CShape) +end +if not drop and not pack then +table.insert(obtainedcargo,realcargo) +end +local CCat4,CType4,CShape4=cargotype:GetStaticTypeAndShape() +realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) table.insert(self.Spawned_Cargo,realcargo) end if not(drop or pack)then -Cargo:RemoveStock() +Cargo:RemoveStock(requestedSets) +self:_RefreshCrateQuantityMenus(Group,Unit,Cargo) 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) else +if not quiet then self:_SendMessage(text,10,false,Group) end +if not pack and not suppressGetEvent and#obtainedcargo>0 then +self:__GetCrates(1,Group,Unit,obtainedcargo) +end +end self:_RefreshLoadCratesMenu(Group,Unit) -return self +return true end function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad) self:T(self.lid.." InjectStatics") @@ -74209,6 +76954,52 @@ self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddis end return self end +function CTLD:_C130RemoveUnitsNearby(_group,_unit) +self:T(self.lid.." _C130RemoveUnitsNearby") +if not _group or not _unit then return self end +local location=_group:GetCoordinate() +if not location then return self end +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=(capabilities.length and capabilities.length/2)or 15 +local finddist=self.PackDistance or(self.CrateDistance or 35) +local zone=ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) +local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce() +local removedAny=false +local removedTable={} +for _,gr in pairs(nearestGroups.Set)do +local gc=gr:GetCoordinate() +if gc then +local dist=location:Get2DDistance(gc) +if dist>innerDist then +local didRemoveThis=false +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.match(gr:GetName(),tName)then +local cname=cfg.Name or"Unit" +table.insert(removedTable,{groupName=gr:GetName(),name=cname,template=tName,coordinate=gr:GetCoordinate()}) +gr:Destroy(false) +self:_SendMessage(cname.." have been removed",10,false,_group) +removedAny=true +didRemoveThis=true +break +end +end +if didRemoveThis then break end +end +end +end +end +if not removedAny then +self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) +else +self:__RemoveCratesNearby(1,_group,_unit,removedTable) +end +return self +end function CTLD:_RemoveCratesNearby(_group,_unit) self:T(self.lid.." _RemoveCratesNearby") local finddist=self.CrateDistance or 35 @@ -74221,8 +77012,10 @@ for _,_entry in pairs(crates)do local entry=_entry local name=entry:GetName()or"none" text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) -if entry:GetPositionable()then -entry:GetPositionable():Destroy(false) +local pos=entry:GetPositionable() +if pos then +entry.coordinate=pos:GetCoordinate() +pos:Destroy(false) end table.insert(removedIDs,entry:GetID()) end @@ -74231,8 +77024,17 @@ text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(),30,true,_group) -self:_CleanupTrackedCrates(removedIDs) +local done={} +for _,e in pairs(crates)do +local n=e:GetName()or"none" +if not done[n]then +local object=self:_FindCratesCargoObject(n) +if object then self:_RefreshCrateQuantityMenus(_group,_unit,object)end +done[n]=true +end +end self:_RefreshLoadCratesMenu(_group,_unit) +self:__RemoveCratesNearby(1,_group,_unit,crates) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end @@ -74258,7 +77060,7 @@ self:E({_point1,_point2}) return-1 end end -function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype) +function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype,ignoreHercInner) self:T(self.lid.." _FindCratesNearby") local finddist=_dist local location=_group:GetCoordinate() @@ -74294,8 +77096,16 @@ local cando=cargo:UnitCanCarry(_unit) if ignoretype==true then cando=true end self:T(self.lid.." Unit can carry: "..tostring(cando)) local distance=self:_GetDistance(location,staticpos) +local hercInnerBlocked=false +if self.UseC130LoadAndUnload and ignoreHercInner and _unit and self:IsC130J(_unit)then +local capabilities=self:_GetUnitCapabilities(_unit) +local innerDist=capabilities.length and(capabilities.length/2)or 4 +if distance0 then local no_troops=loadedcargo.Troopsloaded or 0 local no_crates=loadedcargo.Cratesloaded or 0 local cargotable=loadedcargo.Cargo or{} @@ -74527,6 +77347,18 @@ end if cratecount==0 then report:Add(" N O N E") end +if hercInnerCount>0 then +local hercMass=0 +for _,_cargo in pairs(hercInnerCrates or{})do +local cargo=_cargo +local type=cargo:GetType() +if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS then +report:Add(string.format("Crate: %s size 1",cargo:GetName())) +hercMass=hercMass+cargo:GetMass() +end +end +loadedmass=loadedmass+hercMass +end report:Add("------------------------------------------------------------") report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.") local text=report:Text() @@ -74670,7 +77502,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -74766,7 +77598,10 @@ 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 +if stock and tonumber(stock)>=0 then +_troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) +end end end end @@ -74776,6 +77611,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74864,6 +77700,7 @@ self.Loaded_Cargo[unitname]=nil self.Loaded_Cargo[unitname]=loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group) @@ -74873,6 +77710,9 @@ end end return self end +function CTLD:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop) +return true +end function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop) self:T(self.lid.." _BuildCrates") if self:IsFixedWing(Unit)and self.enableFixedWing and not Engineering then @@ -74889,11 +77729,19 @@ self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end -local finddist=self.CrateDistance or 35 +local baseDist=self.CrateDistance or 35 +local finddist=baseDist +if self.EngineerSearch and self.EngineerSearch>baseDist then +finddist=self.EngineerSearch +finddist=self.EngineerSearch +end local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true) local buildables={} local foundbuilds=false local canbuild=false +if not self:CanBuildCrates(Group,Unit,crates,number,Engineering,MultiDrop)then +return self +end if number>0 then for _,_crate in pairs(crates)do local Crate=_crate @@ -74903,6 +77751,16 @@ local required=Crate:GetCratesNeeded() local template=Crate:GetTemplates() local ctype=Crate:GetType() local ccoord=Crate:GetPositionable():GetCoordinate() +local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0 +local isHercDrop=Crate:WasDropped(true) +if not isHercDrop and distToUnit>baseDist then +elseif self.UseC130LoadAndUnload and self:IsC130J(Unit)and distToUnit<15 then +self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) +return self +elseif self.UseC130LoadAndUnload and self:IsHook(Unit)and distToUnit<5 then +self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) +return self +else if not buildables[name]then local object={} object.Name=name @@ -74925,6 +77783,7 @@ end self:T({buildables=buildables}) end end +end local report=REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables)do @@ -74953,19 +77812,55 @@ else self:T(text) end if canbuild then +local notified=false for _,_build in pairs(buildables)do local build=_build if build.CanBuild then -self:_CleanUpCrates(crates,build,number) +local required=build.Required or 1 +if required<1 then required=1 end +local full=math.floor((build.Found or 0)/required) +if full<1 then full=1 end +local sep=self.buildPairSeparation or 25 +local hdg=(Unit:GetHeading()+180)%360 +local lat=(hdg+90)%360 +local base=Unit:GetCoordinate():Translate(20,hdg) +if full==1 then +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime>0 then local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) +if not notified then self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end self:__CratesBuildStarted(1,Group,Unit,build.Name) -self:_RefreshDropTroopsMenu(Group,Unit) else self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end +else +local start=-((full-1)*sep)/2 +for n=1,full do +local cratesNow,numberNow=self:_FindCratesNearby(Group,Unit,finddist,true,true) +self:_CleanUpCrates(cratesNow,build,numberNow) +self:_RefreshLoadCratesMenu(Group,Unit) +local off=start+(n-1)*sep +local coord=base:Translate(off,lat):GetVec2() +local b={Name=build.Name,Required=build.Required,Template=build.Template,CanBuild=true,Type=build.Type,Coord=coord} +if self.buildtime and self.buildtime>0 then +local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) +buildtimer:Start(self.buildtime) +if not notified then +self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) +notified=true +end +self:__CratesBuildStarted(1,Group,Unit,build.Name) +else +self:_BuildObjectFromCrates(Group,Unit,b,false,nil,MultiDrop) +end +end +end end end end @@ -74978,24 +77873,32 @@ 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() +local packedAny=false for _,_Group in pairs(nearestGroups.Set)do +local didPackThisGroup=false for _,_Template in pairs(_DATABASE.Templates.Groups)do -if(string.match(_Group:GetName(),_Template.GroupName))then +if string.match(_Group:GetName(),_Template.GroupName)then for _,_entry in pairs(self.Cargo_Crates)do -if(_entry.Templates[1]==_Template.GroupName)then +if _entry.Templates[1]==_Template.GroupName then _Group:Destroy() self:_GetCrates(Group,Unit,_entry,nil,false,true) self:_RefreshLoadCratesMenu(Group,Unit) self:__CratesPacked(1,Group,Unit,_entry) -return true +packedAny=true +didPackThisGroup=true +break end end end +if didPackThisGroup then break end end end +if not packedAny then self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end +return true +end function CTLD:_RepairCrates(Group,Unit,Engineering) self:T(self.lid.." _RepairCrates") local finddist=self.CrateDistance or 35 @@ -75163,7 +78066,8 @@ local thisID=nowcrate:GetID() if name==nametype then table.insert(destIDs,thisID) found=found+1 -nowcrate:GetPositionable():Destroy(false) +local pos=nowcrate:GetPositionable() +if pos then pos:Destroy(false)end nowcrate.Positionable=nil nowcrate.HasBeenDropped=false end @@ -75210,13 +78114,57 @@ end timer.scheduleFunction(function()self:_RemoveCratesNearby(Group,Unit)end,{},timer.getTime()+1) return self end -function CTLD:_GetAndLoad(Group,Unit,cargoObj) +function CTLD:_GetAndLoad(Group,Unit,cargoObj,quantity,LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end -self:_GetCrates(Group,Unit,cargoObj) -timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name)end,{},timer.getTime()+1) +local needed=cargoObj and cargoObj:GetCratesNeeded()or 1 +local count=math.max(1,tonumber(quantity)or 1) +local capacitySets=nil +local cap=self:_GetUnitCapabilities(Unit) +local limit=cap and cap.cratelimit or 0 +if limit>0 then +local ld=self.Loaded_Cargo and self.Loaded_Cargo[Unit:GetName()]or nil +local loaded=(ld and type(ld.Cratesloaded)=="number")and ld.Cratesloaded or 0 +local space=limit-loaded +if space<0 then space=0 end +local perSet=needed>0 and needed or 1 +capacitySets=math.floor(space/perSet) +if capacitySets<1 and not LoadAnyWay then +self:_SendMessage("No capacity to load more now!",10,false,Group) +return self +end +if capacitySets<1 and LoadAnyWay then +count=1 +elseif count>capacitySets then +count=capacitySets +end +end +local inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) +if not inzone then +local ship=nil +local width=20 +local distance=nil +local zone=nil +inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) +end +if not inzone then +self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) +return self +end +local total=needed*count +local ok=self:_GetCrates(Group,Unit,cargoObj,total,false,false,true,true) +if ok then +local uname=Unit:GetName() +self._batchCrateLoad=self._batchCrateLoad or{} +self._batchCrateLoad[uname]={remaining=count,group=Group,cname=cargoObj.Name,loaded=0,partials=0} +local details=(LoadAnyWay==true) +for i=1,count do +timer.scheduleFunction(function()self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name,details)end,{},timer.getTime()+0.2*i) +end +end +return self end function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then @@ -75225,6 +78173,236 @@ return self end timer.scheduleFunction(function()self:_LoadCratesNearby(Group,Unit)end,{},timer.getTime()+1) end +function CTLD:_GetCrateStockEntry(cargoObj,stockSummary) +if not cargoObj or not stockSummary then +return nil +end +local name=cargoObj:GetName() +if not name then +return nil +end +return stockSummary[name] +end +function CTLD:_FormatCrateStockSuffix(cargoObj,stockSummary) +if not cargoObj then +return nil +end +local stockEntry=self:_GetCrateStockEntry(cargoObj,stockSummary) +local available=nil +if stockEntry and type(stockEntry.Stock)=="number"then +available=stockEntry.Stock +end +if type(available)~="number"then +local direct=cargoObj:GetStock() +if type(direct)=="number"then +available=direct +end +end +if type(available)~="number"or available<0 then +return nil +end +local rounded=math.floor(available+0.5) +local total=nil +if stockEntry and type(stockEntry.Stock0)=="number"and stockEntry.Stock0>=0 then +total=math.floor(stockEntry.Stock0+0.5) +elseif stockEntry and type(stockEntry.Sum)=="number"and stockEntry.Sum>=0 then +total=math.floor(stockEntry.Sum+0.5) +end +if type(total)~="number"then +local baseTotal=cargoObj.GetStock0 and cargoObj:GetStock0()or nil +if type(baseTotal)=="number"and baseTotal>=0 then +total=math.floor(baseTotal+0.5) +end +end +if type(total)=="number"and total>0 and total~=rounded then +return string.format("[%d/%d]",rounded,total) +else +return string.format("[%d]",rounded) +end +end +function CTLD:_RefreshCrateQuantityMenus(Group,Unit,CargoObj) +if not Group and Unit then Group=Unit:GetGroup()end +if Group and Unit then +local uname=Unit:GetName()or"none" +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +if Group.CTLD_CrateMenus then +local present={} +for item,_ in pairs(Group.CTLD_CrateMenus)do present["C:"..tostring(item)]=true end +for key,_ in pairs(self._qtySnap[uname])do +if string.sub(key,1,2)=="C:"and not present[key]then +self._qtySnap[uname][key]=nil +end +end +local stockSummary=self.showstockinmenuitems and self:_CountStockPlusInHeloPlusAliveGroups(false)or nil +for item,menu in pairs(Group.CTLD_CrateMenus)do +menu:RemoveSubMenus() +local obj=self:_FindCratesCargoObject(item) +if obj then self:_AddCrateQuantityMenus(Group,Unit,menu,obj,stockSummary)end +end +end +end +if CargoObj and Group and Unit then +local uname=Unit:GetName()or"none" +local cap=(self:_GetUnitCapabilities(Unit).cratelimit or 0) +local loaded=(self.Loaded_Cargo[uname]and self.Loaded_Cargo[uname].Cratesloaded)or 0 +local avail=math.max(0,cap-loaded) +local per=CargoObj:GetCratesNeeded()or 1 +if per<1 then per=1 end +local unitAvail=math.max(0,math.min(self.maxCrateMenuQuantity or 1,math.floor(avail/per))) +local s=CargoObj:GetStock() +self._qtySnap=self._qtySnap or{} +self._qtySnap[uname]=self._qtySnap[uname]or{} +local k="C:"..(CargoObj:GetName()or"none") +local snap=tostring(type(s)=="number"and s or-1)..":"..tostring(unitAvail) +if self._qtySnap[uname][k]~=snap then +self._qtySnap[uname][k]=snap +if type(s)=="number"and s>=0 and s=0 and s=0 and s=0 and s=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,cargoObj) +local parent=subcatmenus[cargoObj.Subcategory]or troopsmenu +local mSet=MENU_GROUP:New(_group,menutext,parent) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Troops)do if not cargoObj.DontShowInMenu then -local stock=cargoObj:GetStock() local menutext=cargoObj.Name -if(stock>=0)and(self.showstockinmenuitems==true)then menutext=menutext.." ["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops,self,_group,_unit,cargoObj) +local mSet=MENU_GROUP:New(_group,menutext,troopsmenu) +_group.CTLD_TroopMenus[cargoObj.Name]=mSet +self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) end end end @@ -75349,85 +78530,112 @@ local topcrates=MENU_GROUP:New(_group,"Manage Crates",topmenu) _group.MyTopCratesMenu=topcrates local cratesmenu=MENU_GROUP:New(_group,"Get Crates",topcrates) if self.onestepmenu then +_group.CTLD_CrateMenus={} +local crateStockSummary=nil +if self.showstockinmenuitems then +crateStockSummary=self:_CountStockPlusInHeloPlusAliveGroups(false) +end +local function addCrateMenuEntry(cargoObj,parentMenu,subcatmenus) +if cargoObj.DontShowInMenu then +return +end +local isStaticCargo=false +if(cargoObj.GetType and cargoObj:GetType()==CTLD_CARGO.Enum.STATIC)or cargoObj.CargoType==CTLD_CARGO.Enum.STATIC then +isStaticCargo=true +end +if isStaticCargo and cargoObj.UnitCanCarry and not cargoObj:UnitCanCarry(_unit)then +return +end +local parent=parentMenu +if subcatmenus and cargoObj.Subcategory then +parent=subcatmenus[cargoObj.Subcategory] +if not parent then +parent=MENU_GROUP:New(_group,cargoObj.Subcategory,cratesmenu) +subcatmenus[cargoObj.Subcategory]=parent +end +end +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end +if cargoObj.Location then txt=txt.."[R]"end +if self.showstockinmenuitems then +local suffix=self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) +if suffix then txt=txt..suffix end +end +local mSet=MENU_GROUP:New(_group,txt,parent) +_group.CTLD_CrateMenus[cargoObj.Name]=mSet +self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) +end if self.usesubcats then local subcatmenus={} -for catName,_ in pairs(self.subcats)do -subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) -end for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu,subcatmenus) end else for _,cargoObj in pairs(self.Cargo_Crates)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) -if cargoObj.Location then txt=txt.."[R]"end -local stock=cargoObj:GetStock() -if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -local mSet=MENU_GROUP:New(_group,txt,cratesmenu) -MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) -MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) -end +addCrateMenuEntry(cargoObj,cratesmenu) end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} -for catName,_ in pairs(self.subcats)do +local function getSubcatMenu(catName) +if not catName then return cratesmenu end +if not subcatmenus[catName]then subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end +return subcatmenus[catName] +end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end -MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[cargoObj.Subcategory],self._GetCrates,self,_group,_unit,cargoObj) +MENU_GROUP_COMMAND:New(_group,txt,getSubcatMenu(cargoObj.Subcategory),self._GetCrates,self,_group,_unit,cargoObj) end end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75435,8 +78643,14 @@ MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,c end end for _,cargoObj in pairs(self.Cargo_Statics)do -if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +if(not cargoObj.DontShowInMenu)and(not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit))then +local needed=cargoObj:GetCratesNeeded()or 1 +local txt +if needed>1 then +txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) +else +txt=string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +end if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75485,6 +78699,43 @@ MENU_GROUP_COMMAND:New(_group,line,dropCratesMenu,self._UnloadSingleCrateSet,sel end end end +if self:IsC130J(_unit)then +local topunits=MENU_GROUP:New(_group,"Manage Units",topmenu) +local getunits=MENU_GROUP:New(_group,"Get Units",topunits) +MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) +local unitentries=self.C130GetUnits or{} +local unittype=_unit:GetTypeName()or"none" +local subcatmenus=self.usesubcats and{}or nil +for _,cargoObj in ipairs(unitentries)do +local ok=true +if cargoObj.UnitTypes then +ok=false +if type(cargoObj.UnitTypes)=="string"then +if unittype==cargoObj.UnitTypes then ok=true end +else +for _,ut in pairs(cargoObj.UnitTypes)do +if unittype==ut then ok=true break end +end +end +end +if ok and(not cargoObj.Stock or cargoObj.Stock==-1 or cargoObj.Stock>0)then +local parent=getunits +if self.usesubcats==true and cargoObj.SubCategory then +local sub=subcatmenus[cargoObj.SubCategory] +if not sub then +sub=MENU_GROUP:New(_group,cargoObj.SubCategory,getunits) +subcatmenus[cargoObj.SubCategory]=sub +end +parent=sub +end +local menutext=cargoObj.Name +if type(cargoObj.Stock)=="number"and cargoObj.Stock>=0 and self.showstockinmenuitems then +menutext=menutext.."["..cargoObj.Stock.."]" +end +MENU_GROUP_COMMAND:New(_group,menutext,parent,self._C130GetUnits,self,_group,_unit,cargoObj.Name) +end +end +end MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu,self._ListCargo,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu,self._ListInventory,self,_group,_unit) MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu,self._ListRadioBeacons,self,_group,_unit) @@ -75507,6 +78758,8 @@ end self.MenusDone[_unitName]=true self:_RefreshLoadCratesMenu(_group,_unit) self:_RefreshDropCratesMenu(_group,_unit) +if firstBuild then menucount=menucount+1 end +if firstBuild and not self.showstockinmenuitems then self:_RefreshQuantityMenusForGroup(_group,_unit)end end end else @@ -75518,6 +78771,10 @@ end function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() +if self:IsC130J(Unit)then +MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function()end) +return +end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then @@ -75551,7 +78808,7 @@ lineIndex=lineIndex+1 end end end -function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName) +function CTLD:_LoadSingleCrateSet(Group,Unit,cargoName,details) self:T(self.lid.." _LoadSingleCrateSet cargoName="..(cargoName or"nil")) local grounded=not self:IsUnitInAir(Unit) local hover=self:CanHoverLoad(Unit) @@ -75582,18 +78839,23 @@ self:_SendMessage(string.format("No \"%s\" crates found in range!",cargoName),10 return self end local found=#matchingCrates +local batch=self._batchCrateLoad and self._batchCrateLoad[Unit:GetName()]or nil +local prevSuppress=self.suppressmessages +if batch and(not details)and batch.cname==cargoName then self.suppressmessages=true end local unitName=Unit:GetName() local loadedData=self.Loaded_Cargo[unitName]or{Troopsloaded=0,Cratesloaded=0,Cargo={}} local capabilities=self:_GetUnitCapabilities(Unit) local capacity=capabilities.cratelimit or 0 if loadedData.Cratesloaded>=capacity then self:_SendMessage("No more capacity to load crates!",10,false,Group) +self.suppressmessages=prevSuppress return self end local spaceLeft=capacity-loadedData.Cratesloaded local toLoad=math.min(found,needed,spaceLeft) if toLoad<1 then self:_SendMessage("Cannot load crates: either none found or no capacity left.",10,false,Group) +self.suppressmessages=prevSuppress return self end local crateIDsLoaded={} @@ -75626,6 +78888,7 @@ end end self.Spawned_Cargo=newSpawned local loadedHere=toLoad +if details or(not batch)then if loadedHere=capacity then self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!",loadedHere,needed,cargoName),10,false,Group) else @@ -75643,8 +78906,29 @@ else self:_SendMessage(string.format("Loaded %d %s(s).",loadedHere,cargoName),10,false,Group) end end +end self:_RefreshLoadCratesMenu(Group,Unit) self:_RefreshDropCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,self:_FindCratesCargoObject(cargoName)) +if batch and batch.cname==cargoName then +local setsLoaded=math.floor((loadedHere or 0)/(needed or 1)) +batch.loaded=(batch.loaded or 0)+(setsLoaded or 0) +if loadedHere<(needed or 1)then batch.partials=(batch.partials or 0)+1 end +batch.remaining=(batch.remaining or 1)-1 +if batch.remaining<=0 then +self.suppressmessages=prevSuppress +if not details then +local txt=string.format("Loaded %d %s.",batch.loaded,cargoName) +if batch.partials and batch.partials>0 then +txt=txt.." Some sets could not be fully loaded." +end +self:_SendMessage(txt,10,false,batch.group) +end +self._batchCrateLoad[Unit:GetName()]=nil +else +self.suppressmessages=prevSuppress +end +end return self end function CTLD:_UnloadSingleCrateSet(Group,Unit,setIndex) @@ -75738,6 +79022,7 @@ end self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group,Unit) self:_RefreshLoadCratesMenu(Group,Unit) +self:_RefreshCrateQuantityMenus(Group,Unit,nil) return self end function CTLD:_RefreshDropCratesMenu(Group,Unit) @@ -75781,29 +79066,62 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75820,33 +79138,98 @@ for cName,list in pairs(cargoByName)do local needed=list[1]:GetCratesNeeded()or 1 table.sort(list,function(a,b)return a:GetID()=needed then +local sets=math.floor(#list/(needed>0 and needed or 1)) +if sets>0 then +local parentLabel=string.format("%d. %s (%d SET)",lineIndex,cName,sets) +local parentMenu=MENU_GROUP:New(Group,parentLabel,dropCratesMenu) +for s=1,sets do local chunk={} -for n=i,i+needed-1 do -table.insert(chunk,list[n]) -end -local label=string.format("%d. %s",lineIndex,cName) +for n=i,i+needed-1 do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) -local setIndex=#self.CrateGroupList[Unit:GetName()] -local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) -MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then -MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) -end i=i+needed -else -local chunk={} -for n=i,#list do -table.insert(chunk,list[n]) end -local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) +if sets==1 then +MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,1) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,1) +end +else +for q=1,sets do +local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and"s"or""),parentMenu) +MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +end,self,Group,Unit,cName,needed,q) +if not(self:IsUnitInAir(Unit)and self:IsFixedWing(Unit))then +MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) +local uName=UnitArg:GetName() +for k=1,qty do +local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] +if not lst then break end +local idx=nil +for j=1,#lst do +local ch=lst[j] +local first=ch and ch[1] +if first and(not first:WasDropped())and first:GetName()==cNameArg and#ch>=neededArg then idx=j break end +end +if not idx then break end +selfArg:_UnloadSingleCrateSet(GroupArg,UnitArg,idx) +end +selfArg:_BuildCrates(GroupArg,UnitArg) +end,self,Group,Unit,cName,needed,q) +end +end +end +lineIndex=lineIndex+1 +end +if i<=#list then +local left=#list-i+1 +local chunk={} +for n=i,#list do table.insert(chunk,list[n])end table.insert(self.CrateGroupList[Unit:GetName()],chunk) local setIndex=#self.CrateGroupList[Unit:GetName()] +local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) -i=#list+1 -end lineIndex=lineIndex+1 end end @@ -75860,7 +79243,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end 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) @@ -75968,6 +79351,7 @@ if _troop.Name==cName then local st=_troop:GetStock() if st and tonumber(st)>=0 then _troop:AddStock() +self:_RefreshTroopQuantityMenus(Group,Unit,_troop) end end end @@ -75998,6 +79382,7 @@ end self.Loaded_Cargo[unitName].Troopsloaded=troopsLoaded self.Loaded_Cargo[unitName].Cratesloaded=cratesLoaded self:_RefreshDropTroopsMenu(Group,Unit) +self:_RefreshTroopQuantityMenus(Group,Unit,nil) else local isHerc=self:IsFixedWing(Unit) if isHerc then @@ -76070,7 +79455,45 @@ table.insert(self.Cargo_Troops,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +function CTLD:AddUnits(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnits") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Units for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock or nil +entry.Stock0=Stock or nil +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=true +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddUnitsNoMove(Name,Templates,Type,Stock,SubCategory,UnitTypes) +self:T(self.lid.." AddUnitsNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."UnitsNoMove for "..Name.." has missing template(s)!") +return self +end +self.C130GetUnits=self.C130GetUnits or{} +local entry={} +entry.Name=Name +entry.Templates=Templates +entry.Type=Type +entry.Stock=Stock +entry.Stock0=Stock +entry.SubCategory=SubCategory or"Other" +entry.UnitTypes=UnitTypes +entry.CanMove=false +table.insert(self.C130GetUnits,entry) +return self +end +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) self:T(self.lid.." AddCratesCargo") if not self:_CheckTemplates(Templates)then self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") @@ -76085,11 +79508,41 @@ cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end +cargo.C130TypeName=C130TypeName table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end -function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName,C130TypeName) +self:T(self.lid.." AddCratesCargoNoMove") +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) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +cargo.C130TypeName=C130TypeName +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 local type=CTLD_CARGO.Enum.STATIC @@ -76100,6 +79553,9 @@ if unittemplate and unittemplate.resourcePayload then ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) end local cargo=CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end @@ -76241,7 +79697,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -76264,9 +79725,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -76600,6 +80097,7 @@ return self end function CTLD:IsCorrectHover(Unit) self:T(self.lid.." IsCorrectHover") +if self:IsFixedWing(Unit)then return false end local outcome=false if self:IsUnitInAir(Unit)then local uspeed=Unit:GetVelocityMPS() @@ -76777,6 +80275,23 @@ Troopstable[genname].GenericCargo=generic end end end +for _id,_unit in pairs(self.C130GetUnits or{})do +local genname=_unit.Name +local stock0=_unit.Stock0 or 0 +if stock0>0 and not Troopstable[genname]then +local stock=_unit.Stock or 0 +local rel=stock0>0 and math.floor((stock/stock0)*100)or 100 +Troopstable[genname]={ +Stock0=stock0, +Stock=stock, +StockR=rel, +Infield=0, +Inhelo=0, +CratesInfield=0, +Sum=stock, +} +end +end for _id,_cargo in pairs(self.Cargo_Troops)do local generic=_cargo local genname=generic:GetName() @@ -76807,10 +80322,31 @@ Troopstable[genname].Infield=Troopstable[genname].Infield+1 Troopstable[genname].Sum=Troopstable[genname].Infield+Troopstable[genname].Stock+Troopstable[genname].Inhelo end else +local gname=_group:GetName() +local uName=nil +for _,cfg in pairs(self.C130GetUnits or{})do +local templ=cfg.Templates or{} +if type(templ)=="string"then +templ={templ} +end +for _,tName in pairs(templ)do +if string.find(gname,tName,1,true)then +uName=cfg.Name +break +end +end +if uName then break end +end +if uName and Troopstable[uName]then +self:T("Found C-130 unit "..uName.." in the field. Adding.") +Troopstable[uName].Infield=Troopstable[uName].Infield+1 +Troopstable[uName].Sum=Troopstable[uName].Infield+Troopstable[uName].Stock+Troopstable[uName].Inhelo +else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) end end end +end for _unitname,_loaded in pairs(self.Loaded_Cargo)do local _unit=UNIT:FindByName(_unitname) if _unit and _unit:IsAlive()then @@ -76819,12 +80355,12 @@ local loadedcargo=self.Loaded_Cargo[unitname].Cargo or{} for _,_cgo in pairs(loadedcargo)do local cargo=_cgo local type=cargo.CargoType -local gname=cargo.Name +local gname=cargo:GetName() local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname) -self:T("Looking at "..gname.." in the helo - type = "..type) +self:T("Looking at "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS or type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then if gcargo and gcargo:GetStock0()>0 then -self:T("Adding "..gname.." in the helo - type = "..type) +self:T("Adding "..gname.." in the helo - type = "..tostring(type)) if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then Troopstable[gname].Inhelo=Troopstable[gname].Inhelo+1 end @@ -76892,6 +80428,47 @@ end end return Troopstable end +function CTLD:AddStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock+number +end +break +end +end +return self +end +function CTLD:SetStockUnits(Name,Number) +local name=Name or"none" +local number=Number +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +if number==nil or number==-1 then +_unit.Stock=-1 +else +_unit.Stock=number +end +break +end +end +return self +end +function CTLD:GetStockUnits() +local Stock={} +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +Stock[_unit.Name]=_unit.Stock or-1 +end +return Stock +end function CTLD:AddStockTroops(Name,Number) local name=Name or"none" local number=Number or 1 @@ -76899,6 +80476,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76911,6 +80489,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76923,6 +80502,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:AddStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -76935,6 +80515,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshCrateQuantityMenus(nil,nil,_troop) break end end @@ -76947,6 +80528,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) break end end @@ -76959,6 +80541,7 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:SetStock(number) +self:_RefreshQuantityMenusForGroup() break end end @@ -77009,6 +80592,7 @@ local gentroops=self.Cargo_Troops for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshTroopQuantityMenus(nil,nil,_troop) end end return self @@ -77020,6 +80604,7 @@ local gentroops=self.Cargo_Crates for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() end end return self @@ -77031,6 +80616,27 @@ local gentroops=self.Cargo_Statics for _id,_troop in pairs(gentroops)do if _troop.Name==name then _troop:RemoveStock(number) +self:_RefreshQuantityMenusForGroup() +end +end +return self +end +function CTLD:RemoveStockUnits(Name,Number) +local name=Name or"none" +local number=Number or 1 +local units=self.C130GetUnits or{} +for _id,_unit in pairs(units)do +if _unit.Name==name then +local stock=_unit.Stock +if stock==nil or stock==-1 then +_unit.Stock=-1 +else +_unit.Stock=stock-number +if _unit.Stock<0 then +_unit.Stock=0 +end +end +break end end return self @@ -77480,11 +81086,44 @@ 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) +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end end return self end function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable) self:T({From,Event,To}) +if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then +local playername=Unit:GetPlayerName() +for _,_cargo in pairs(Cargotable)do +local Vehicle=_cargo.Positionable +if Vehicle then +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 +end +end +return self +end +function CTLD:OnAfterGetCrates(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) +return self +end +function CTLD:OnAfterRemoveCratesNearby(From,Event,To,Group,Unit,Cargotable) +self:T({From,Event,To}) return self end function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle) @@ -77512,9 +81151,13 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end +if not Group or not Unit then self:_RefreshQuantityMenusForGroup()end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -77761,11 +81404,22 @@ 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) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) +if self.C130GetUnits then +for _,_unit in pairs(self.C130GetUnits)do +if _unit.Name==cargoname then +if type(_unit.Stock)=="number"and _unit.Stock~=-1 then +_unit.Stock0=_unit.Stock0 or _unit.Stock +_unit.Stock=math.max(0,(_unit.Stock or 0)-1) +end +break +end +end +end 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,timestamp) end -elseif(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then +elseif self.loadSavedCrates and(type(groupname)=="string"and groupname=="STATIC")or cargotype==CTLD_CARGO.Enum.REPAIR then local dropzone=ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic=nil if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then @@ -77777,8 +81431,12 @@ injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) 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) injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) -local map=cargotype:GetStaticResourceMap() -injectstatic:SetStaticResourceMap(map) +local unittemplate=_DATABASE:GetStaticUnitTemplate(cargoname) +local ResourceMap=nil +if unittemplate and unittemplate.resourcePayload then +ResourceMap=UTILS.DeepCopy(unittemplate.resourcePayload) +end +injectstatic:SetStaticResourceMap(ResourceMap) end if injectstatic then self:InjectStatics(dropzone,injectstatic,false,true) @@ -78247,6 +81905,8 @@ CreateRadioBeacons=true, UserSetGroup=nil, AllowIRStrobe=false, IRStrobeRuntime=300, +FARPRescueDistance=500, +EnableMenuSmokeMASH=true, } CSAR.AircraftType={} CSAR.AircraftType["SA342Mistral"]=2 @@ -78260,13 +81920,14 @@ CSAR.AircraftType["Mi-24P"]=8 CSAR.AircraftType["Mi-24V"]=8 CSAR.AircraftType["Bell-47"]=2 CSAR.AircraftType["UH-60L"]=10 +CSAR.AircraftType["UH-60L_DAP"]=2 CSAR.AircraftType["AH-64D_BLK_II"]=2 CSAR.AircraftType["Bronco-OV-10A"]=2 CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.35" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -78346,12 +82007,13 @@ self.loadtimemax=135 self.radioSound="beacon.ogg" self.beaconRefresher=29 self.allowFARPRescue=true -self.FARPRescueDistance=1000 +self.FARPRescueDistance=500 self.max_units=6 self.useprefix=true self.csarPrefix={"helicargo","MEDEVAC"} self.template=Template or"generic" self.mashprefix={"MASH"} +self.EnableMenuSmokeMASH=true self.autosmoke=false self.autosmokedistance=2000 self.limitmaxdownedpilots=true @@ -78529,6 +82191,9 @@ BeaconName=_unitName..math.random(1,10000) else BeaconName="Ghost-1-1"..math.random(1,10000) end +if _playerName==nil or _playerName==""then +_playerName="AI MIA" +end if(_freq and _freq~=0)then self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName) end @@ -78712,11 +82377,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78771,7 +82436,7 @@ end self.takenOff[_event.IniUnitName]=nil local _place=_event.Place if _place==nil then -self:T(self.lid.." Landing Place Nil") +self:T(self.lid.." Landing Place nil") return self end if self.inTransitGroups[_event.IniUnitName]==nil then @@ -78779,7 +82444,10 @@ 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) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -78796,7 +82464,7 @@ 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:T({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq}) self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,self.suppressmessages,"none") Unit.destroy(_event.initiator) end @@ -79134,7 +82802,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -79152,7 +82820,13 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist0 then +local debugtext=string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) +self:T("*******************************") +self:T(debugtext) +self:T("*******************************") +end +if(_distself.FARPRescueDistance*1.1 then +_shortestDistance=100 +end +end +end end for _,_mashes in pairs(MashSets)do for _,_mashUnit in pairs(_mashes or{})do @@ -79472,11 +83186,12 @@ _distance=self:_GetDistance(_helicoord,_mashcoord) if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then _shortestDistance=_distance MashName=_mashUnit:GetName()or"Unknown" +Coordinate=_mashcoord end end end if _shortestDistance~=-1 then -return _shortestDistance,MashName +return _shortestDistance,MashName,Coordinate else return-1 end @@ -79531,6 +83246,9 @@ local _rootMenu3=MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, local _rootMenu4=MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath,self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh() +end +if self.EnableMenuSmokeMASH then +local _rootMenu6=MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath,self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -79545,6 +83263,7 @@ self:T(self.lid.." _GetDistance") if _point1 and _point2 then local distance1=_point1:Get2DDistance(_point2) local distance2=_point1:DistanceFromPointVec2(_point2) +MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1)=="number"then return distance1 elseif distance2 and type(distance2)=="number"then @@ -79616,7 +83335,7 @@ local name=_radioUnit:GetName() local Frequency=_freq local Sound="l10n/DEFAULT/"..self.radioSound local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0} -self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) +self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound,vec3,0,true,Frequency,self.ADFRadioPwr or 500,BeaconName) end end @@ -79926,6 +83645,7 @@ for _,_grp in pairs(pilots)do local DownedPilot=_grp if DownedPilot and DownedPilot.alive then local playerName=DownedPilot.player +if playerName==nil or playerName==""then playerName="AI MIA"end local group=DownedPilot.group local coalition=group:GetCoalition() local country=group:GetCountry() @@ -79935,7 +83655,7 @@ 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) +self:T(self.lid.."Saving to CSAR File: "..txt) data=data..txt end end @@ -80005,8 +83725,8 @@ 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) +MESSAGE:New(text,10):ToAllIf(self.verbose>0) +self:T(self.lid..text) local file=assert(io.open(filename,"rb")) local loadeddata={} for line in file:lines()do @@ -80081,6 +83801,23 @@ Squadron:SetAirwing(self) if Squadron:IsStopped()then Squadron:Start() end +local airbasename=self:GetAirbaseName() +if airbasename then +local group=GROUP:FindByName(Squadron.templategroup) +local Nunits=1 +local units +if group then units=group:GetUnits()end +if units then Nunits=#units end +local typename=Squadron.aircrafttype or"none" +local NAssets=Squadron.Ngroups*Nunits +local storage=STORAGE:New(airbasename) +if storage and storage.warehouse and storage:IsLimitedAircraft()and typename~="none"then +local NInStore=storage:GetItemAmount(typename)or 0 +if NAssets>NInStore then +storage:AddItem(typename,NAssets) +end +end +end return self end function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance) @@ -81010,8 +84747,8 @@ 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) +text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, cannons=%d, rockets=%d, missiles=%d, cargo=%d/%d kg", +i,name,status,life,life0,ammo.Guns,ammo.Cannons,ammo.Rockets,ammo.Missiles,element.weightCargo,element.weightMaxCargo) end if#self.elements==0 then text=text.." none!" @@ -81351,7 +85088,7 @@ end return true end function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation) -self:T2(self.lid.."onafterRTZ") +self:T(self.lid.."onafterRTZ") local zone=Zone or self.homezone self:CancelAllMissions() if zone then @@ -81465,9 +85202,9 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95) self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true) -self.engage.Waypoint.detour=0 +self.engage.Waypoint.detour=1 end else self:T(self.lid.."Could not get position of target ==> Disengage!") @@ -81743,6 +85480,7 @@ CASENHANCED="CAS Enhanced", HOVER="Hover", LANDATCOORDINATE="Land at Coordinate", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", CARGOTRANSPORT="Cargo Transport", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defence", @@ -81766,6 +85504,7 @@ BARRAGE="Barrage", ARMORATTACK="AmorAttack", HOVER="Hover", GROUNDATTACK="Ground Attack", +NAVALENGAGEMENT="Naval Engagement", FERRY="Ferry", RELOCATECOHORT="Relocate Cohort", AIRDEFENSE="Air Defense", @@ -81813,7 +85552,7 @@ HELICOPTER="Helicopter", GROUND="Ground", NAVAL="Naval", } -AUFTRAG.version="1.2.1" +AUFTRAG.version="1.3.0" function AUFTRAG:New(Type) local self=BASE:Inherit(self,FSM:New()) _AUFTRAGSNR=_AUFTRAGSNR+1 @@ -82322,7 +86061,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) mission:_TargetFromObject(Carrier) mission.missionTask=ENUMS.MissionTask.NOTHING -mission.missionFraction=0.5 +mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction mission.categories={AUFTRAG.Category.HELICOPTER} @@ -82434,7 +86173,7 @@ mission.DCStask=mission:GetDCSMissionTask() mission.DCStask.params.formation=Formation or"Off Road" return mission end -function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation,StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) mission:_TargetFromObject(OpsZone) mission.coalition=Coalition @@ -82442,6 +86181,7 @@ mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZON mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto +mission.StayInZoneTime=StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil @@ -82476,6 +86216,20 @@ mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.mi mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission end +function AUFTRAG:NewNAVALENGAGEMENT(Target,Speed,Depth) +local mission=AUFTRAG:New(AUFTRAG.Type.NAVALENGAGEMENT) +mission:_TargetFromObject(Target) +mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.NAVALENGAGEMENT) +mission.optionROE=ENUMS.ROE.OpenFire +mission.optionAlarm=ENUMS.AlarmState.Auto +mission.missionFraction=0.70 +mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil +mission.missionAltitude=Depth or 0 +mission.categories={AUFTRAG.Category.NAVAL} +mission.DCStask=mission:GetDCSMissionTask() +mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil +return mission +end function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation) local mission=AUFTRAG:New(AUFTRAG.Type.RECON) mission:_TargetFromObject(ZoneSet) @@ -82611,33 +86365,35 @@ end function AUFTRAG:NewFromTarget(Target,MissionType) local mission=nil if MissionType==AUFTRAG.Type.ANTISHIP then -mission=self:NewANTISHIP(Target,Altitude) +mission=self:NewANTISHIP(Target) elseif MissionType==AUFTRAG.Type.ARTY then -mission=self:NewARTY(Target,Nshots,Radius) +mission=self:NewARTY(Target,0.3) elseif MissionType==AUFTRAG.Type.BAI then -mission=self:NewBAI(Target,Altitude) +mission=self:NewBAI(Target) elseif MissionType==AUFTRAG.Type.BOMBCARPET then -mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength) +mission=self:NewBOMBCARPET(Target) elseif MissionType==AUFTRAG.Type.BOMBING then -mission=self:NewBOMBING(Target,Altitude) +mission=self:NewBOMBING(Target) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then -mission=self:NewBOMBRUNWAY(Target,Altitude) +mission=self:NewBOMBRUNWAY(Target) elseif MissionType==AUFTRAG.Type.STRAFING then -mission=self:NewSTRAFING(Target,Altitude) +mission=self:NewSTRAFING(Target) elseif MissionType==AUFTRAG.Type.CAS then -mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes) +mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),nil,nil,Target:GetAverageCoordinate()) elseif MissionType==AUFTRAG.Type.CASENHANCED then -mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes) +mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000)) elseif MissionType==AUFTRAG.Type.INTERCEPT then mission=self:NewINTERCEPT(Target) elseif MissionType==AUFTRAG.Type.SEAD then -mission=self:NewSEAD(Target,Altitude) +mission=self:NewSEAD(Target) elseif MissionType==AUFTRAG.Type.STRIKE then -mission=self:NewSTRIKE(Target,Altitude) +mission=self:NewSTRIKE(Target) elseif MissionType==AUFTRAG.Type.ARMORATTACK then -mission=self:NewARMORATTACK(Target,Speed) +mission=self:NewARMORATTACK(Target) elseif MissionType==AUFTRAG.Type.GROUNDATTACK then -mission=self:NewGROUNDATTACK(Target,Speed,Formation) +mission=self:NewGROUNDATTACK(Target) +elseif MissionType==AUFTRAG.Type.NAVALENGAGEMENT then +mission=self:NewNAVALENGAGEMENT(Target) else return nil end @@ -82698,7 +86454,7 @@ 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) +mission=AUFTRAG:NewARTY(Target,0.2) elseif auftrag==AUFTRAG.Type.AWACS then mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.BAI then @@ -83261,6 +87017,15 @@ function AUFTRAG:IsOver() local over=self.status==AUFTRAG.Status.DONE or self.status==AUFTRAG.Status.CANCELLED or self.status==AUFTRAG.Status.SUCCESS or self.status==AUFTRAG.Status.FAILED return over end +function AUFTRAG:IsRepeatable() +local repeatmeS=self.repeatedSuccess=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) +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)) @@ -83465,7 +87230,7 @@ function AUFTRAG:Evaluate() local failed=false local targetdamage=self:GetTargetDamage() local owndamage=self.Ncasualties/self.Nelements*100 -local Ntargets=self:CountMissionTargets() +local Ntargets=self:CountMissionTargets(true) local Ntargets0=self:GetTargetInitialNumber() local Life=self:GetTargetLife() local Life0=self:GetTargetInitialLife() @@ -83711,12 +87476,18 @@ 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 +local NopsgroupsAlive=self:CountOpsGroups() +local NopsgroupsDone=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.DONE)+self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.CANCELLED) +if self:IsStarted()and NopsgroupsAlive==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 -if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and self:CountOpsGroups()>0 then -self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) +if NopsgroupsAlive==NopsgroupsDone then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] but all groups [=%d] are done or cancelled. Mission DONE!",self.status,self:GetState(),NopsgroupsAlive)) +return true +end +if(self:IsStarted()or self:IsExecuting())and(fsmState==AUFTRAG.Status.STARTED or fsmState==AUFTRAG.Status.EXECUTING)and NopsgroupsAlive>0 then +self:T(self.lid..string.format("CheckGroupsDone: Mission is in state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!",self.status,self:GetState())) return false end return true @@ -83981,11 +87752,11 @@ else end return self end -function AUFTRAG:CountMissionTargets() +function AUFTRAG:CountMissionTargets(OnlyReallyAlive) local N=0 local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil if self.engageTarget then -N=self.engageTarget:CountTargets(Coalitions) +N=self.engageTarget:CountTargets(Coalitions,OnlyReallyAlive) end return N end @@ -84523,6 +88294,15 @@ param.action="Wedge" param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil DCStask.params=param table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.NAVALENGAGEMENT then +local DCStask={} +DCStask.id=AUFTRAG.SpecialTask.NAVALENGAGEMENT +local param={} +param.target=self:GetTargetData() +param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil +param.altitude=self.missionAltitude or 0 +DCStask.params=param +table.insert(DCStasks,DCStask) elseif self.type==AUFTRAG.Type.AMMOSUPPLY then local DCStask={} DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY @@ -84783,7 +88563,7 @@ end do AWACS={ ClassName="AWACS", -version="0.2.72", +version="0.2.74", lid="", coalition=coalition.side.BLUE, coalitiontxt="blue", @@ -85400,6 +89180,11 @@ self:T(self.lid.."SetLocale") self.locale=Locale or"en" return self end +function AWACS:SetBullsCoordinate(Coordinate) +self:T(self.lid.."SetBullsCoordinate") +self.AOCoordinate=Coordinate +return self +end function AWACS:SetMaxMissionRange(NM) self.MaxMissionRange=NM or 125 return self @@ -85659,7 +89444,9 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) +end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -85672,8 +89459,37 @@ 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) +if self.AllowMarkers then MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) end +end +return self +end +function AWACS:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function AWACS:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling return self end function AWACS:DrawFEZ() @@ -87150,11 +90966,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) self.PlayerStationName=newname return self @@ -87183,11 +91003,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) else local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1) @@ -87208,11 +91032,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) end return true,self.AnchorStacks:GetSize() @@ -87300,6 +91128,12 @@ intel:SetFilterCategory({Unit.Category.AIRPLANE}) else intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end +if self.usecorridors==true then +intel:SetCorridorZones(self.corridorzones) +if self.corridorceiling or self.corridorfloor then +intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) +end +end local function NewCluster(Cluster) self:__NewCluster(5,Cluster) end @@ -87843,11 +91677,15 @@ 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()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end else local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation=true AirWing.StationName=newname @@ -88486,24 +92324,36 @@ 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) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end 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 +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) +end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end else local AOCoordString=self.AOCoordinate:ToStringLLDDM() local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) +if self.AllowMarkers then MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) +end if not self.GCI then +if self.AllowMarkers then MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) end +end local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) +if self.AllowMarkers then MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) end +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) @@ -89518,6 +93368,65 @@ text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asse end self:I(self.lid..text) end +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:" +local legion=self +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 +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 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())) @@ -89547,7 +93456,7 @@ OFFENSIVE="Offensive", AGGRESSIVE="Aggressive", TOTALWAR="Total War" } -CHIEF.version="0.6.1" +CHIEF.version="0.7.0" function CHIEF:New(Coalition,AgentSet,Alias) Alias=Alias or"CHIEF" if type(Coalition)=="string"then @@ -90577,6 +94486,8 @@ 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)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.NAVALENGAGEMENT,50)) +table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30)) else self:E(self.lid.."ERROR: Unknown Group category!") end @@ -91014,8 +94925,11 @@ end end return self end -function COHORT:RemoveAssets(N) +function COHORT:RemoveAssets(N,Delay) self:T2(self.lid..string.format("Remove %d assets of Cohort",N)) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,COHORT.RemoveAssets,self,N,0) +else N=N or 1 local n=0 for i=#self.assets,1,-1 do @@ -91023,6 +94937,7 @@ local asset=self.assets[i] self:T2(self.lid..string.format("Checking removing asset %s",asset.spawngroupname)) if not(asset.requested or asset.spawned or asset.isReserved)then self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname)) +asset.legion:_DeleteStockItem(asset) table.remove(self.assets,i) n=n+1 else @@ -91033,6 +94948,7 @@ break end end self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d",n,N,#self.assets)) +end return self end function COHORT:GetName() @@ -96967,7 +100883,7 @@ 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) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -97191,6 +101107,9 @@ clustermarkers=false, clusterarrows=false, prediction=300, detectStatics=false, +DetectAccoustic=false, +DetectAccousticRadius=1000, +DetectAccousticUnitTypes={Unit.Category.HELICOPTER}, } INTEL.Ctype={ GROUND="Ground", @@ -97198,7 +101117,7 @@ NAVAL="Naval", AIRCRAFT="Aircraft", STRUCTURE="Structure" } -INTEL.version="0.3.6" +INTEL.version="0.3.10" function INTEL:New(DetectionSet,Coalition,Alias) local self=BASE:Inherit(self,FSM:New()) self.detectionset=DetectionSet or SET_GROUP:New() @@ -97251,6 +101170,7 @@ self:AddTransition("*","LostCluster","*") self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() +self:SetCorridorZones() self:SetConflictZones() return self end @@ -97258,6 +101178,16 @@ function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) +self.DetectAccoustic=true +self.DetectAccousticRadius=Radius or 1000 +self.DetectAccousticUnitTypes=UnitCategories or{Unit.Category.HELICOPTER} +return self +end +function INTEL:SetAccousticDetectionOff() +self.DetectAccoustic=false +return self +end function INTEL:AddAcceptZone(AcceptZone) self.acceptzoneset:AddZone(AcceptZone) return self @@ -97290,6 +101220,30 @@ function INTEL:RemoveConflictZone(ConflictZone) self.conflictzoneset:Remove(ConflictZone:GetName(),true) return self end +function INTEL:SetCorridorZones(CorridorZoneSet) +self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() +return self +end +function INTEL:AddCorridorZone(CorridorZone) +self.corridorzoneset:AddZone(CorridorZone) +return self +end +function INTEL:RemoveCorridorZone(CorridorZone) +self.corridorzoneset:Remove(CorridorZone:GetName(),true) +return self +end +function INTEL:SetCorridorLimits(Floor,Ceiling) +self.corridorceiling=Ceiling or 10000 +self.corridorfloor=Floor or 1 +return self +end +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) +local Ceiling=Ceiling or 25000 +local Floor=Floor or 15000 +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +return self +end function INTEL:SetForgetTime(TimeInterval) return self end @@ -97456,6 +101410,17 @@ 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 +if self.DetectAccoustic then +local recce=group:GetFirstUnitAlive() +local detectionzone=group:GetProperty("INTEL_DETECT_ACCZONE") +if not detectionzone then +detectionzone=ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) +group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) +end +if recce and recce:IsGround()then +self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) +end +end end end local remove={} @@ -97497,6 +101462,33 @@ if inzone and(not inconflictzone)then table.insert(remove,unitname) end end +if self.corridorzoneset:Count()>0 then +self:T("Corridorzone Check for unit "..unit:GetName()) +local inzone=false +for _,_zone in pairs(self.corridorzoneset.Set)do +local zone=_zone +if unit:IsInZone(zone)then +local corridorfloor=zone:GetProperty("CorridorFloor")or self.corridorfloor +local corridorceiling=zone:GetProperty("CorridorCeiling")or self.corridorceiling +local debugtext="Corridorzone Check for unit "..unit:GetName().."\n" +debugtext=debugtext..string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), +tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) +MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) +if unit:IsAir()and(corridorfloor~=nil or corridorceiling~=nil)then +local alt=unit:GetAltitude() +if corridorfloor and alt>corridorfloor then inzone=true end +if corridorceiling and(inzone==true or corridorfloor==nil)and alt0 and unit:IsInstanceOf("UNIT")then local unitcategory=unit:GetUnitCategory() local keepit=false @@ -97711,6 +101703,25 @@ end end end end +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) +local othercoalition=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE +self:T("Other coalition = "..othercoalition) +if detectionzone then +local reccename=Recce:GetName() +local DetectAccousticUnitTypes=self.DetectAccousticUnitTypes or{Unit.Category.HELICOPTER} +detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) +local unitset=detectionzone:GetScannedSetUnit(othercoalition) +self:T("Accoustic detection found #Units "..unitset:CountAlive()) +for _,_unit in pairs(unitset.Set or{})do +if _unit and _unit:IsAlive()and _unit:GetCoalition()~=self.coalition then +local name=_unit:GetName()or"none" +DetectedUnits[name]=_unit +RecceDetecting[name]=reccename +self:T("Unit name = "..name) +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) @@ -98560,6 +102571,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98615,7 +102633,7 @@ 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)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -99106,7 +103124,7 @@ text=text..string.format("Missions: %d [Running=%d]\n",NmissionsTotal,NmissionsR for _,mtype in pairs(AUFTRAG.Type)do local n=self:CountMissionsInQueue(mtype) if n>0 then -local N=self:CountMissionsInQueue(mtype) +local N=self:CountMissionsInQueue(mtype,true) text=text..string.format(" - %s: %d [Running=%d]\n",mtype,n,N) end end @@ -99201,15 +103219,17 @@ end end return n end -function LEGION:CountMissionsInQueue(MissionTypes) +function LEGION:CountMissionsInQueue(MissionTypes,OnlyRunning) MissionTypes=MissionTypes or AUFTRAG.Type local N=0 for _,_mission in pairs(self.missionqueue)do local mission=_mission +if(not OnlyRunning)or(mission.statusLegion~=AUFTRAG.Status.PLANNED)then if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then N=N+1 end end +end return N end function LEGION:CountAssets(InStock,MissionTypes,Attributes) @@ -99963,7 +103983,7 @@ Qintowind={}, pathCorridor=400, engage={}, } -NAVYGROUP.version="1.0.3" +NAVYGROUP.version="1.0.4" function NAVYGROUP:New(group) local og=_DATABASE:GetOpsGroup(group) if og then @@ -100330,7 +104350,7 @@ 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("Ammo = %d (G=%d/C=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Cannons,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())) @@ -100554,7 +104574,7 @@ 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) +function NAVYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Depth) self:T(self.lid.."Engaging Target") if Target:IsInstanceOf("TARGET")then self.engage.Target=Target @@ -100562,13 +104582,15 @@ 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) +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) 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) +local uid=self:GetWaypointCurrentUID() +self.engage.Depth=Depth or 0 +self.engage.Speed=Speed +self.engage.Waypoint=self:AddWaypoint(intercoord,Speed,uid,Depth,true) self.engage.Waypoint.detour=1 end function NAVYGROUP:_UpdateEngageTarget() @@ -100578,11 +104600,11 @@ 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 +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,nil,uid,Formation,true) -self.engage.Waypoint.detour=0 +local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.8) +self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Depth,true) +self.engage.Waypoint.detour=1 end else self:Disengage() @@ -100596,8 +104618,8 @@ 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!") +if task and(task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT)then +self:T(self.lid.."Disengage with current task GROUNDATTACK/NAVALENGAGEMENT ==> Task Done!") self:TaskDone(task) end if self.engage.Waypoint then @@ -102626,6 +106648,12 @@ end function OPSGROUP:IsOutOfTorpedos() return self.outofTorpedos end +function OPSGROUP:IsOutOfA2GAmmo() +if(self.outofMissilesAG and self.outofBombs and self.outofGuns)or self.outofAmmo then +return true +end +return false +end function OPSGROUP:IsLasing() return self.spot.On end @@ -103405,14 +107433,32 @@ end function OPSGROUP:_UpdateTask(Task,Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then +if Mission.type==AUFTRAG.Type.RESCUEHELO then +self:T("**********") +self:T("** RESCUEHELO USED") +self:T("**********") +local param=Task.dcstask.params +local followUnit=UNIT:FindByName(param.unitname) +local helogroupname=self:GetGroup():GetName() +Task.formation=RESCUEHELO:New(followUnit,helogroupname) +Task.formation:SetRespawnOnOff(false) +Task.formation.respawninair=false +Task.formation:SetTakeoffCold() +Task.formation:SetHomeBase(followUnit) +Task.formation.helo=self:GetGroup() +Task.formation:Start() +if self:IsFlightgroup()then +self:SetDespawnAfterLanding() +end +else 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=FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION) 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() +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then local zone=Task.dcstask.params.zone local surfacetypes=nil @@ -103496,6 +107542,15 @@ end if target then self:EngageTarget(target,speed,Task.dcstask.params.formation) end +elseif Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT 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=UTILS.MpsToKnots(Task.dcstask.params.speed) +end +if target then +self:EngageTarget(target,speed,Task.dcstask.params.altitude) +end elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then if self.isFlightgroup then self:T("We are Special Auftrag Patrol Race Track, starting now ...") @@ -103555,7 +107610,12 @@ 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())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -103603,7 +107663,7 @@ nAmmo=ammo.MissilesCR elseif weaponType==ENUMS.WeaponFlag.AnyRocket then nAmmo=ammo.Rockets elseif weaponType==ENUMS.WeaponFlag.Cannons then -nAmmo=ammo.Guns +nAmmo=ammo.Cannons end local nShots=DCSTask.params.expendQty or 1 self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo)) @@ -103679,7 +107739,7 @@ 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 +elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.NAVALENGAGEMENT then done=true elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then done=true @@ -104059,7 +108119,7 @@ end end end function OPSGROUP:onafterMissionDone(From,Event,To,Mission) -local text=string.format("Mission %s DONE!",Mission.name) +local text=string.format("Mission DONE %s!",Mission.name) self:T(self.lid..text) Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE) if self:IsOnMission(Mission.auftragsnummer)then @@ -104339,10 +108399,12 @@ end if targetzone and self:IsInZone(targetzone)then self:T(self.lid.."Already in mission zone ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return elseif d<25 then self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()") self:TaskExecute(waypointtask) +self:PassingWaypoint(waypoint) return end if(self.speedMax<=3.6 or mission.teleport)and not mission.unpaused then @@ -104922,7 +108984,7 @@ 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:T(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() @@ -105069,7 +109131,6 @@ function OPSGROUP:_Spawn(Delay,Template) if Delay and Delay>0 then self:ScheduleOnce(Delay,OPSGROUP._Spawn,self,0,Template) else -self:T2({Template=Template}) if self:IsArmygroup()and self.ValidateAndRepositionGroundUnits then UTILS.ValidateAndRepositionGroundUnits(Template.units) end @@ -105137,9 +109198,11 @@ end if self.legion then if not self:IsInUtero()then local asset=self.legion:GetAssetByName(self.groupname) +if asset then local request=self.legion:GetRequestByID(asset.rid) self.legion:AssetDead(asset,request) end +end self:__Stop(-5) elseif not self.isAI then self:__Stop(-1) @@ -105195,7 +109258,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(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())) @@ -106710,10 +110773,10 @@ if ammo.Total==0 and not self.outofAmmo then self.outofAmmo=true self:OutOfAmmo() end -if self.outofGuns and ammo.Guns>0 then +if self.outofGuns and ammo.Shells>0 then self.outofGuns=false end -if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then +if ammo.Shells==0 and self.ammo.Shells>0 and not self.outofGuns then self.outofGuns=true self:OutOfGuns() end @@ -107478,7 +111541,7 @@ 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:T(self.lid..string.format("Setting Default callsign %s-%s",tostring(CallsignName),tostring(CallsignNumber))) self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -107843,7 +111906,9 @@ function OPSGROUP:GetAmmoTot() local units=self.group:GetUnits() local Ammo={} Ammo.Total=0 +Ammo.Shells=0 Ammo.Guns=0 +Ammo.Cannons=0 Ammo.Rockets=0 Ammo.Bombs=0 Ammo.Torpedos=0 @@ -107858,7 +111923,9 @@ local unit=_unit if unit and unit:IsExist()then local ammo=self:GetAmmoUnit(unit) Ammo.Total=Ammo.Total+ammo.Total +Ammo.Shells=Ammo.Shells+ammo.Shells Ammo.Guns=Ammo.Guns+ammo.Guns +Ammo.Cannons=Ammo.Cannons+ammo.Cannons Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos @@ -107878,6 +111945,8 @@ display=false end local nammo=0 local nshells=0 +local nguns=0 +local ncannons=0 local nrockets=0 local nmissiles=0 local nmissilesAA=0 @@ -107908,6 +111977,14 @@ 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"]["caliber"]then +local caliber=ammotable[w]["desc"]["warhead"]["caliber"] +if caliber<25 then +nguns=nguns+Nammo +else +ncannons=ncannons+Nammo +end +end 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 @@ -107953,7 +112030,9 @@ end nammo=nshells+nrockets+nmissiles+nbombs+ntorps local ammo={} ammo.Total=nammo -ammo.Guns=nshells +ammo.Shells=nshells +ammo.Guns=nguns +ammo.Cannons=ncannons ammo.Rockets=nrockets ammo.Bombs=nbombs ammo.Torpedos=ntorps @@ -110002,8 +114081,11 @@ NextTaskSuccess={}, NextTaskFailure={}, FinalState="none", PreviousCount=0, +CanSmoke=true, +ShowThreatDetails=true, +PersistMe=false, } -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.31" function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType) local self=BASE:Inherit(self,FSM:New()) self.Type=Type @@ -110129,6 +114211,13 @@ end end) return OpsZone:GetOwner()==Coalition and isClientInZone and isCaptureGroupInZone end +function PLAYERTASK:CanJoinTask(Group,Client) +return true +end +function PLAYERTASK:EnablePersistance() +self.PersistMe=true +return self +end function PLAYERTASK:_SetController(Controller) self:T(self.lid.."_SetController") self.TaskController=Controller @@ -110165,6 +114254,16 @@ self:T(self.lid.."AddSubType") self.TaskSubType=Type return self end +function PLAYERTASK:SetCanSmoke(OnOff) +self:T(self.lid.."AddSSetCanSmokeubType") +self.CanSmoke=OnOff +return self +end +function PLAYERTASK:SetShowThreatDetails(OnOff) +self:T(self.lid.."SetShowThreatDetails") +self.ShowThreatDetails=OnOff +return self +end function PLAYERTASK:GetSubType() self:T(self.lid.."GetSubType") return self.TaskSubType @@ -110562,10 +114661,10 @@ self.timestamp=timer.getAbsTime() self:__Stop(-1) return self end -function PLAYERTASK:onafterCancel(From,Event,To) +function PLAYERTASK:onafterCancel(From,Event,To,Silent) self:T({From,Event,To}) if self.TaskController then -self.TaskController:__TaskCancelled(-1,self) +self.TaskController:__TaskCancelled(-1,self,Silent) end self.timestamp=timer.getAbsTime() self.FinalState="Cancelled" @@ -110608,7 +114707,10 @@ if self.TargetMarker then self.TargetMarker:Remove() end self.FinalState="Failed" -self:__Done(-1) +if self.TaskController then +self.TaskController:__TaskFailed(-1,self) +end +self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count=self:GetClientObjects() @@ -110668,6 +114770,12 @@ Scoring=nil, MenuNoTask=nil, InformationMenu=false, TaskInfoDuration=30, +TaskPersistance={}, +TaskPersistanceSwitch=false, +TaskPersistancePath=nil, +TaskPersistanceFilename=nil, +TasksPersistable={}, +SceneryExplosivesAmount=300, } PLAYERTASKCONTROLLER.Type={ A2A="Air-To-Air", @@ -110696,6 +114804,12 @@ PLAYERTASKCONTROLLER.Scores={ [AUFTRAG.Type.CAP]=100, [AUFTRAG.Type.CAPTUREZONE]=100, } +PLAYERTASKCONTROLLER.TasksPersistable={ +[AUFTRAG.Type.PRECISIONBOMBING]=true, +[AUFTRAG.Type.BOMBING]=true, +[AUFTRAG.Type.ARTY]=true, +[AUFTRAG.Type.SEAD]=true, +} PLAYERTASKCONTROLLER.SeadAttributes={ SAM=GROUP.Attribute.GROUND_SAM, AAA=GROUP.Attribute.GROUND_AAA, @@ -110724,12 +114838,15 @@ THREATHIGH="high", THREATMEDIUM="medium", THREATLOW="low", THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s", +NOTHREATTEXT="%s\nNo target information available.", 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.", +NOTHREATTEXTTTS="%s, %s. No target information available.", MARKTASK="%s, %s, copy, task %03d location marked on map!", SMOKETASK="%s, %s, copy, task %03d location smoked!", +NOSMOKETASK="%s, %s, negative, task %03d location cannot be smoked!", FLARETASK="%s, %s, copy, task %03d location illuminated!", ABORTTASK="All stations, %s, %s has aborted %s task %03d!", UNKNOWN="Unknown", @@ -110808,12 +114925,15 @@ THREATHIGH="hoch", THREATMEDIUM="mittel", THREATLOW="niedrig", THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", +NOTHREATTEXT="%s\nKeine Zielinformation verfügbar.", ELEVATION="\nZiel Höhe: %s %s", METER="Meter", FEET="Fuss", THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", +NOTHREATTEXTTTS="%s, %s. Keine Zielinformation verfügbar.", MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", +NOSMOKETASK="%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN="Unbekannt", @@ -110871,7 +114991,7 @@ CARRIER="Flugzeugträger", RADIOS="Frequenzen", }, } -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter) local self=BASE:Inherit(self,FSM:New()) self.Name=Name or"CentCom" @@ -110942,11 +115062,24 @@ self:AddTransition("*","PlayerJoinedTask","*") self:AddTransition("*","PlayerAbortedTask","*") self:AddTransition("*","Stop","Stopped") self:__Start(2) -local starttime=math.random(5,10) +local starttime=math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") return self end +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) +self.TaskPersistanceSwitch=true +self.TaskPersistancePath=Path +self.TaskPersistanceFilename=Filename +self.SceneryExplosivesAmount=KgsOfTNT or 300 +return self +end +function PLAYERTASKCONTROLLER:DisableTaskPersistance() +self.TaskPersistanceSwitch=false +self.TaskPersistancePath=nil +self.TaskPersistanceFilename=nil +return self +end function PLAYERTASKCONTROLLER:EnableScoring(Scoring) self.Scoring=Scoring or SCORING:New(self.Name) return self @@ -111297,9 +115430,9 @@ self.ClusterRadius=Radius or 0.5 self.usecluster=true return self end -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") -Task:__Cancel(-1) +Task:__Cancel(-1,Silent) return self end function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff) @@ -111849,17 +115982,24 @@ end PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) -if not Silent then -self:__TaskAdded(10,PlayerTask) -end +self:__TaskAdded(10,PlayerTask,Silent) else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end return self end +function PLAYERTASKCONTROLLER:CanJoinTask(Task,Group,Client) +return true +end function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client) self:T({Force,Group,Client}) self:T(self.lid.."_JoinTask") +if not self:CanJoinTask(Task,Group,Client)then +return self +end +if not Task:CanJoinTask(Group,Client)then +return self +end local force=false if type(Force)=="boolean"then force=Force @@ -111944,7 +116084,7 @@ 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 +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic) @@ -111987,8 +116127,9 @@ local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0) local Elevation=Coordinate:GetLandHeight()or 0 local CoordText="" local CoordTextLLDM=nil +local ShowThreatInfo=task.ShowThreatDetails local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then +if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -112004,7 +116145,12 @@ 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) +if ShowThreatInfo==true then text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText) +else +ThreatLocaleText=self.gettext:GetEntry("NOTHREATTEXT",self.locale) +text=string.format(ThreatLocaleText,taskname) +end local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS local elevationmeasure=self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric()then @@ -112106,8 +116252,14 @@ CoordText="MGRS;"..Text if self.PathToGoogleKey then end end +local ttstext local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale) -local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +if ShowThreatInfo==true then +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText) +else +ThreatLocaleTextTTS=self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) +ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) +end if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -112160,6 +116312,7 @@ local playername,ttsplayername=self:_GetPlayerName(Client) local text="" if self.TasksPerPlayer:HasUniqueID(playername)then local task=self.TasksPerPlayer:ReadByID(playername) +if task.CanSmoke==true then task:SmokeTarget() local textmark=self.gettext:GetEntry("SMOKETASK",self.locale) text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr) @@ -112169,6 +116322,14 @@ self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) end self:__TaskTargetSmoked(5,task) else +local textmark=self.gettext:GetEntry("NOSMOKETASK",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 +end +else text=self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then @@ -112577,6 +116738,49 @@ self:E(self.lid.."*****NO detection has been set up (yet)!") end return self end +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +if self.Intel then +self.Intel:AddCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) +self:T(self.lid.."AddCorridorZoneSet") +if self.Intel then +self.Intel.corridorzoneset:AddSet(CorridorZoneSet) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) +self:T(self.lid.."RemoveCorridorZone") +if self.Intel then +self.Intel:RemoveCorridorZone(CorridorZone) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) +else +self:E(self.lid.."*****NO detection has been set up (yet)!") +end +return self +end +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +if self.Intel then +self.Intel:SetCorridorLimits(Floor,Ceiling) +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 @@ -112708,6 +116912,106 @@ self.BCModulation=Modulation end return self end +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) +self:T(self.lid.."_UpdateTargetsAlive") +local delta=Task.Target:CountTargets()-TargetsLeft +if delta>0 then +self:T("Delta targets to be removed: "..delta) +local count=0 +local targets=Task.Target:GetObjects() +for _,_object in pairs(targets or{})do +if _object and _object.ClassName and(_object:IsInstanceOf("GROUP")or _object:IsInstanceOf("UNIT")or _object:IsInstanceOf("STATIC")or _object:IsInstanceOf("SCENERY"))then +if count0 then -local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +local alert +if self.ClassName=="EASYGCICAP"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) +elseif self.ClassName=="EASYA2G"then +alert=AUFTRAG:NewALERT5(AUFTRAG.Type.BAI) +end alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) @@ -116174,6 +120524,33 @@ self.ConflictZoneSet:AddZone(Zone) self.GoZoneSet:AddZone(Zone) return self end +function EASYGCICAP:SetCorridorZones(CorridorZones) +self:T(self.lid.."SetCorridorZones") +if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE")then +self.corridorzones=CorridorZones +self.usecorridors=true +elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE")then +if not self.corridorzones then self.corridorzones=SET_ZONE:New()end +self.corridorzones:AddZone(CorridorZones) +self.usecorridors=true +end +return self +end +function EASYGCICAP:AddCorridorZone(CorridorZone) +self:T(self.lid.."AddCorridorZone") +self:SetCorridorZones(CorridorZone) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) +self.corridorfloor=UTILS.FeetToMeters(Floor) +self.corridorceiling=UTILS.FeetToMeters(Ceiling) +return self +end +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) +self.corridorfloor=Floor +self.corridorceiling=Ceiling +return self +end function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) self:T("_TryAssignIntercept for size "..WingSize or 1) local assigned=false @@ -116298,6 +120675,8 @@ end if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then success=false end +else +success=true end return success end, @@ -116306,6 +120685,13 @@ nogozoneset, conflictzoneset ) end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -116331,6 +120717,12 @@ BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end BlueIntel:Start() if self.debug then BlueIntel.debug=true @@ -116403,7 +120795,7 @@ 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}) +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.ALERT5,AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK}) self.ReadyFlightGroups=nil self.ReadyFlightGroups={} for _,_asset in pairs(assetsonmission or{})do @@ -116413,7 +120805,8 @@ 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() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) if ready then self.ReadyFlightGroups[name]=FG end @@ -116446,6 +120839,477 @@ _wing:Stop() end return self end +EASYA2G={ +ClassName="EASYA2G", +overhead=0.2, +capgrouping=1, +airbasename=nil, +airbase=nil, +coalition="blue", +alias=nil, +wings={}, +Intel=nil, +resurrection=900, +capspeed=300, +capalt=25000, +capdir=45, +capleg=5, +maxinterceptsize=2, +missionrange=100, +noalert5=2, +ManagedAW={}, +ManagedSQ={}, +ManagedCP={}, +ManagedTK={}, +ManagedEWR={}, +ManagedREC={}, +MaxAliveMissions=8, +debug=false, +engagerange=50, +repeatsonfailure=3, +GoZoneSet=nil, +NoGoZoneSet=nil, +ConflictZoneSet=nil, +Monitor=false, +TankerInvisible=true, +CapFormation=nil, +ReadyFlightGroups={}, +DespawnAfterLanding=false, +DespawnAfterHolding=true, +ListOfAuftrag={}, +defaulttakeofftype="hot", +FuelLowThreshold=25, +FuelCriticalThreshold=10, +showpatrolpointmarks=false, +EngageTargetTypes={"Ground"}, +} +EASYA2G.version="0.1.3" +function EASYA2G:New(Alias,AirbaseName,Coalition,ScoutName) +local self=BASE:Inherit(self,EASYGCICAP:New(Alias,AirbaseName,Coalition,ScoutName)) +self.alias=Alias or AirbaseName.." A2G Wing" +self.coalitionname=string.lower(Coalition)or"blue" +self.coalition=self.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED +self.wings={} +if type(ScoutName)=="string"then ScoutName={ScoutName}end +self.EWRName=ScoutName +self.airbasename=AirbaseName +self.airbase=AIRBASE:FindByName(self.airbasename) +self.GoZoneSet=SET_ZONE:New() +self.NoGoZoneSet=SET_ZONE:New() +self.ConflictZoneSet=SET_ZONE:New() +self.resurrection=900 +self.capspeed=225 +self.capalt=5000 +self.capdir=90 +self.capleg=5 +self.capgrouping=2 +self.missionrange=100 +self.noalert5=2 +self.MaxAliveMissions=8 +self.engagerange=50 +self.repeatsonfailure=3 +self.Monitor=false +self.TankerInvisible=true +self.CapFormation=ENUMS.Formation.FixedWing.FingerFour.Group +self.DespawnAfterLanding=false +self.DespawnAfterHolding=true +self.ListOfAuftrag={} +self.defaulttakeofftype="hot" +self.FuelLowThreshold=25 +self.FuelCriticalThreshold=10 +self.showpatrolpointmarks=false +self.EngageTargetTypes={"Ground"} +self.lid=string.format("EASYA2G %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 EASYA2G:SetTankerAndScoutsInvisible(Switch) +self:T(self.lid.."SetTankerAndScoutsInvisible") +self.TankerInvisible=Switch +return self +end +function EASYA2G:SetDefaultA2GSpeed(Speed) +self:T(self.lid.."SetDefaultSpeed") +self.capspeed=Speed or 300 +return self +end +function EASYA2G:SetA2GFormation(Formation) +self:T(self.lid.."SetA2GFormation") +self.CapFormation=Formation +return self +end +function EASYA2G:SetDefaultA2GAlt(Altitude) +self:T(self.lid.."SetDefaultAltitude") +self.capalt=Altitude or 25000 +return self +end +function EASYA2G:SetDefaultA2GDirection(Direction) +self:T(self.lid.."SetDefaultDirection") +self.capdir=Direction or 90 +return self +end +function EASYA2G:SetDefaultA2GLeg(Leg) +self:T(self.lid.."SetDefaultLeg") +self.capleg=Leg or 5 +return self +end +function EASYA2G:SetDefaultA2GGrouping(Grouping) +self:T(self.lid.."SetDefaultA2GGrouping") +self.capgrouping=Grouping or 2 +return self +end +function EASYA2G:SetA2GStartTimeVariation(Start,End) +self.capOptionVaryStartTime=Start or 5 +self.capOptionVaryEndTime=End or 60 +return self +end +function EASYA2G:SetA2GEngageTargetTypes(types) +self.EngageTargetTypes=types or{"Ground"} +return self +end +function EASYA2G:AddHoldingPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) +self:T(self.lid.."AddHoldingPointA2G") +local coordinate=Coordinate +local EntryCAP={} +if Coordinate:IsInstanceOf("ZONE_BASE")then +coordinate=Coordinate:GetCoordinate() +EntryCAP.Zone=Coordinate +end +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 5 +self.ManagedCP[#self.ManagedCP+1]=EntryCAP +if self.debug then +local mark=MARKER:New(coordinate,self.lid.."Holding Point"):ToAll() +end +return self +end +function EASYA2G:_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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE}) +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.CAS,AUFTRAG.Type.CASENHANCED,AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5,AUFTRAG.Type.BOMBING,AUFTRAG.Type.STRIKE},75) +return self +end +function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) +self:T("_TryAssignMission 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:T(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:T(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.distmindist then +mindist=dist +point=data.Coordinate +end +end +end +return point +end +function EASYA2G:_AssignMission(Cluster) +self:I(self.lid.."_AssignMission") +local overhead=self.overhead +local capspeed=self.capspeed+100 +local capalt=self.capalt or 5000 +local maxsize=self.maxinterceptsize +local repeatsonfailure=self.repeatsonfailure +local wings=self.wings +local ctlpts=self.ManagedCP +local MaxAliveMissions=self.MaxAliveMissions +local nogozoneset=self.NoGoZoneSet +local conflictzoneset=self.ConflictZoneSet +local ReadyFlightGroups=self.ReadyFlightGroups +if Cluster.ctype==INTEL.Ctype.AIRCRAFT then return end +local contact=self.Intel:GetHighestThreatContact(Cluster) +local name=contact.groupname +local threat=contact.threatlevel +local position=self.Intel:CalcClusterFuturePosition(Cluster,300) +local bestdistance=2000*1000 +local targetairwing=nil +local targetawname="" +local clustersize=self.Intel:ClusterCountUnits(Cluster)or 1 +local wingsize=math.abs(overhead*(clustersize+1)) +if wingsize>maxsize 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 Attackers need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"A2G"):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 coa=AIRBASE:FindByName(name):GetCoalition() +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +local samecoalitionab=coa==self.coalition and true or false +if distance=wingsize and samecoalitionab==true 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 +if data.Zone then +zonecoord=data.Zone:GetCoordinate() +end +local airwing=wings[name][1] +local coa=AIRBASE:FindByName(name):GetCoalition() +local samecoalitionab=coa==self.coalition and true or false +local distance=position:DistanceFromPointVec2(zonecoord) +local airframes=airwing:CountAssets(true) +if distance=wingsize and samecoalitionab==true then +bestdistance=distance +targetairwing=airwing +targetawname=name +end +end +local text=string.format("Closest Airwing is %s",targetawname) +local m=MESSAGE:New(text,10,"EasyA2G"):ToAllIf(self.debug):ToLog() +if targetairwing then +local AssetCount=targetairwing:CountAssetsOnMission(MissionTypes,Cohort) +local missioncount=self:_CountAliveAuftrags() +self:T(self.lid.." Assets on Mission "..AssetCount) +if missioncount0 then +InterceptAuftrag:AddConditionSuccess( +function(group,zoneset,conflictset) +local success=false +if group and group:IsAlive()then +local coord=group:GetCoordinate() +if coord and zoneset:Count()>0 and zoneset:IsCoordinateInZone(coord)then +success=true +end +if coord and conflictset:Count()>0 and conflictset:IsCoordinateInZone(coord)then +success=false +end +else +success=true +end +return success +end, +contact.group, +nogozoneset, +conflictzoneset +) +end +InterceptAuftrag:AddConditionFailure( +function() +local failure=false +if InterceptAuftrag:CountOpsGroups()==0 and InterceptAuftrag:IsExecuting()then failure=true end +return failure +end +) +table.insert(self.ListOfAuftrag,InterceptAuftrag) +local assigned,rest=self:_TryAssignMission(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,"EasyA2G"):ToAllIf(self.debug):ToLog() +end +end +end +function EASYA2G:_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.alias) +BlueIntel:SetClusterAnalysis(true,false,false) +BlueIntel:SetForgetTime(300) +BlueIntel:SetAcceptZones(self.GoZoneSet) +BlueIntel:SetRejectZones(self.NoGoZoneSet) +BlueIntel:SetConflictZones(self.ConflictZoneSet) +BlueIntel:SetVerbosity(0) +if self.usecorridors==true then +BlueIntel:SetCorridorZones(self.corridorzones) +if self.corridorfloor or self.corridorceiling then +BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) +end +end +BlueIntel:Start() +if self.debug then +BlueIntel.debug=true +end +local function AssignCluster(Cluster) +self:_AssignMission(Cluster) +end +function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) +AssignCluster(Cluster) +end +self.Intel=BlueIntel +return self +end +function EASYA2G:onafterStart(From,Event,To) +self:T({From,Event,To}) +self:_StartIntel() +self:_CreateAirwings() +self:_CreateSquads() +self:_SetTankerPatrolPoints() +self:_SetAwacsPatrolPoints() +self:_SetReconPatrolPoints() +self:__Status(-10) +return self +end +function EASYA2G:onafterStatus(From,Event,To) +self:T({From,Event,To}) +local cleaned=false +local cleanlist={} +for _,_auftrag in pairs(self.ListOfAuftrag)do +local auftrag=_auftrag +if auftrag and(not(auftrag:IsCancelled()or auftrag:IsDone()or auftrag:IsOver()))then +table.insert(cleanlist,auftrag) +cleaned=true +end +end +if cleaned==true then +self.ListOfAuftrag=nil +self.ListOfAuftrag=cleanlist +end +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 +local alert5mission=0 +for _,_wing in pairs(self.wings)do +local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort) +local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes) +interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.BAI}) +reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON}) +awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER}) +alert5mission=alert5mission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.ALERT5}) +assets=assets+count +instock=instock+count2 +local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.BAI,AUFTRAG.Type.ALERT5}) +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:CanAirToGround() +local isalert5=(FG:GetMissionCurrent()~=nil and FG:GetMissionCurrent().type==AUFTRAG.Type.ALERT5)and true or false +local ready=hasmissiles and FG:IsFuelGood()and(FG:IsAirborne()or isalert5) +self:T(string.format("Flightgroup %s Engaging = %s Ready = %s (HasAmmo = %s HasFuel = %s Alert5 = %s)",tostring(name),tostring(engage),tostring(ready),tostring(hasmissiles),tostring(FG:IsFuelGood()),tostring(isalert5))) +if ready then +self.ReadyFlightGroups[name]=FG +end +end +end +end +if self.Monitor then +local threatcount=#self.Intel.Clusters or 0 +local text=self.alias +text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nHoldPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock +text=text.."\nThreats: "..threatcount +text=text.."\nAirWing alive Missions: "..capmission+awacsmission+tankermission+reconmission+interceptmission+alert5mission +text=text.."\n - A2G Attack: "..interceptmission +text=text.."\n - AWACS: "..awacsmission +text=text.."\n - TANKER: "..tankermission +text=text.."\n - Recon: "..reconmission +text=text.."\n - Alert5 "..alert5mission +text=text.."\nMission Limit: "..self.MaxAliveMissions +MESSAGE:New(text,15,"A2G"):ToAll():ToLogIf(self.debug) +end +self:__Status(30) +return self +end AI_BALANCER={ ClassName="AI_BALANCER", PatrolZones={}, @@ -132120,6 +136984,652 @@ end return true end end +NAVFIX={ +ClassName="NAVFIX", +verbose=0, +} +NAVFIX.Type={ +POINT="Point", +INTERSECTION="Intersection", +AIRPORT="Airport", +NDB="NDB", +VOR="VOR", +DME="DME", +VORDME="VOR/DME", +LOC="Localizer", +ILS="ILS", +TACAN="TACAN" +} +NAVFIX.version="0.1.0" +function NAVFIX:NewFromVector(Name,Type,Vector) +self=BASE:Inherit(self,BASE:New()) +self.vector=Vector +self.name=Name +self.typePoint=Type or NAVFIX.Type.POINT +local coord=COORDINATE:NewFromVec3(self.vector) +self.marker=MARKER:New(coord,self:_GetMarkerText()) +self.lid=string.format("NAVFIX %s [%s] | ",tostring(self.name),tostring(self.typePoint)) +self:I(self.lid..string.format("Created NAVFIX")) +return self +end +function NAVFIX:NewFromCoordinate(Name,Type,Coordinate) +local Vector=VECTOR:NewFromVec(Coordinate) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDMS(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDMS(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromLLDD(Name,Type,Latitude,Longitude) +local Vector=VECTOR:NewFromLLDD(Latitude,Longitude) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromNavFix(Name,Type,NavFix,Distance,Bearing,Reciprocal) +Bearing=Bearing+UTILS.GetMagneticDeclination() +if Reciprocal then +Bearing=Bearing-180 +end +local Vector=NavFix.vector:Translate(UTILS.NMToMeters(Distance),Bearing,true) +self=NAVFIX:NewFromVector(Name,Type,Vector) +return self +end +function NAVFIX:NewFromBeacon(Beacon) +local frequency,unit=BEACONS:_GetFrequency(Beacon.frequency) +frequency=string.format("%.3f",frequency) +if Beacon.typeName=="TACAN"then +frequency=Beacon.channel +unit="X" +end +self=NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) +return self +end +function NAVFIX:SetIntermediateFix(IntermediateFix) +self.isIF=IntermediateFix +return self +end +function NAVFIX:SetInitialApproachFix(IntermediateFix) +self.isIAF=IntermediateFix +return self +end +function NAVFIX:SetFinalApproachFix(FinalApproachFix) +self.isFAF=FinalApproachFix +return self +end +function NAVFIX:SetMissedApproachFix(MissedApproachFix) +self.isMAF=MissedApproachFix +return self +end +function NAVFIX:SetAltMin(Altitude) +self.altMin=Altitude +return self +end +function NAVFIX:SetAltMax(Altitude) +self.altMax=Altitude +return self +end +function NAVFIX:SetAltMandatory(Altitude) +self.altMin=Altitude +self.altMax=Altitude +return self +end +function NAVFIX:SetSpeedMin(Speed) +self.speedMin=Speed +return self +end +function NAVFIX:SetSpeedMax(Speed) +self.speedMax=Speed +return self +end +function NAVFIX:SetSpeedMandatory(Speed) +self.speedMin=Speed +self.speedMax=Speed +return self +end +function NAVFIX:SetCompulsory(Compulsory) +self.isCompulsory=Compulsory +return self +end +function NAVFIX:SetFlyOver(FlyOver) +self.isFlyover=FlyOver +return self +end +function NAVFIX:GetAltitude() +local alt=nil +if self.altMin and self.altMax and self.altMin~=self.altMax then +alt=math.random(self.altMin,self.altMax) +elseif self.altMin then +alt=self.altMin +elseif self.altMax then +alt=self.altMax +end +return alt +end +function NAVFIX:GetSpeed() +local speed=nil +if self.speedMin and self.speedMax and self.speedMin~=self.speedMax then +speed=math.random(self.speedMin,self.speedMax) +elseif self.speedMin then +speed=self.speedMin +elseif self.speedMax then +speed=self.speedMax +end +return speed +end +function NAVFIX:MarkerShow() +self.marker:ToAll() +return self +end +function NAVFIX:MarkerRemove() +self.marker:Remove() +return self +end +function NAVFIX:_GetMarkerText() +local altmin=self.altMin and tostring(self.altMin)or"" +local altmax=self.altMax and tostring(self.altMax)or"" +local speedmin=self.speedMin and tostring(self.speedMin)or"" +local speedmax=self.speedMax and tostring(self.speedMax)or"" +local text=string.format("NAVFIX %s",self.name) +if self.isIAF then +text=text..string.format(" (IAF)") +end +if self.isIF then +text=text..string.format(" (IF)") +end +text=text..string.format("\nAltitude [ft]: %s - %s",altmin,altmax) +text=text..string.format("\nSpeed [knots]: %s - %s",speedmin,speedmax) +text=text..string.format("\nCompulsory: %s",tostring(self.isCompulsory)) +text=text..string.format("\nFly Over: %s",tostring(self.isFlyover)) +return text +end +NAVAID={ +ClassName="NAVAID", +verbose=0, +} +NAVAID.version="0.1.0" +function NAVAID:NewFromScenery(Name,Type,ZoneName,SceneryName) +local zone=ZONE:FindByName(ZoneName) +local Coordinate=zone:GetCoordinate() +self=BASE:Inherit(self,NAVFIX:NewFromCoordinate(Name,Type,Coordinate)) +self.zone=ZONE:FindByName(ZoneName) +if SceneryName then +self.scenery=SCENERY:FindByNameInZone(SceneryName,ZoneName) +if not self.scenery then +self:E(string.format("ERROR: Could not find scenery object %s in zone %s",SceneryName,ZoneName)) +end +end +self.alias=string.format("%s %s %s",tostring(ZoneName),tostring(SceneryName),tostring(Type)) +self.lid=string.format("NAVAID %s | ",self.alias) +self:I(self.lid..string.format("Created NAVAID!")) +return self +end +function NAVAID:SetFrequency(Frequency) +self.frequency=Frequency +return self +end +function NAVAID:SetChannel(Channel,Band) +self.channel=Channel +self.band=Band or"X" +return self +end +BEACONS={ +ClassName="BEACONS", +verbose=1, +beacons={}, +} +BEACONS.version="0.1.0" +function BEACONS:NewFromTable(BeaconTable) +self=BASE:Inherit(self,BASE:New()) +for _,_beacon in pairs(BeaconTable)do +local beacon=_beacon +beacon.vec3={x=beacon.position[1],y=beacon.position[2],z=beacon.position[3]} +beacon.coordinate=COORDINATE:NewFromVec3(beacon.vec3) +beacon.typeName=self:_GetTypeName(beacon.type) +beacon.scenery=beacon.coordinate:FindClosestScenery(20) +if false then +if beacon.scenery then +env.info(string.format("FF Beacon %s %s %s got scenery object %s, %s",beacon.callsign,beacon.beaconId,beacon.typeName,beacon.scenery:GetName(),beacon.scenery:GetTypeName())) +UTILS.PrintTableToLog(beacon.scenery.SceneryObject) +UTILS.PrintTableToLog(beacon.sceneObjects) +else +env.info(string.format("FF NO scenery object %s %s %s ",beacon.callsign,beacon.beaconId,beacon.typeName)) +end +end +table.insert(self.beacons,beacon) +end +self:I(string.format("Added %d beacons",#self.beacons)) +if self.verbose>0 then +local text="Beacon types:" +for typeName,typeID in pairs(BEACON.Type)do +local n=self:CountBeacons(typeID) +text=text..string.format("\n%s = %d",typeName,n) +end +self:I(text) +end +return self +end +function BEACONS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with beacon info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(beacons) +return self +end +function BEACONS:GetVec3(beacon) +return beacon.vec3 +end +function BEACONS:GetCoordinate(beacon) +local coordinate=COORDINATE:NewFromVec3(beacon.vec3) +return coordinate +end +function BEACONS:GetClosestBeacon(Coordinate,TypeID,DistMax,ExcludeList) +local beacon=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_beacon in pairs(self.beacons)do +local bc=_beacon +if(TypeID==nil or TypeID==bc.type)and(not UTILS.IsInTable(ExcludeList,bc,"beaconId"))then +local dist=Coordinate:Get2DDistance(bc.vec3) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function BEACONS:_GetTypeName(typeID) +if typeID~=nil then +for typeName,_typeID in pairs(BEACON.Type)do +if _typeID==typeID then +return typeName +end +end +end +return"Unknown" +end +RADIOS={ +ClassName="RADIOS", +verbose=0, +radios={}, +} +RADIOS.version="0.1.0" +function RADIOS:NewFromTable(RadioTable) +self=BASE:Inherit(self,BASE:New()) +local airdromes=AIRBASE.GetAllAirbases(nil,Airbase.Category.AIRDROME) +for _,_radio in pairs(RadioTable)do +local radio=_radio +if false then +local cs=radio.callsign[1] +if cs and cs.common then +radio.name=cs.common[1] +elseif cs and cs.nato then +radio.name=cs.nato[1] +else +radio.name="Unknown" +end +radio.name=self:_GetAirbaseName(airbasenames,radio.name) +radio.airbase=AIRBASE:FindByName(radio.name) +end +local aid=tonumber(string.match(radio.radioId,"airfield(%d+)_")) +radio.airbase=self:_GetAirbaseByID(airdromes,aid) +if radio.airbase then +radio.coordinate=radio.airbase:GetCoordinate() +radio.vec3=radio.airbase:GetVec3() +radio.name=radio.airbase:GetName() +end +table.insert(self.radios,radio) +end +self:I(string.format("Added %d radios",#self.radios)) +return self +end +function RADIOS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with radios info does not exist! File=%s",tostring(FileName))) +return nil +end +local radiobak=UTILS.DeepCopy(radio) +dofile(FileName) +self=self:NewFromTable(radio) +radio=UTILS.DeepCopy(radiobak) +return self +end +function RADIOS:GetVec3(radio) +return radio.vec3 +end +function RADIOS:GetCoordinate(radio) +return radio.coordinate +end +function RADIOS:GetClosestRadio(Coordinate,DistMax,ExcludeList) +local radio=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_radio in pairs(self.radios)do +local ra=_radio +if(not UTILS.IsInTable(ExcludeList,ra,"radioId"))then +local dist=Coordinate:Get2DDistance(ra.coordinate) +if dist=1e6 then +freq=freq/1e6 +unit="MHz" +elseif freq>=1e3 then +freq=freq/1e3 +unit="kHz" +end +return freq,unit +end +function RADIOS:_GetBandName(BandNumber) +if BandNumber~=nil then +for bandName,bandNumber in pairs(ENUMS.FrequencyBand)do +if bandNumber==BandNumber then +return bandName +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseName(airbasenames,name) +local airbase=AIRBASE:FindByName(name) +if airbase then +return name +else +for _,airbasename in pairs(airbasenames)do +if string.find(airbasename,name)then +return airbasename +end +end +end +return"Unknown" +end +function RADIOS:_GetAirbaseByID(airbases,aid) +for _,_airbase in pairs(airbases)do +local airbase=_airbase +local id=airbase:GetID(true) +if id==aid then +return airbase +end +end +return nil +end +TOWNS={ +ClassName="TOWNS", +verbose=0, +towns={}, +} +TOWNS.version="0.1.0" +function TOWNS:NewFromTable(TownTable) +self=BASE:Inherit(self,BASE:New()) +for TownName,_town in pairs(TownTable)do +local town=_town +town.name=TownName +town.coordinate=COORDINATE:NewFromLLDD(town.latitude,town.longitude) +town.coordRoad=town.coordinate:GetClosestPointToRoad() +town.coordRail=town.coordinate:GetClosestPointToRoad(true) +table.insert(self.towns,town) +end +self:I(string.format("Added %d towns",#self.towns)) +return self +end +function TOWNS:NewFromFile(FileName) +self=BASE:Inherit(self,BASE:New()) +local exists=UTILS.FileExists(FileName) +if exists==false then +self:E(string.format("ERROR: file with towns info does not exist!")) +return nil +end +dofile(FileName) +self=self:NewFromTable(towns) +return self +end +function TOWNS:GetVec3(town) +return town.vec3 +end +function TOWNS:GetCoordinate(town) +return town.coordinate +end +function TOWNS:GetCoordRoad(town) +return town.coordRoad +end +function TOWNS:GetCoordRail(town) +return town.coordRail +end +function TOWNS:GetConnectionRoad(townA,townB,Railroad) +local path=townA.coordRoad:GetPathlineOnRoad(townB.coordRoad,false,Railroad) +return path +end +function TOWNS:GetClosestTown(Coordinate,DistMax,ExcludeList) +local Town=nil +local distmin=math.huge +ExcludeList=ExcludeList or{} +for _,_town in pairs(self.towns)do +local town=_town +if(not UTILS.IsInTable(ExcludeList,town,"name"))then +local dist=Coordinate:Get2DDistance(town.coordinate) +if dist