env.info('*** MOOSE GITHUB Commit Hash ID: 2025-05-03T17:03:30+02:00-e9d812f3157235f28b713f68ad1051a3fafb85df ***')
if not MOOSE_DEVELOPMENT_FOLDER then
MOOSE_DEVELOPMENT_FOLDER='Scripts'
end
ModuleLoader=MOOSE_DEVELOPMENT_FOLDER..'/Moose/Modules.lua'
if io then
local f=io.open(ModuleLoader,"r")
if f~=nil then
io.close(f)
env.info('*** MOOSE DYNAMIC INCLUDE START *** ')
local base=_G
__Moose={}
__Moose.Include=function(IncludeFile)
if not __Moose.Includes[IncludeFile]then
__Moose.Includes[IncludeFile]=IncludeFile
local f=assert(base.loadfile(IncludeFile))
if f==nil then
error("Moose: Could not load Moose file "..IncludeFile)
else
env.info("Moose: "..IncludeFile.." dynamically loaded.")
return f()
end
end
end
__Moose.Includes={}
__Moose.Include(MOOSE_DEVELOPMENT_FOLDER..'/Moose/Modules.lua')
BASE:TraceOnOff(true)
env.info('*** MOOSE INCLUDE END *** ')
do return end
end
else
env.info('*** MOOSE DYNAMIC INCLUDE NOT POSSIBLE (Desanitize io to use it) *** ')
end
env.info('*** MOOSE STATIC INCLUDE START *** ')
ENUMS={}
env.setErrorMessageBoxEnabled(false)
ENUMS.ROE={
WeaponFree=0,
OpenFireWeaponFree=1,
OpenFire=2,
ReturnFire=3,
WeaponHold=4,
}
ENUMS.ROT={
NoReaction=0,
PassiveDefense=1,
EvadeFire=2,
BypassAndEscape=3,
AllowAbortMission=4,
}
ENUMS.AlarmState={
Auto=0,
Green=1,
Red=2,
}
ENUMS.WeaponFlag={
LGB=2,
TvGB=4,
SNSGB=8,
HEBomb=16,
Penetrator=32,
NapalmBomb=64,
FAEBomb=128,
ClusterBomb=256,
Dispencer=512,
CandleBomb=1024,
ParachuteBomb=2147483648,
LightRocket=2048,
MarkerRocket=4096,
CandleRocket=8192,
HeavyRocket=16384,
AntiRadarMissile=32768,
AntiShipMissile=65536,
AntiTankMissile=131072,
FireAndForgetASM=262144,
LaserASM=524288,
TeleASM=1048576,
CruiseMissile=2097152,
AntiRadarMissile2=1073741824,
SRAM=4194304,
MRAAM=8388608,
LRAAM=16777216,
IR_AAM=33554432,
SAR_AAM=67108864,
AR_AAM=134217728,
GunPod=268435456,
BuiltInCannon=536870912,
GuidedBomb=14,
AnyUnguidedBomb=2147485680,
AnyBomb=2147485694,
AnyRocket=30720,
GuidedASM=1572864,
TacticalASM=1835008,
AnyASM=4161536,
AnyASM2=1077903360,
AnyAAM=264241152,
AnyAutonomousMissile=36012032,
AnyMissile=268402688,
Cannons=805306368,
Torpedo=4294967296,
Auto=3221225470,
AutoDCS=1073741822,
AnyAG=2956984318,
AnyAA=264241152,
AnyUnguided=2952822768,
AnyGuided=268402702,
}
ENUMS.WeaponType={}
ENUMS.WeaponType.Bomb={
LGB=2,
TvGB=4,
SNSGB=8,
HEBomb=16,
Penetrator=32,
NapalmBomb=64,
FAEBomb=128,
ClusterBomb=256,
Dispencer=512,
CandleBomb=1024,
ParachuteBomb=2147483648,
GuidedBomb=14,
AnyUnguidedBomb=2147485680,
AnyBomb=2147485694,
}
ENUMS.WeaponType.Rocket={
LightRocket=2048,
MarkerRocket=4096,
CandleRocket=8192,
HeavyRocket=16384,
AnyRocket=30720,
}
ENUMS.WeaponType.Gun={
GunPod=268435456,
BuiltInCannon=536870912,
Cannons=805306368,
}
ENUMS.WeaponType.Missile={
AntiRadarMissile=32768,
AntiShipMissile=65536,
AntiTankMissile=131072,
FireAndForgetASM=262144,
LaserASM=524288,
TeleASM=1048576,
CruiseMissile=2097152,
AntiRadarMissile2=1073741824,
GuidedASM=1572864,
TacticalASM=1835008,
AnyASM=4161536,
AnyASM2=1077903360,
AnyAutonomousMissile=36012032,
AnyMissile=268402688,
}
ENUMS.WeaponType.AAM={
SRAM=4194304,
MRAAM=8388608,
LRAAM=16777216,
IR_AAM=33554432,
SAR_AAM=67108864,
AR_AAM=134217728,
AnyAAM=264241152,
}
ENUMS.WeaponType.Torpedo={
Torpedo=4294967296,
}
ENUMS.WeaponType.Any={
Weapon=3221225470,
AG=2956984318,
AA=264241152,
Unguided=2952822768,
Guided=268402702,
}
ENUMS.MissionTask={
NOTHING="Nothing",
AFAC="AFAC",
ANTISHIPSTRIKE="Antiship Strike",
AWACS="AWACS",
CAP="CAP",
CAS="CAS",
ESCORT="Escort",
GROUNDESCORT="Ground escort",
FIGHTERSWEEP="Fighter Sweep",
GROUNDATTACK="Ground Attack",
INTERCEPT="Intercept",
PINPOINTSTRIKE="Pinpoint Strike",
RECONNAISSANCE="Reconnaissance",
REFUELING="Refueling",
RUNWAYATTACK="Runway Attack",
SEAD="SEAD",
TRANSPORT="Transport",
}
ENUMS.Formation={}
ENUMS.Formation.FixedWing={}
ENUMS.Formation.FixedWing.LineAbreast={}
ENUMS.Formation.FixedWing.LineAbreast.Close=65537
ENUMS.Formation.FixedWing.LineAbreast.Open=65538
ENUMS.Formation.FixedWing.LineAbreast.Group=65539
ENUMS.Formation.FixedWing.Trail={}
ENUMS.Formation.FixedWing.Trail.Close=131073
ENUMS.Formation.FixedWing.Trail.Open=131074
ENUMS.Formation.FixedWing.Trail.Group=131075
ENUMS.Formation.FixedWing.Wedge={}
ENUMS.Formation.FixedWing.Wedge.Close=196609
ENUMS.Formation.FixedWing.Wedge.Open=196610
ENUMS.Formation.FixedWing.Wedge.Group=196611
ENUMS.Formation.FixedWing.EchelonRight={}
ENUMS.Formation.FixedWing.EchelonRight.Close=262145
ENUMS.Formation.FixedWing.EchelonRight.Open=262146
ENUMS.Formation.FixedWing.EchelonRight.Group=262147
ENUMS.Formation.FixedWing.EchelonLeft={}
ENUMS.Formation.FixedWing.EchelonLeft.Close=327681
ENUMS.Formation.FixedWing.EchelonLeft.Open=327682
ENUMS.Formation.FixedWing.EchelonLeft.Group=327683
ENUMS.Formation.FixedWing.FingerFour={}
ENUMS.Formation.FixedWing.FingerFour.Close=393217
ENUMS.Formation.FixedWing.FingerFour.Open=393218
ENUMS.Formation.FixedWing.FingerFour.Group=393219
ENUMS.Formation.FixedWing.Spread={}
ENUMS.Formation.FixedWing.Spread.Close=458753
ENUMS.Formation.FixedWing.Spread.Open=458754
ENUMS.Formation.FixedWing.Spread.Group=458755
ENUMS.Formation.FixedWing.BomberElement={}
ENUMS.Formation.FixedWing.BomberElement.Close=786433
ENUMS.Formation.FixedWing.BomberElement.Open=786434
ENUMS.Formation.FixedWing.BomberElement.Group=786435
ENUMS.Formation.FixedWing.BomberElementHeight={}
ENUMS.Formation.FixedWing.BomberElementHeight.Close=851968
ENUMS.Formation.FixedWing.FighterVic={}
ENUMS.Formation.FixedWing.FighterVic.Close=917505
ENUMS.Formation.FixedWing.FighterVic.Open=917506
ENUMS.Formation.RotaryWing={}
ENUMS.Formation.RotaryWing.Column={}
ENUMS.Formation.RotaryWing.Column.D70=720896
ENUMS.Formation.RotaryWing.Wedge={}
ENUMS.Formation.RotaryWing.Wedge.D70=8
ENUMS.Formation.RotaryWing.FrontRight={}
ENUMS.Formation.RotaryWing.FrontRight.D300=655361
ENUMS.Formation.RotaryWing.FrontRight.D600=655362
ENUMS.Formation.RotaryWing.FrontLeft={}
ENUMS.Formation.RotaryWing.FrontLeft.D300=655617
ENUMS.Formation.RotaryWing.FrontLeft.D600=655618
ENUMS.Formation.RotaryWing.EchelonRight={}
ENUMS.Formation.RotaryWing.EchelonRight.D70=589825
ENUMS.Formation.RotaryWing.EchelonRight.D300=589826
ENUMS.Formation.RotaryWing.EchelonRight.D600=589827
ENUMS.Formation.RotaryWing.EchelonLeft={}
ENUMS.Formation.RotaryWing.EchelonLeft.D70=590081
ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082
ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083
ENUMS.Formation.Vehicle={}
ENUMS.Formation.Vehicle.Vee="Vee"
ENUMS.Formation.Vehicle.EchelonRight="EchelonR"
ENUMS.Formation.Vehicle.OffRoad="Off Road"
ENUMS.Formation.Vehicle.Rank="Rank"
ENUMS.Formation.Vehicle.EchelonLeft="EchelonL"
ENUMS.Formation.Vehicle.OnRoad="On Road"
ENUMS.Formation.Vehicle.Cone="Cone"
ENUMS.Formation.Vehicle.Diamond="Diamond"
ENUMS.FormationOld={}
ENUMS.FormationOld.FixedWing={}
ENUMS.FormationOld.FixedWing.LineAbreast=1
ENUMS.FormationOld.FixedWing.Trail=2
ENUMS.FormationOld.FixedWing.Wedge=3
ENUMS.FormationOld.FixedWing.EchelonRight=4
ENUMS.FormationOld.FixedWing.EchelonLeft=5
ENUMS.FormationOld.FixedWing.FingerFour=6
ENUMS.FormationOld.FixedWing.SpreadFour=7
ENUMS.FormationOld.FixedWing.BomberElement=12
ENUMS.FormationOld.FixedWing.BomberElementHeight=13
ENUMS.FormationOld.FixedWing.FighterVic=14
ENUMS.FormationOld.RotaryWing={}
ENUMS.FormationOld.RotaryWing.Wedge=8
ENUMS.FormationOld.RotaryWing.Echelon=9
ENUMS.FormationOld.RotaryWing.Front=10
ENUMS.FormationOld.RotaryWing.Column=11
ENUMS.Morse={}
ENUMS.Morse.A="* -"
ENUMS.Morse.B="- * * *"
ENUMS.Morse.C="- * - *"
ENUMS.Morse.D="- * *"
ENUMS.Morse.E="*"
ENUMS.Morse.F="* * - *"
ENUMS.Morse.G="- - *"
ENUMS.Morse.H="* * * *"
ENUMS.Morse.I="* *"
ENUMS.Morse.J="* - - -"
ENUMS.Morse.K="- * -"
ENUMS.Morse.L="* - * *"
ENUMS.Morse.M="- -"
ENUMS.Morse.N="- *"
ENUMS.Morse.O="- - -"
ENUMS.Morse.P="* - - *"
ENUMS.Morse.Q="- - * -"
ENUMS.Morse.R="* - *"
ENUMS.Morse.S="* * *"
ENUMS.Morse.T="-"
ENUMS.Morse.U="* * -"
ENUMS.Morse.V="* * * -"
ENUMS.Morse.W="* - -"
ENUMS.Morse.X="- * * -"
ENUMS.Morse.Y="- * - -"
ENUMS.Morse.Z="- - * *"
ENUMS.Morse.N1="* - - - -"
ENUMS.Morse.N2="* * - - -"
ENUMS.Morse.N3="* * * - -"
ENUMS.Morse.N4="* * * * -"
ENUMS.Morse.N5="* * * * *"
ENUMS.Morse.N6="- * * * *"
ENUMS.Morse.N7="- - * * *"
ENUMS.Morse.N8="- - - * *"
ENUMS.Morse.N9="- - - - *"
ENUMS.Morse.N0="- - - - -"
ENUMS.Morse[" "]=" "
ENUMS.ISOLang=
{
Arabic='AR',
Chinese='ZH',
English='EN',
French='FR',
German='DE',
Russian='RU',
Spanish='ES',
Japanese='JA',
Italian='IT',
}
ENUMS.Phonetic=
{
A='Alpha',
B='Bravo',
C='Charlie',
D='Delta',
E='Echo',
F='Foxtrot',
G='Golf',
H='Hotel',
I='India',
J='Juliett',
K='Kilo',
L='Lima',
M='Mike',
N='November',
O='Oscar',
P='Papa',
Q='Quebec',
R='Romeo',
S='Sierra',
T='Tango',
U='Uniform',
V='Victor',
W='Whiskey',
X='Xray',
Y='Yankee',
Z='Zulu',
}
ENUMS.ReportingName=
{
NATO={
Dragon="JF-17",
Fagot="MiG-15",
Farmer="MiG-19",
Felon="Su-57",
Fencer="Su-24",
Fishbed="MiG-21",
Fitter="Su-17",
Flogger="MiG-23",
Flogger_D="MiG-27",
Flagon="Su-15",
Foxbat="MiG-25",
Fulcrum="MiG-29",
Foxhound="MiG-31",
Flanker="Su-27",
Flanker_C="Su-30",
Flanker_E="Su-35",
Flanker_F="Su-37",
Flanker_L="J-11A",
Firebird="J-10",
Sea_Flanker="Su-33",
Fullback="Su-34",
Frogfoot="Su-25",
Tomcat="F-14",
Mirage="Mirage",
Codling="Yak-40",
Maya="L-39",
Warthog="A-10",
Skyhawk="A-4E",
Viggen="AJS37",
Harrier_B="AV8BNA",
Harrier="AV-8B",
Spirit="B-2",
Aviojet="C-101",
Nighthawk="F-117A",
Eagle="F-15C",
Mudhen="F-15E",
Viper="F-16",
Phantom="F-4E",
Tiger="F-5",
Sabre="F-86",
Hornet="A-18",
Hawk="Hawk",
Albatros="L-39",
Goshawk="T-45",
Starfighter="F-104",
Tornado="Tornado",
Atlas="A400",
Lancer="B1-B",
Stratofortress="B-52H",
Hercules="C-130",
Super_Hercules="Hercules",
Globemaster="C-17",
Greyhound="C-2A",
Galaxy="C-5",
Hawkeye="E-2D",
Sentry="E-3A",
Stratotanker="KC-135",
Gasstation="KC-135MPRS",
Extender="KC-10",
Orion="P-3C",
Viking="S-3B",
Osprey="V-22",
Badger="H6-J",
Bear_J="Tu-142",
Bear="Tu-95",
Blinder="Tu-22",
Blackjack="Tu-160",
Clank="An-30",
Curl="An-26",
Candid="IL-76",
Midas="IL-78",
Mainstay="A-50",
Mainring="KJ-2000",
Yak="Yak-52",
Helix="Ka-27",
Shark="Ka-50",
Hind="Mi-24",
Halo="Mi-26",
Hip="Mi-8",
Havoc="Mi-28",
Gazelle="SA342",
Huey="UH-1H",
Cobra="AH-1",
Apache="AH-64",
Chinook="CH-47",
Sea_Stallion="CH-53",
Kiowa="OH-58",
Seahawk="SH-60",
Blackhawk="UH-60",
Sea_King="S-61",
UCAV="WingLoong",
Reaper="MQ-9",
Predator="MQ-1A",
}
}
ENUMS.Link16Power={
none=0,
low=1,
medium=2,
high=3,
}
ENUMS.Storage={
weapons={
missiles={},
bombs={},
nurs={},
containers={},
droptanks={},
adapters={},
torpedoes={},
Gazelle={},
CH47={},
OH58={},
UH1H={},
AH64D={},
}
}
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.FARPType={
FARP="FARP",
INVISIBLE="INVISIBLE",
HELIPADSINGLE="HELIPADSINGLE",
PADSINGLE="PADSINGLE",
}
ENUMS.FARPObjectTypeNamesAndShape={
[ENUMS.FARPType.FARP]={TypeName="FARP",ShapeName="FARPS"},
[ENUMS.FARPType.INVISIBLE]={TypeName="Invisible FARP",ShapeName="invisiblefarp"},
[ENUMS.FARPType.HELIPADSINGLE]={TypeName="SINGLE_HELIPAD",ShapeName="FARP"},
[ENUMS.FARPType.PADSINGLE]={TypeName="FARP_SINGLE_01",ShapeName="FARP_SINGLE_01"},
}
SMOKECOLOR=trigger.smokeColor
FLARECOLOR=trigger.flareColor
BIGSMOKEPRESET={
SmallSmokeAndFire=1,
MediumSmokeAndFire=2,
LargeSmokeAndFire=3,
HugeSmokeAndFire=4,
SmallSmoke=5,
MediumSmoke=6,
LargeSmoke=7,
HugeSmoke=8,
}
DCSMAP={
Caucasus="Caucasus",
NTTR="Nevada",
Normandy="Normandy",
PersianGulf="PersianGulf",
TheChannel="TheChannel",
Syria="Syria",
MarianaIslands="MarianaIslands",
Falklands="Falklands",
Sinai="SinaiMap",
Kola="Kola",
Afghanistan="Afghanistan",
Iraq="Iraq",
GermanyCW="GermanyCW",
}
CALLSIGN={
Aircraft={
Enfield=1,
Springfield=2,
Uzi=3,
Colt=4,
Dodge=5,
Ford=6,
Chevy=7,
Pontiac=8,
Hawg=9,
Boar=10,
Pig=11,
Tusk=12,
},
AWACS={
Overlord=1,
Magic=2,
Wizard=3,
Focus=4,
Darkstar=5,
},
Tanker={
Texaco=1,
Arco=2,
Shell=3,
Navy_One=4,
Mauler=5,
Bloodhound=6,
},
JTAC={
Axeman=1,
Darknight=2,
Warrior=3,
Pointer=4,
Eyeball=5,
Moonbeam=6,
Whiplash=7,
Finger=8,
Pinpoint=9,
Ferret=10,
Shaba=11,
Playboy=12,
Hammer=13,
Jaguar=14,
Deathstar=15,
Anvil=16,
Firefly=17,
Mantis=18,
Badger=19,
},
FARP={
London=1,
Dallas=2,
Paris=3,
Moscow=4,
Berlin=5,
Rome=6,
Madrid=7,
Warsaw=8,
Dublin=9,
Perth=10,
},
F16={
Viper=9,
Venom=10,
Lobo=11,
Cowboy=12,
Python=13,
Rattler=14,
Panther=15,
Wolf=16,
Weasel=17,
Wild=18,
Ninja=19,
Jedi=20,
},
F18={
Hornet=9,
Squid=10,
Ragin=11,
Roman=12,
Sting=13,
Jury=14,
Jokey=15,
Ram=16,
Hawk=17,
Devil=18,
Check=19,
Snake=20,
},
F15E={
Dude=9,
Thud=10,
Gunny=11,
Trek=12,
Sniper=13,
Sled=14,
Best=15,
Jazz=16,
Rage=17,
Tahoe=18,
},
B1B={
Bone=9,
Dark=10,
Vader=11
},
B52={
Buff=9,
Dump=10,
Kenworth=11,
},
TransportAircraft={
Heavy=9,
Trash=10,
Cargo=11,
Ascot=12,
},
AH64={
Army_Air=9,
Apache=10,
Crow=11,
Sioux=12,
Gatling=13,
Gunslinger=14,
Hammerhead=15,
Bootleg=16,
Palehorse=17,
Carnivor=18,
Saber=19,
},
Kiowa={
Anvil=1,
Azrael=2,
BamBam=3,
Blackjack=4,
Bootleg=5,
BurninStogie=6,
Chaos=7,
CrazyHorse=8,
Crusader=9,
Darkhorse=10,
Eagle=11,
Lighthorse=12,
Mustang=13,
Outcast=14,
Palehorse=15,
Pegasus=16,
Pistol=17,
Roughneck=18,
Saber=19,
Shamus=20,
Spur=21,
Stetson=22,
Wrath=23,
},
}
UTILS={
_MarkID=1
}
UTILS.IsInstanceOf=function(object,className)
if type(className)~='string'then
if type(className)=='table'and className.IsInstanceOf~=nil then
className=className.ClassName
else
local err_str='className parameter should be a string; parameter received: '..type(className)
return false
end
end
if type(object)=='table'and object.IsInstanceOf~=nil then
return object:IsInstanceOf(className)
else
local basicDataTypes={'string','number','function','boolean','nil','table'}
for _,basicDataType in ipairs(basicDataTypes)do
if className==basicDataType then
return type(object)==basicDataType
end
end
end
return false
end
UTILS.DeepCopy=function(object)
local lookup_table={}
local function _copy(object)
if type(object)~="table"then
return object
elseif lookup_table[object]then
return lookup_table[object]
end
local new_table={}
lookup_table[object]=new_table
for index,value in pairs(object)do
new_table[_copy(index)]=_copy(value)
end
return setmetatable(new_table,getmetatable(object))
end
local objectreturn=_copy(object)
return objectreturn
end
UTILS.OneLineSerialize=function(tbl)
local lookup_table={}
local function _Serialize(tbl)
if type(tbl)=='table'then
if lookup_table[tbl]then
return lookup_table[object]
end
local tbl_str={}
lookup_table[tbl]=tbl_str
tbl_str[#tbl_str+1]='{'
for ind,val in pairs(tbl)do
local ind_str={}
if type(ind)=="number"then
ind_str[#ind_str+1]='['
ind_str[#ind_str+1]=tostring(ind)
ind_str[#ind_str+1]=']='
else
ind_str[#ind_str+1]='['
ind_str[#ind_str+1]=UTILS.BasicSerialize(ind)
ind_str[#ind_str+1]=']='
end
local val_str={}
if((type(val)=='number')or(type(val)=='boolean'))then
val_str[#val_str+1]=tostring(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='string'then
val_str[#val_str+1]=UTILS.BasicSerialize(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='nil'then
val_str[#val_str+1]='nil,'
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
elseif type(val)=='table'then
if ind=="__index"then
else
val_str[#val_str+1]=_Serialize(val)
val_str[#val_str+1]=','
tbl_str[#tbl_str+1]=table.concat(ind_str)
tbl_str[#tbl_str+1]=table.concat(val_str)
end
elseif type(val)=='function'then
tbl_str[#tbl_str+1]="f() "..tostring(ind)
tbl_str[#tbl_str+1]=','
else
env.info('unable to serialize value type '..UTILS.BasicSerialize(type(val))..' at index '..tostring(ind))
env.info(debug.traceback())
end
end
tbl_str[#tbl_str+1]='}'
return table.concat(tbl_str)
else
return tostring(tbl)
end
end
local objectreturn=_Serialize(tbl)
return objectreturn
end
function UTILS._OneLineSerialize(tbl)
if type(tbl)=='table'then
local tbl_str={}
tbl_str[#tbl_str+1]='{ '
for ind,val in pairs(tbl)do
if type(ind)=="number"then
tbl_str[#tbl_str+1]='['
tbl_str[#tbl_str+1]=tostring(ind)
tbl_str[#tbl_str+1]='] = '
else
tbl_str[#tbl_str+1]='['
tbl_str[#tbl_str+1]=UTILS.BasicSerialize(ind)
tbl_str[#tbl_str+1]='] = '
end
if((type(val)=='number')or(type(val)=='boolean'))then
tbl_str[#tbl_str+1]=tostring(val)
tbl_str[#tbl_str+1]=', '
elseif type(val)=='string'then
tbl_str[#tbl_str+1]=UTILS.BasicSerialize(val)
tbl_str[#tbl_str+1]=', '
elseif type(val)=='nil'then
tbl_str[#tbl_str+1]='nil, '
elseif type(val)=='table'then
else
end
end
tbl_str[#tbl_str+1]='}'
return table.concat(tbl_str)
else
return UTILS.BasicSerialize(tbl)
end
end
UTILS.BasicSerialize=function(s)
if s==nil then
return"\"\""
else
if((type(s)=='number')or(type(s)=='boolean')or(type(s)=='function')or(type(s)=='userdata'))then
return tostring(s)
elseif type(s)=="table"then
return UTILS._OneLineSerialize(s)
elseif type(s)=='string'then
s=string.format('(%s)',s)
return s
end
end
end
function UTILS.TableLength(T)
local count=0
for _ in pairs(T or{})do count=count+1 end
return count
end
function UTILS.PrintTableToLog(table,indent,noprint)
local text="\n"
if not table or type(table)~="table"then
env.warning("No table passed!")
return nil
end
if not indent then indent=0 end
for k,v in pairs(table)do
if string.find(k," ")then k='"'..k..'"'end
if type(v)=="table"and UTILS.TableLength(v)>0 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),noprint).."\n"
if not noprint then
env.info(string.rep("  ",indent).."},")
end
text=text..string.rep("  ",indent).."},\n"
elseif type(v)=="function"then
else
local value
if tostring(v)=="true"or tostring(v)=="false"or tonumber(v)~=nil then
value=v
else
value='"'..tostring(v)..'"'
end
if not noprint then
env.info(string.rep("  ",indent)..tostring(k).." = "..tostring(value)..",\n")
end
text=text..string.rep("  ",indent)..tostring(k).." = "..tostring(value)..",\n"
end
end
return text
end
function UTILS.TableShow(tbl,loc,indent,tableshow_tbls)
tableshow_tbls=tableshow_tbls or{}
loc=loc or""
indent=indent or""
if type(tbl)=='table'then
tableshow_tbls[tbl]=loc
local tbl_str={}
tbl_str[#tbl_str+1]=indent..'{\n'
for ind,val in pairs(tbl)do
if type(ind)=="number"then
tbl_str[#tbl_str+1]=indent
tbl_str[#tbl_str+1]=loc..'['
tbl_str[#tbl_str+1]=tostring(ind)
tbl_str[#tbl_str+1]='] = '
else
tbl_str[#tbl_str+1]=indent
tbl_str[#tbl_str+1]=loc..'['
tbl_str[#tbl_str+1]=UTILS.BasicSerialize(ind)
tbl_str[#tbl_str+1]='] = '
end
if((type(val)=='number')or(type(val)=='boolean'))then
tbl_str[#tbl_str+1]=tostring(val)
tbl_str[#tbl_str+1]=',\n'
elseif type(val)=='string'then
tbl_str[#tbl_str+1]=UTILS.BasicSerialize(val)
tbl_str[#tbl_str+1]=',\n'
elseif type(val)=='nil'then
tbl_str[#tbl_str+1]='nil,\n'
elseif type(val)=='table'then
if tableshow_tbls[val]then
tbl_str[#tbl_str+1]=tostring(val)..' already defined: '..tableshow_tbls[val]..',\n'
else
tableshow_tbls[val]=loc..'['..UTILS.BasicSerialize(ind)..']'
tbl_str[#tbl_str+1]=tostring(val)..' '
tbl_str[#tbl_str+1]=UTILS.TableShow(val,loc..'['..UTILS.BasicSerialize(ind)..']',indent..'    ',tableshow_tbls)
tbl_str[#tbl_str+1]=',\n'
end
elseif type(val)=='function'then
if debug and debug.getinfo then
local fcnname=tostring(val)
local info=debug.getinfo(val,"S")
if info.what=="C"then
tbl_str[#tbl_str+1]=string.format('%q',fcnname..', C function')..',\n'
else
if(string.sub(info.source,1,2)==[[./]])then
tbl_str[#tbl_str+1]=string.format('%q',fcnname..', defined in ('..info.linedefined..'-'..info.lastlinedefined..')'..info.source)..',\n'
else
tbl_str[#tbl_str+1]=string.format('%q',fcnname..', defined in ('..info.linedefined..'-'..info.lastlinedefined..')')..',\n'
end
end
else
tbl_str[#tbl_str+1]='a function,\n'
end
else
tbl_str[#tbl_str+1]='unable to serialize value type '..UTILS.BasicSerialize(type(val))..' at index '..tostring(ind)
end
end
tbl_str[#tbl_str+1]=indent..'}'
return table.concat(tbl_str)
end
end
function UTILS.Gdump(fname)
if lfs and io then
local fdir=lfs.writedir()..[[Logs\]]..fname
local f=io.open(fdir,'w')
f:write(UTILS.TableShow(_G))
f:close()
env.info(string.format('Wrote debug data to $1',fdir))
else
env.error("WARNING: lfs and/or io not de-sanitized - cannot dump _G!")
end
end
function UTILS.DoString(s)
local f,err=loadstring(s)
if f then
return true,f()
else
return false,err
end
end
UTILS.ToDegree=function(angle)
return angle*180/math.pi
end
UTILS.ToRadian=function(angle)
return angle*math.pi/180
end
UTILS.MetersToNM=function(meters)
return meters/1852
end
UTILS.KiloMetersToNM=function(kilometers)
return kilometers/1852*1000
end
UTILS.MetersToSM=function(meters)
return meters/1609.34
end
UTILS.KiloMetersToSM=function(kilometers)
return kilometers/1609.34*1000
end
UTILS.MetersToFeet=function(meters)
return meters/0.3048
end
UTILS.KiloMetersToFeet=function(kilometers)
return kilometers/0.3048*1000
end
UTILS.NMToMeters=function(NM)
return NM*1852
end
UTILS.NMToKiloMeters=function(NM)
return NM*1852/1000
end
UTILS.FeetToMeters=function(feet)
return feet*0.3048
end
UTILS.KnotsToKmph=function(knots)
return knots*1.852
end
UTILS.KmphToKnots=function(knots)
return knots/1.852
end
UTILS.KmphToMps=function(kmph)
return kmph/3.6
end
UTILS.MpsToKmph=function(mps)
return mps*3.6
end
UTILS.MiphToMps=function(miph)
return miph*0.44704
end
UTILS.MpsToMiph=function(mps)
return mps/0.44704
end
UTILS.MpsToKnots=function(mps)
return mps*1.94384
end
UTILS.KnotsToMps=function(knots)
if type(knots)=="number"then
return knots/1.94384
else
return 0
end
end
UTILS.CelsiusToFahrenheit=function(Celcius)
return Celcius*9/5+32
end
UTILS.hPa2inHg=function(hPa)
return hPa*0.0295299830714
end
UTILS.IasToTas=function(ias,altitude,oatcorr)
oatcorr=oatcorr or 0.017
local tas=ias+(ias*oatcorr*UTILS.MetersToFeet(altitude)/1000)
return tas
end
UTILS.TasToIas=function(tas,altitude,oatcorr)
oatcorr=oatcorr or 0.017
local ias=tas/(1+oatcorr*UTILS.MetersToFeet(altitude)/1000)
return ias
end
UTILS.KnotsToAltKIAS=function(knots,altitude)
return(knots*0.018*(altitude/1000))+knots
end
UTILS.hPa2mmHg=function(hPa)
return hPa*0.7500615613030
end
UTILS.kg2lbs=function(kg)
return kg*2.20462
end
UTILS.tostringLL=function(lat,lon,acc,DMS)
local latHemi,lonHemi
if lat>0 then
latHemi='N'
else
latHemi='S'
end
if lon>0 then
lonHemi='E'
else
lonHemi='W'
end
lat=math.abs(lat)
lon=math.abs(lon)
local latDeg=math.floor(lat)
local latMin=(lat-latDeg)*60
local lonDeg=math.floor(lon)
local lonMin=(lon-lonDeg)*60
if DMS then
local oldLatMin=latMin
latMin=math.floor(latMin)
local latSec=UTILS.Round((oldLatMin-latMin)*60,acc)
local oldLonMin=lonMin
lonMin=math.floor(lonMin)
local lonSec=UTILS.Round((oldLonMin-lonMin)*60,acc)
if latSec==60 then
latSec=0
latMin=latMin+1
end
if lonSec==60 then
lonSec=0
lonMin=lonMin+1
end
local secFrmtStr
secFrmtStr='%02d'
if acc<=0 then
secFrmtStr='%02d'
else
local width=3+acc
secFrmtStr='%0'..width..'.'..acc..'f'
end
return string.format('%03d°',latDeg)..string.format('%02d',latMin)..'\''..string.format(secFrmtStr,latSec)..'"'..latHemi..' '
..string.format('%03d°',lonDeg)..string.format('%02d',lonMin)..'\''..string.format(secFrmtStr,lonSec)..'"'..lonHemi
else
latMin=UTILS.Round(latMin,acc)
lonMin=UTILS.Round(lonMin,acc)
if latMin==60 then
latMin=0
latDeg=latDeg+1
end
if lonMin==60 then
lonMin=0
lonDeg=lonDeg+1
end
local minFrmtStr
if acc<=0 then
minFrmtStr='%02d'
else
local width=3+acc
minFrmtStr='%0'..width..'.'..acc..'f'
end
return string.format('%03d°',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..'   '
..string.format('%03d°',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi
end
end
UTILS.tostringLLM2KData=function(lat,lon,acc)
local latHemi,lonHemi
if lat>0 then
latHemi='N'
else
latHemi='S'
end
if lon>0 then
lonHemi='E'
else
lonHemi='W'
end
lat=math.abs(lat)
lon=math.abs(lon)
local latDeg=math.floor(lat)
local latMin=(lat-latDeg)*60
local lonDeg=math.floor(lon)
local lonMin=(lon-lonDeg)*60
latMin=UTILS.Round(latMin,acc)
lonMin=UTILS.Round(lonMin,acc)
if latMin==60 then
latMin=0
latDeg=latDeg+1
end
if lonMin==60 then
lonMin=0
lonDeg=lonDeg+1
end
local minFrmtStr
if acc<=0 then
minFrmtStr='%02d'
else
local width=3+acc
minFrmtStr='%0'..width..'.'..acc..'f'
end
return latHemi..string.format('%02d:',latDeg)..string.format(minFrmtStr,latMin),lonHemi..string.format('%02d:',lonDeg)..string.format(minFrmtStr,lonMin)
end
UTILS.tostringMGRS=function(MGRS,acc)
if acc<=0 then
return MGRS.UTMZone..' '..MGRS.MGRSDigraph
else
if acc>5 then acc=5 end
local Easting=tostring(MGRS.Easting)
local Northing=tostring(MGRS.Northing)
local nE=5-string.len(Easting)
local nN=5-string.len(Northing)
for i=1,nE do Easting="0"..Easting end
for i=1,nN do Northing="0"..Northing end
return string.format("%s %s %s %s",MGRS.UTMZone,MGRS.MGRSDigraph,string.sub(Easting,1,acc),string.sub(Northing,1,acc))
end
end
function UTILS.Round(num,idp)
local mult=10^(idp or 0)
return math.floor(num*mult+0.5)/mult
end
function UTILS.DoString(s)
local f,err=loadstring(s)
if f then
return true,f()
else
return false,err
end
end
function UTILS.spairs(t,order)
local keys={}
for k in pairs(t)do keys[#keys+1]=k end
if order then
table.sort(keys,function(a,b)return order(t,a,b)end)
else
table.sort(keys)
end
local i=0
return function()
i=i+1
if keys[i]then
return keys[i],t[keys[i]]
end
end
end
function UTILS.kpairs(t,getkey,order)
local keys={}
local keyso={}
for k,o in pairs(t)do keys[#keys+1]=k keyso[#keyso+1]=getkey(o)end
if order then
table.sort(keys,function(a,b)return order(t,a,b)end)
else
table.sort(keys)
end
local i=0
return function()
i=i+1
if keys[i]then
return keyso[i],t[keys[i]]
end
end
end
function UTILS.rpairs(t)
local keys={}
for k in pairs(t)do keys[#keys+1]=k end
local random={}
local j=#keys
for i=1,j do
local k=math.random(1,#keys)
random[i]=keys[k]
table.remove(keys,k)
end
local i=0
return function()
i=i+1
if random[i]then
return random[i],t[random[i]]
end
end
end
function UTILS.GetMarkID()
UTILS._MarkID=UTILS._MarkID+1
return UTILS._MarkID
end
function UTILS.RemoveMark(MarkID,Delay)
if Delay and Delay>0 then
TIMER:New(UTILS.RemoveMark,MarkID):Start(Delay)
else
if MarkID then
trigger.action.removeMark(MarkID)
end
end
end
function UTILS.IsInRadius(InVec2,Vec2,Radius)
local InRadius=((InVec2.x-Vec2.x)^2+(InVec2.y-Vec2.y)^2)^0.5<=Radius
return InRadius
end
function UTILS.IsInSphere(InVec3,Vec3,Radius)
local InSphere=((InVec3.x-Vec3.x)^2+(InVec3.y-Vec3.y)^2+(InVec3.z-Vec3.z)^2)^0.5<=Radius
return InSphere
end
function UTILS.BeaufortScale(speed)
local bn=nil
local bd=nil
if speed<0.51 then
bn=0
bd="Calm"
elseif speed<2.06 then
bn=1
bd="Light Air"
elseif speed<3.60 then
bn=2
bd="Light Breeze"
elseif speed<5.66 then
bn=3
bd="Gentle Breeze"
elseif speed<8.23 then
bn=4
bd="Moderate Breeze"
elseif speed<11.32 then
bn=5
bd="Fresh Breeze"
elseif speed<14.40 then
bn=6
bd="Strong Breeze"
elseif speed<17.49 then
bn=7
bd="Moderate Gale"
elseif speed<21.09 then
bn=8
bd="Fresh Gale"
elseif speed<24.69 then
bn=9
bd="Strong Gale"
elseif speed<28.81 then
bn=10
bd="Storm"
elseif speed<32.92 then
bn=11
bd="Violent Storm"
else
bn=12
bd="Hurricane"
end
return bn,bd
end
function UTILS.Split(str,sep)
local result={}
local regex=("([^%s]+)"):format(sep)
for each in str:gmatch(regex)do
table.insert(result,each)
end
return result
end
function UTILS.GetCharacters(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars,c)
end
return chars
end
function UTILS.SecondsToClock(seconds,short)
if seconds==nil then
return nil
end
local seconds=tonumber(seconds)or 0
local _seconds=seconds%(60*60*24)
if seconds<0 then
return nil
else
local hours=string.format("%02.f",math.floor(_seconds/3600))
local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60)))
local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60))
local days=string.format("%d",seconds/(60*60*24))
local clock=hours..":"..mins..":"..secs.."+"..days
if short then
if hours=="00"then
clock=hours..":"..mins..":"..secs
else
clock=hours..":"..mins..":"..secs
end
end
return clock
end
end
function UTILS.SecondsOfToday()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time,true)
return UTILS.ClockToSeconds(clock)
end
function UTILS.SecondsToMidnight()
return 24*60*60-UTILS.SecondsOfToday()
end
function UTILS.ClockToSeconds(clock)
if clock==nil then
return nil
end
local seconds=0
local dsplit=UTILS.Split(clock,"+")
if#dsplit>1 then
seconds=seconds+tonumber(dsplit[2])*60*60*24
end
local tsplit=UTILS.Split(dsplit[1],":")
local i=1
for _,time in ipairs(tsplit)do
if i==1 then
seconds=seconds+tonumber(time)*60*60
elseif i==2 then
seconds=seconds+tonumber(time)*60
elseif i==3 then
seconds=seconds+tonumber(time)
end
i=i+1
end
return seconds
end
function UTILS.DisplayMissionTime(duration)
duration=duration or 5
local Tnow=timer.getAbsTime()
local mission_time=Tnow-timer.getTime0()
local mission_time_minutes=mission_time/60
local mission_time_seconds=mission_time%60
local local_time=UTILS.SecondsToClock(Tnow)
local text=string.format("Time: %s - %02d:%02d",local_time,mission_time_minutes,mission_time_seconds)
MESSAGE:New(text,duration):ToAll()
end
function UTILS.ReplaceIllegalCharacters(Text,ReplaceBy)
ReplaceBy=ReplaceBy or"_"
local text=Text:gsub("[<>|/?*:\\]",ReplaceBy)
return text
end
function UTILS.RandomGaussian(x0,sigma,xmin,xmax,imax)
sigma=sigma or 10
imax=imax or 100
local r
local gotit=false
local i=0
while not gotit do
local x1=math.random()
local x2=math.random()
r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0
i=i+1
if(r>=xmin and r<=xmax)or i>imax then
gotit=true
end
end
return r
end
function UTILS.Randomize(value,fac,lower,upper)
local min
if lower then
min=math.max(value-value*fac,lower)
else
min=value-value*fac
end
local max
if upper then
max=math.min(value+value*fac,upper)
else
max=value+value*fac
end
local r=math.random(min,max)
return r
end
function UTILS.VecDot(a,b)
return a.x*b.x+a.y*b.y+a.z*b.z
end
function UTILS.Vec2Dot(a,b)
return a.x*b.x+a.y*b.y
end
function UTILS.VecNorm(a)
return math.sqrt(UTILS.VecDot(a,a))
end
function UTILS.Vec2Norm(a)
return math.sqrt(UTILS.Vec2Dot(a,a))
end
function UTILS.VecDist2D(a,b)
local d=math.huge
if(not a)or(not b)then return d end
local c={x=b.x-a.x,y=b.y-a.y}
d=math.sqrt(c.x*c.x+c.y*c.y)
return d
end
function UTILS.VecDist3D(a,b)
local d=math.huge
if(not a)or(not b)then return d end
local c={x=b.x-a.x,y=b.y-a.y,z=b.z-a.z}
d=math.sqrt(UTILS.VecDot(c,c))
return d
end
function UTILS.VecCross(a,b)
return{x=a.y*b.z-a.z*b.y,y=a.z*b.x-a.x*b.z,z=a.x*b.y-a.y*b.x}
end
function UTILS.VecSubstract(a,b)
return{x=a.x-b.x,y=a.y-b.y,z=a.z-b.z}
end
function UTILS.VecSubtract(a,b)
return UTILS.VecSubstract(a,b)
end
function UTILS.Vec2Substract(a,b)
return{x=a.x-b.x,y=a.y-b.y}
end
function UTILS.Vec2Subtract(a,b)
return UTILS.Vec2Substract(a,b)
end
function UTILS.VecAdd(a,b)
return{x=a.x+b.x,y=a.y+b.y,z=a.z+b.z}
end
function UTILS.Vec2Add(a,b)
return{x=a.x+b.x,y=a.y+b.y}
end
function UTILS.VecAngle(a,b)
local cosalpha=UTILS.VecDot(a,b)/(UTILS.VecNorm(a)*UTILS.VecNorm(b))
local alpha=0
if cosalpha>=0.9999999999 then
alpha=0
elseif cosalpha<=-0.999999999 then
alpha=math.pi
else
alpha=math.acos(cosalpha)
end
return math.deg(alpha)
end
function UTILS.VecHdg(a)
local h=math.deg(math.atan2(a.z,a.x))
if h<0 then
h=h+360
end
return h
end
function UTILS.Vec2Hdg(a)
local h=math.deg(math.atan2(a.y,a.x))
if h<0 then
h=h+360
end
return h
end
function UTILS.HdgDiff(h1,h2)
local alpha=math.rad(tonumber(h1))
local beta=math.rad(tonumber(h2))
local v1={x=math.cos(alpha),y=0,z=math.sin(alpha)}
local v2={x=math.cos(beta),y=0,z=math.sin(beta)}
local delta=UTILS.VecAngle(v1,v2)
return math.abs(delta)
end
function UTILS.HdgTo(a,b)
local dz=b.z-a.z
local dx=b.x-a.x
local heading=math.deg(math.atan2(dz,dx))
if heading<0 then
heading=360+heading
end
return heading
end
function UTILS.VecTranslate(a,distance,angle)
local SX=a.x
local SY=a.z
local Radians=math.rad(angle or 0)
local TX=distance*math.cos(Radians)+SX
local TY=distance*math.sin(Radians)+SY
return{x=TX,y=a.y,z=TY}
end
function UTILS.Vec2Translate(a,distance,angle)
local SX=a.x
local SY=a.y
local Radians=math.rad(angle or 0)
local TX=distance*math.cos(Radians)+SX
local TY=distance*math.sin(Radians)+SY
return{x=TX,y=TY}
end
function UTILS.Rotate2D(a,angle)
local phi=math.rad(angle)
local x=a.z
local y=a.x
local Z=x*math.cos(phi)-y*math.sin(phi)
local X=x*math.sin(phi)+y*math.cos(phi)
local Y=a.y
local A={x=X,y=Y,z=Z}
return A
end
function UTILS.Vec2Rotate2D(a,angle)
local phi=math.rad(angle)
local x=a.x
local y=a.y
local X=x*math.cos(phi)-y*math.sin(phi)
local Y=x*math.sin(phi)+y*math.cos(phi)
local A={x=X,y=Y}
return A
end
function UTILS.TACANToFrequency(TACANChannel,TACANMode)
if type(TACANChannel)~="number"then
return nil
end
if TACANMode~="X"and TACANMode~="Y"then
return nil
end
local A=1151
local B=64
if TACANChannel<64 then
B=1
end
if TACANMode=='Y'then
A=1025
if TACANChannel<64 then
A=1088
end
else
if TACANChannel<64 then
A=962
end
end
return(A+TACANChannel-B)*1000000
end
function UTILS.GetDCSMap()
return env.mission.theatre
end
function UTILS.GetDCSMissionDate()
local year=tostring(env.mission.date.Year)
local month=tostring(env.mission.date.Month)
local day=tostring(env.mission.date.Day)
return string.format("%s/%s/%s",year,month,day),tonumber(year),tonumber(month),tonumber(day)
end
function UTILS.GetMissionDay(Time)
Time=Time or timer.getAbsTime()
local clock=UTILS.SecondsToClock(Time,false)
local x=tonumber(UTILS.Split(clock,"+")[2])
return x
end
function UTILS.GetMissionDayOfYear(Time)
local Date,Year,Month,Day=UTILS.GetDCSMissionDate()
local d=UTILS.GetMissionDay(Time)
return UTILS.GetDayOfYear(Year,Month,Day)+d
end
function UTILS.GetMagneticDeclination(map)
map=map or UTILS.GetDCSMap()
local declination=0
if map==DCSMAP.Caucasus then
declination=6
elseif map==DCSMAP.NTTR then
declination=12
elseif map==DCSMAP.Normandy then
declination=-10
elseif map==DCSMAP.PersianGulf then
declination=2
elseif map==DCSMAP.TheChannel then
declination=-10
elseif map==DCSMAP.Syria then
declination=5
elseif map==DCSMAP.MarianaIslands then
declination=2
elseif map==DCSMAP.Falklands then
declination=12
elseif map==DCSMAP.Sinai then
declination=4.8
elseif map==DCSMAP.Kola then
declination=15
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
return declination
end
function UTILS.FileExists(file)
if io then
local f=io.open(file,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
else
return nil
end
end
function UTILS.CheckMemory(output)
local time=timer.getTime()
local clock=UTILS.SecondsToClock(time)
local mem=collectgarbage("count")
if output then
env.info(string.format("T=%s  Memory usage %d kByte = %.2f MByte",clock,mem,mem/1024))
end
return mem
end
function UTILS.GetCoalitionName(Coalition)
if Coalition then
if Coalition==coalition.side.BLUE then
return"Blue"
elseif Coalition==coalition.side.RED then
return"Red"
elseif Coalition==coalition.side.NEUTRAL then
return"Neutral"
else
return"Unknown"
end
else
return"Unknown"
end
end
function UTILS.GetCoalitionEnemy(Coalition,Neutral)
local Coalitions={}
if Coalition then
if Coalition==coalition.side.RED then
Coalitions={coalition.side.BLUE}
elseif Coalition==coalition.side.BLUE then
Coalitions={coalition.side.RED}
elseif Coalition==coalition.side.NEUTRAL then
Coalitions={coalition.side.RED,coalition.side.BLUE}
end
end
if Neutral then
table.insert(Coalitions,coalition.side.NEUTRAL)
end
return Coalitions
end
function UTILS.GetModulationName(Modulation)
if Modulation then
if Modulation==0 then
return"AM"
elseif Modulation==1 then
return"FM"
else
return"Unknown"
end
else
return"Unknown"
end
end
function UTILS.GetReportingName(Typename)
local typename=string.lower(Typename)
for name,value in pairs(ENUMS.ReportingName.NATO)do
local svalue=string.lower(value)
if string.find(typename,svalue,1,true)then
return name
end
end
return"Bogey"
end
function UTILS.GetCallsignName(Callsign)
for name,value in pairs(CALLSIGN.Aircraft)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.AWACS)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.JTAC)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.Tanker)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.B1B)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.B52)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.F15E)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.F16)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.F18)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.FARP)do
if value==Callsign then
return name
end
end
for name,value in pairs(CALLSIGN.TransportAircraft)do
if value==Callsign then
return name
end
end
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
return"Ghostrider"
end
function UTILS.GMTToLocalTimeDifference()
local theatre=UTILS.GetDCSMap()
if theatre==DCSMAP.Caucasus then
return 4
elseif theatre==DCSMAP.PersianGulf then
return 4
elseif theatre==DCSMAP.NTTR then
return-8
elseif theatre==DCSMAP.Normandy then
return 0
elseif theatre==DCSMAP.TheChannel then
return 2
elseif theatre==DCSMAP.Syria then
return 3
elseif theatre==DCSMAP.MarianaIslands then
return 10
elseif theatre==DCSMAP.Falklands then
return-3
elseif theatre==DCSMAP.Sinai then
return 2
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
end
end
function UTILS.GetDayOfYear(Year,Month,Day)
local floor=math.floor
local n1=floor(275*Month/9)
local n2=floor((Month+9)/12)
local n3=(1+floor((Year-4*floor(Year/4)+2)/3))
return n1-(n2*n3)+Day-30
end
function UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,Rising,Tlocal)
local zenith=90.83
local latitude=Latitude
local longitude=Longitude
local rising=Rising
local n=DayOfYear
Tlocal=Tlocal or 0
local rad=math.rad
local deg=math.deg
local floor=math.floor
local frac=function(n)return n-floor(n)end
local cos=function(d)return math.cos(rad(d))end
local acos=function(d)return deg(math.acos(d))end
local sin=function(d)return math.sin(rad(d))end
local asin=function(d)return deg(math.asin(d))end
local tan=function(d)return math.tan(rad(d))end
local atan=function(d)return deg(math.atan(d))end
local function fit_into_range(val,min,max)
local range=max-min
local count
if val<min then
count=floor((min-val)/range)+1
return val+count*range
elseif val>=max then
count=floor((val-max)/range)+1
return val-count*range
else
return val
end
end
local lng_hour=longitude/15
local t
if rising then
t=n+((6-lng_hour)/24)
else
t=n+((18-lng_hour)/24)
end
local M=(0.9856*t)-3.289
local L=fit_into_range(M+(1.916*sin(M))+(0.020*sin(2*M))+282.634,0,360)
local RA=fit_into_range(atan(0.91764*tan(L)),0,360)
local Lquadrant=floor(L/90)*90
local RAquadrant=floor(RA/90)*90
RA=RA+Lquadrant-RAquadrant
RA=RA/15
local sinDec=0.39782*sin(L)
local cosDec=cos(asin(sinDec))
local cosH=(cos(zenith)-(sinDec*sin(latitude)))/(cosDec*cos(latitude))
if rising and cosH>1 then
return"N/S"
elseif cosH<-1 then
return"N/R"
end
local H
if rising then
H=360-acos(cosH)
else
H=acos(cosH)
end
H=H/15
local T=H+RA-(0.06571*t)-6.622
local UT=fit_into_range(T-lng_hour+Tlocal,0,24)
return floor(UT)*60*60+frac(UT)*60*60
end
function UTILS.GetSunrise(Day,Month,Year,Latitude,Longitude,Tlocal)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tlocal)
end
function UTILS.GetSunset(Day,Month,Year,Latitude,Longitude,Tlocal)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tlocal)
end
function UTILS.GetOSTime()
if os then
local ts=0
local t=os.date("*t")
local s=t.sec
local m=t.min*60
local h=t.hour*3600
ts=s+m+h
return ts
else
return nil
end
end
function UTILS.ShuffleTable(t)
if t==nil or type(t)~="table"then
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
return
end
math.random()
math.random()
math.random()
local TempTable={}
for i=1,#t do
local r=math.random(1,#t)
TempTable[i]=t[r]
table.remove(t,r)
end
return TempTable
end
function UTILS.GetRandomTableElement(t,replace)
if t==nil or type(t)~="table"then
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
return
end
math.random()
math.random()
math.random()
local r=math.random(#t)
local element=t[r]
if not replace then
table.remove(t,r)
end
return element
end
function UTILS.IsLoadingDoorOpen(unit_name)
local unit=Unit.getByName(unit_name)
if unit~=nil then
local type_name=unit:getTypeName()
BASE:T("TypeName = "..type_name)
if type_name=="Mi-8MT"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1 or unit:getDrawArgumentValue(250)<0)then
BASE:T(unit_name.." Cargo doors are open or cargo door not present")
return true
end
if type_name=="Mi-24P"and(unit:getDrawArgumentValue(38)==1 or unit:getDrawArgumentValue(86)==1)then
BASE:T(unit_name.." a side door is open")
return true
end
if type_name=="UH-1H"and(unit:getDrawArgumentValue(43)==1 or unit:getDrawArgumentValue(44)==1)then
BASE:T(unit_name.." a side door is open ")
return true
end
if string.find(type_name,"SA342")and(unit:getDrawArgumentValue(34)==1)then
BASE:T(unit_name.." front door(s) are open or doors removed")
return true
end
if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1215)==1 and unit:getDrawArgumentValue(1216)==1)then
BASE:T(unit_name.." rear doors are open")
return true
end
if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1220)==1 or unit:getDrawArgumentValue(1221)==1)then
BASE:T(unit_name.." para doors are open")
return true
end
if string.find(type_name,"Hercules")and(unit:getDrawArgumentValue(1217)==1)then
BASE:T(unit_name.." side door is open")
return true
end
if type_name=="Bell-47"then
BASE:T(unit_name.." door is open")
return true
end
if type_name=="UH-60L"and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then
BASE:T(unit_name.." cargo door is open")
return true
end
if type_name=="UH-60L"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumentValue(400)==1)then
BASE:T(unit_name.." front door(s) are open")
return true
end
if type_name=="AH-64D_BLK_II"then
BASE:T(unit_name.." front door(s) are open")
return true
end
if type_name=="Bronco-OV-10A"then
BASE:T(unit_name.." front door(s) are open")
return true
end
if type_name=="MH-60R"and(unit:getDrawArgumentValue(403)>0 or unit:getDrawArgumentValue(403)==-1)then
BASE:T(unit_name.." cargo door is open")
return true
end
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
function UTILS.GenerateFMFrequencies()
local FreeFMFrequencies={}
for _first=3,7 do
for _second=0,5 do
for _third=0,9 do
local _frequency=((100*_first)+(10*_second)+_third)*100000
table.insert(FreeFMFrequencies,_frequency)
end
end
end
return FreeFMFrequencies
end
function UTILS.GenerateVHFrequencies()
local _skipFrequencies={
214,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,
905,907,920,935,942,950,995,
1000,1025,1030,1050,1065,1116,1175,1182,1210,1215
}
local FreeVHFFrequencies={}
local _start=200000
while _start<400000 do
local _found=false
for _,value in pairs(_skipFrequencies)do
if value*1000==_start then
_found=true
break
end
end
if _found==false then
table.insert(FreeVHFFrequencies,_start)
end
_start=_start+10000
end
_start=400000
while _start<850000 do
local _found=false
for _,value in pairs(_skipFrequencies)do
if value*1000==_start then
_found=true
break
end
end
if _found==false then
table.insert(FreeVHFFrequencies,_start)
end
_start=_start+10000
end
_start=850000
while _start<=999000 do
local _found=false
for _,value in pairs(_skipFrequencies)do
if value*1000==_start then
_found=true
break
end
end
if _found==false then
table.insert(FreeVHFFrequencies,_start)
end
_start=_start+50000
end
return FreeVHFFrequencies
end
function UTILS.GenerateUHFrequencies(Start,End)
local FreeUHFFrequencies={}
local _start=220000000
if not Start then
while _start<399000000 do
if _start~=243000000 then
table.insert(FreeUHFFrequencies,_start)
end
_start=_start+500000
end
else
local myend=End*1000000 or 399000000
local mystart=Start*1000000 or 220000000
while _start<399000000 do
if _start~=243000000 and(_start<mystart or _start>myend)then
print(_start)
table.insert(FreeUHFFrequencies,_start)
end
_start=_start+500000
end
end
return FreeUHFFrequencies
end
function UTILS.GenerateLaserCodes()
local jtacGeneratedLaserCodes={}
local function ContainsDigit(_number,_numberToFind)
local _thisNumber=_number
local _thisDigit=0
while _thisNumber~=0 do
_thisDigit=_thisNumber%10
_thisNumber=math.floor(_thisNumber/10)
if _thisDigit==_numberToFind then
return true
end
end
return false
end
local _code=1111
local _count=1
while _code<1777 and _count<30 do
while true do
_code=_code+1
if not ContainsDigit(_code,8)
and not ContainsDigit(_code,9)
and not ContainsDigit(_code,0)then
table.insert(jtacGeneratedLaserCodes,_code)
break
end
end
_count=_count+1
end
return jtacGeneratedLaserCodes
end
function UTILS.EnsureTable(Object,ReturnNil)
if Object then
if type(Object)~="table"then
Object={Object}
end
else
if ReturnNil then
return nil
else
Object={}
end
end
return Object
end
function UTILS.SaveToFile(Path,Filename,Data)
if not io then
BASE:E("ERROR: io not desanitized. Can't save current file.")
return false
end
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
local path=nil
if lfs then
path=Path or lfs.writedir()
end
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
local f=assert(io.open(filename,"wb"))
f:write(Data)
f:close()
return true
end
function UTILS.LoadFromFile(Path,Filename)
if not io then
BASE:E("ERROR: io not desanitized. Can't save current state.")
return false
end
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
local path=nil
if lfs then
path=Path or lfs.writedir()
end
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
local exists=UTILS.CheckFileExists(Path,Filename)
if not exists then
BASE:I(string.format("ERROR: File %s does not exist!",filename))
return false
end
local file=assert(io.open(filename,"rb"))
local loadeddata={}
for line in file:lines()do
loadeddata[#loadeddata+1]=line
end
file:close()
return true,loadeddata
end
function UTILS.CheckFileExists(Path,Filename)
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
if not io then
BASE:E("ERROR: io not desanitized.")
return false
end
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
local path=nil
if lfs then
path=Path or lfs.writedir()
end
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if not exists then
BASE:E(string.format("ERROR: File %s does not exist!",filename))
return false
else
return true
end
end
function UTILS.GetCountPerTypeName(Group)
local units=Group:GetUnits()
local TypeNameTable={}
for _,_unt in pairs(units)do
local unit=_unt
local typen=unit:GetTypeName()
if not TypeNameTable[typen]then
TypeNameTable[typen]=1
else
TypeNameTable[typen]=TypeNameTable[typen]+1
end
end
return TypeNameTable
end
function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured)
local filename=Filename or"StateListofGroups"
local data="--Save Stationary List of Groups: "..Filename.."\n"
for _,_group in pairs(List)do
local group=GROUP:FindByName(_group)
if group and group:IsAlive()then
local units=group:CountAliveUnits()
local position=group:GetVec3()
if Structured then
local structure=UTILS.GetCountPerTypeName(group)
local strucdata=""
for typen,anzahl in pairs(structure)do
strucdata=strucdata..typen.."=="..anzahl..";"
end
data=string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata)
else
data=string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
end
else
data=string.format("%s%s,0,0,0,0\n",data,_group)
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
local filename=Filename or"SetOfGroups"
local data="--Save SET of groups: "..Filename.."\n"
local List=Set:GetSetObjects()
for _,_group in pairs(List)do
local group=_group
if group and group:IsAlive()then
local name=group:GetName()
local template=string.gsub(name,"-(.+)$","")
if string.find(name,"AID")then
template=string.gsub(name,"(.AID.%d+$","")
end
if string.find(template,"#")then
template=string.gsub(name,"#(%d+)$","")
end
local units=group:CountAliveUnits()
local position=group:GetVec3()
if Structured then
local structure=UTILS.GetCountPerTypeName(group)
local strucdata=""
for typen,anzahl in pairs(structure)do
strucdata=strucdata..typen.."=="..anzahl..";"
end
data=string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
else
data=string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
end
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.SaveSetOfStatics(Set,Path,Filename)
local filename=Filename or"SetOfStatics"
local data="--Save SET of statics: "..Filename.."\n"
local List=Set:GetSetObjects()
for _,_group in pairs(List)do
local group=_group
if group and group:IsAlive()then
local name=group:GetName()
local position=group:GetVec3()
data=string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z)
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.SaveStationaryListOfStatics(List,Path,Filename)
local filename=Filename or"StateListofStatics"
local data="--Save Stationary List of Statics: "..Filename.."\n"
for _,_group in pairs(List)do
local group=STATIC:FindByName(_group,false)
if group and group:IsAlive()then
local position=group:GetVec3()
data=string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z)
else
data=string.format("%s%s,0,0,0,0\n",data,_group)
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
local fires={}
local function Smokers(name,coord,effect,density)
local eff=math.random(8)
if type(effect)=="number"then eff=effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units=group:GetUnits()
local reduced=0
for _,_unit in pairs(units)do
local typo=_unit:GetTypeName()
if typename==typo then
if Cinematic then
local coordinate=_unit:GetCoordinate()
local name=_unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced=reduced+1
if reduced==anzahl then break end
end
end
end
local reduce=true
if Reduce==false then reduce=false end
local filename=Filename or"StateListofGroups"
local datatable={}
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local groupname=dataset[1]
local size=tonumber(dataset[2])
local posx=tonumber(dataset[3])
local posy=tonumber(dataset[4])
local posz=tonumber(dataset[5])
local structure=dataset[6]
local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz})
local data={groupname=groupname,size=size,coordinate=coordinate,group=GROUP:FindByName(groupname)}
if reduce then
local actualgroup=GROUP:FindByName(groupname)
if actualgroup and actualgroup:IsAlive()and actualgroup:CountAliveUnits()>size then
if Structured and structure then
local loadedstructure={}
local strcset=UTILS.Split(structure,";")
for _,_data in pairs(strcset)do
local datasplit=UTILS.Split(_data,"==")
loadedstructure[datasplit[1]]=tonumber(datasplit[2])
end
local originalstructure=UTILS.GetCountPerTypeName(actualgroup)
for _name,_number in pairs(originalstructure)do
local loadednumber=0
if loadedstructure[_name]then
loadednumber=loadedstructure[_name]
end
local reduce=false
if loadednumber<_number then reduce=true end
if reduce then
Cruncher(actualgroup,_name,_number-loadednumber)
end
end
else
local reduction=actualgroup:CountAliveUnits()-size
local units=actualgroup:GetUnits()
local units2=UTILS.ShuffleTable(units)
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
table.insert(datatable,data)
end
else
return nil
end
return datatable,fires
end
function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
local fires={}
local usedtemplates={}
local spawn=true
if Spawn==false then spawn=false end
local filename=Filename or"SetOfGroups"
local setdata=SET_GROUP:New()
local datatable={}
local function Smokers(name,coord,effect,density)
local eff=math.random(8)
if type(effect)=="number"then eff=effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units=group:GetUnits()
local reduced=0
for _,_unit in pairs(units)do
local typo=_unit:GetTypeName()
if typename==typo then
if Cinematic then
local coordinate=_unit:GetCoordinate()
local name=_unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced=reduced+1
if reduced==anzahl then break end
end
end
end
local function PostSpawn(args)
local spwndgrp=args[1]
local size=args[2]
local structure=args[3]
setdata:AddObject(spwndgrp)
local actualsize=spwndgrp:CountAliveUnits()
if actualsize>size then
if Structured and structure then
local loadedstructure={}
local strcset=UTILS.Split(structure,";")
for _,_data in pairs(strcset)do
local datasplit=UTILS.Split(_data,"==")
loadedstructure[datasplit[1]]=tonumber(datasplit[2])
end
local originalstructure=UTILS.GetCountPerTypeName(spwndgrp)
for _name,_number in pairs(originalstructure)do
local loadednumber=0
if loadedstructure[_name]then
loadednumber=loadedstructure[_name]
end
local reduce=false
if loadednumber<_number then reduce=true end
if reduce then
Cruncher(spwndgrp,_name,_number-loadednumber)
end
end
else
local reduction=actualsize-size
local units=spwndgrp:GetUnits()
local units2=UTILS.ShuffleTable(units)
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
local function MultiUse(Data)
local template=Data.template
if template and usedtemplates[template]and usedtemplates[template].used and usedtemplates[template].used>1 then
if not usedtemplates[template].done then
local spwnd=0
local spawngrp=SPAWN:New(template)
spawngrp:InitLimit(0,usedtemplates[template].used)
for _,_entry in pairs(usedtemplates[template].data)do
spwnd=spwnd+1
local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
usedtemplates[template].done=true
end
return true
else
return false
end
end
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local groupname=dataset[1]
local template=dataset[2]
local size=tonumber(dataset[3])
local posx=tonumber(dataset[4])
local posy=tonumber(dataset[5])
local posz=tonumber(dataset[6])
local structure=dataset[7]
local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz})
local group=nil
if size>0 then
local data={groupname=groupname,size=size,coordinate=coordinate,template=template,structure=structure}
table.insert(datatable,data)
if usedtemplates[template]then
usedtemplates[template].used=usedtemplates[template].used+1
table.insert(usedtemplates[template].data,data)
else
usedtemplates[template]={
data={},
used=1,
done=false,
}
table.insert(usedtemplates[template].data,data)
end
end
end
for _id,_entry in pairs(datatable)do
if spawn and not MultiUse(_entry)and _entry.size>0 then
local group=SPAWN:New(_entry.template)
local sgrp=group:SpawnFromCoordinate(_entry.coordinate)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
end
else
return nil
end
if spawn then
return setdata,fires
else
return datatable
end
end
function UTILS.LoadSetOfStatics(Path,Filename)
local filename=Filename or"SetOfStatics"
local datatable=SET_STATIC:New()
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local staticname=dataset[1]
local StaticObject=STATIC:FindByName(staticname,false)
if StaticObject then
datatable:AddObject(StaticObject)
end
end
else
return nil
end
return datatable
end
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
local fires={}
local reduce=true
if Reduce==false then reduce=false end
local filename=Filename or"StateListofStatics"
local datatable={}
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local staticname=dataset[1]
local size=tonumber(dataset[2])
local posx=tonumber(dataset[3])
local posy=tonumber(dataset[4])
local posz=tonumber(dataset[5])
local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz})
local data={staticname=staticname,size=size,coordinate=coordinate,static=STATIC:FindByName(staticname,false)}
table.insert(datatable,data)
if size==0 and reduce then
local static=STATIC:FindByName(staticname,false)
if static then
if Dead then
local deadobject=SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry())
deadobject:InitDead(true)
local heading=static:GetHeading()
local coord=static:GetCoordinate()
static:Destroy(false)
deadobject:SpawnFromCoordinate(coord,heading,staticname)
if Cinematic then
local effect=math.random(8)
if type(Effect)=="number"then
effect=Effect
end
coord:BigSmokeAndFire(effect,Density,staticname)
table.insert(fires,staticname)
end
else
static:Destroy(false)
end
end
end
end
else
return nil
end
return datatable,fires
end
function UTILS.BearingToCardinal(Heading)
if Heading>=0 and Heading<=22 then return"North"
elseif Heading>=23 and Heading<=66 then return"North-East"
elseif Heading>=67 and Heading<=101 then return"East"
elseif Heading>=102 and Heading<=146 then return"South-East"
elseif Heading>=147 and Heading<=201 then return"South"
elseif Heading>=202 and Heading<=246 then return"South-West"
elseif Heading>=247 and Heading<=291 then return"West"
elseif Heading>=292 and Heading<=338 then return"North-West"
elseif Heading>=339 then return"North"
end
end
function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
local BRAANATO="Merged."
local GroupNumber=ToGrp:GetSize()
local GroupWords="Singleton"
if GroupNumber==2 then GroupWords="Two-Ship"
elseif GroupNumber>=3 then GroupWords="Heavy"
end
local grpLeadUnit=ToGrp:GetUnit(1)
local tgtCoord=grpLeadUnit:GetCoordinate()
local currentCoord=FromGrp:GetCoordinate()
local hdg=UTILS.Round(ToGrp:GetHeading()/100,1)*100
local bearing=UTILS.Round(currentCoord:HeadingTo(tgtCoord),0)
local rangeMetres=tgtCoord:Get2DDistance(currentCoord)
local rangeNM=UTILS.Round(UTILS.MetersToNM(rangeMetres),0)
local aspect=tgtCoord:ToStringAspect(currentCoord)
local alt=UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)
local track=UTILS.BearingToCardinal(hdg)
if rangeNM>3 then
if aspect==""then
BRAANATO=string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing,rangeNM,alt,track)
else
BRAANATO=string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords,bearing,rangeNM,alt,aspect,track)
end
end
return BRAANATO
end
function UTILS.IsInTable(Table,Object,Key)
for key,object in pairs(Table)do
if Key then
if Object[Key]==object[Key]then
return true
end
else
if object==Object then
return true
end
end
end
return false
end
function UTILS.IsAnyInTable(Table,Objects,Key)
for _,Object in pairs(UTILS.EnsureTable(Objects))do
for key,object in pairs(Table)do
if Key then
if Object[Key]==object[Key]then
return true
end
else
if object==Object then
return true
end
end
end
end
return false
end
function UTILS.PlotRacetrack(Coordinate,Altitude,Speed,Heading,Leg,Coalition,Color,Alpha,LineType,ReadOnly)
local fix_coordinate=Coordinate
local altitude=Altitude
local speed=Speed or 350
local heading=Heading or 270
local leg_distance=Leg or 10
local coalition=Coalition or-1
local color=Color or{1,0,0}
local alpha=Alpha or 1
local lineType=LineType or 1
speed=UTILS.IasToTas(speed,UTILS.FeetToMeters(altitude),oatcorr)
local turn_radius=0.0211*speed-3.01
local point_two=fix_coordinate:Translate(UTILS.NMToMeters(leg_distance),heading,true,false)
local point_three=point_two:Translate(UTILS.NMToMeters(turn_radius)*2,heading-90,true,false)
local point_four=fix_coordinate:Translate(UTILS.NMToMeters(turn_radius)*2,heading-90,true,false)
local circle_center_fix_four=point_two:Translate(UTILS.NMToMeters(turn_radius),heading-90,true,false)
local circle_center_two_three=fix_coordinate:Translate(UTILS.NMToMeters(turn_radius),heading-90,true,false)
fix_coordinate:LineToAll(point_two,coalition,color,alpha,lineType)
point_four:LineToAll(point_three,coalition,color,alpha,lineType)
circle_center_fix_four:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType)
circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius),coalition,color,alpha,nil,0,lineType)
end
function UTILS.TimeNow()
return UTILS.SecondsToClock(timer.getAbsTime(),false,false)
end
function UTILS.TimeDifferenceInSeconds(start_time,end_time)
return UTILS.ClockToSeconds(end_time)-UTILS.ClockToSeconds(start_time)
end
function UTILS.TimeLaterThan(time_string)
if timer.getAbsTime()>UTILS.ClockToSeconds(time_string)then
return true
end
return false
end
function UTILS.TimeBefore(time_string)
if timer.getAbsTime()<UTILS.ClockToSeconds(time_string)then
return true
end
return false
end
function UTILS.CombineTimeStrings(time_string_01,time_string_02)
local hours1,minutes1,seconds1=time_string_01:match("(%d+):(%d+):(%d+)")
local hours2,minutes2,seconds2=time_string_02:match("(%d+):(%d+):(%d+)")
local total_seconds=tonumber(seconds1)+tonumber(seconds2)+tonumber(minutes1)*60+tonumber(minutes2)*60+tonumber(hours1)*3600+tonumber(hours2)*3600
total_seconds=total_seconds%(24*3600)
if total_seconds<0 then
total_seconds=total_seconds+24*3600
end
local hours=math.floor(total_seconds/3600)
total_seconds=total_seconds-hours*3600
local minutes=math.floor(total_seconds/60)
local seconds=total_seconds%60
return string.format("%02d:%02d:%02d",hours,minutes,seconds)
end
function UTILS.SubtractTimeStrings(time_string_01,time_string_02)
local hours1,minutes1,seconds1=time_string_01:match("(%d+):(%d+):(%d+)")
local hours2,minutes2,seconds2=time_string_02:match("(%d+):(%d+):(%d+)")
local total_seconds=tonumber(seconds1)-tonumber(seconds2)+tonumber(minutes1)*60-tonumber(minutes2)*60+tonumber(hours1)*3600-tonumber(hours2)*3600
total_seconds=total_seconds%(24*3600)
if total_seconds<0 then
total_seconds=total_seconds+24*3600
end
local hours=math.floor(total_seconds/3600)
total_seconds=total_seconds-hours*3600
local minutes=math.floor(total_seconds/60)
local seconds=total_seconds%60
return string.format("%02d:%02d:%02d",hours,minutes,seconds)
end
function UTILS.TimeBetween(start_time,end_time)
return UTILS.TimeLaterThan(start_time)and UTILS.TimeBefore(end_time)
end
function UTILS.PercentageChance(chance)
chance=chance or math.random(0,100)
chance=UTILS.Clamp(chance,0,100)
local percentage=math.random(0,100)
if percentage<chance then
return true
end
return false
end
function UTILS.Clamp(value,min,max)
if value<min then value=min end
if value>max then value=max end
return value
end
function UTILS.ClampAngle(value)
if value>360 then return value-360 end
if value<0 then return value+360 end
return value
end
function UTILS.RemapValue(value,old_min,old_max,new_min,new_max)
new_min=new_min or 0
new_max=new_max or 100
local old_range=old_max-old_min
local new_range=new_max-new_min
local percentage=(value-old_min)/old_range
return(new_range*percentage)+new_min
end
function UTILS.RandomPointInTriangle(pt1,pt2,pt3)
local pt={math.random(),math.random()}
table.sort(pt)
local s=pt[1]
local t=pt[2]-pt[1]
local u=1-pt[2]
return{x=s*pt1.x+t*pt2.x+u*pt3.x,
y=s*pt1.y+t*pt2.y+u*pt3.y}
end
function UTILS.AngleBetween(angle,min,max)
angle=(360+(angle%360))%360
min=(360+min%360)%360
max=(360+max%360)%360
if min<max then return min<=angle and angle<=max end
return min<=angle or angle<=max
end
function UTILS.WriteJSON(data,file_path)
package.path=package.path..";.\\Scripts\\?.lua"
local JSON=require("json")
local pretty_json_text=JSON:encode_pretty(data)
local write_file=io.open(file_path,"w")
write_file:write(pretty_json_text)
write_file:close()
end
function UTILS.ReadJSON(file_path)
package.path=package.path..";.\\Scripts\\?.lua"
local JSON=require("json")
local read_file=io.open(file_path,"r")
local contents=read_file:read("*a")
io.close(read_file)
return JSON:decode(contents)
end
function UTILS.GetZoneProperties(zone_name)
local return_table={}
for _,zone in pairs(env.mission.triggers.zones)do
if zone["name"]==zone_name then
if table.length(zone["properties"])>0 then
for _,property in pairs(zone["properties"])do
return_table[property["key"]]=property["value"]
end
return return_table
else
BASE:I(string.format("%s doesn't have any properties",zone_name))
return{}
end
end
end
end
function UTILS.RotatePointAroundPivot(point,pivot,angle)
local radians=math.rad(angle)
local x=point.x-pivot.x
local y=point.y-pivot.y
local rotated_x=x*math.cos(radians)-y*math.sin(radians)
local rotatex_y=x*math.sin(radians)+y*math.cos(radians)
local original_x=rotated_x+pivot.x
local original_y=rotatex_y+pivot.y
return{x=original_x,y=original_y}
end
function UTILS.UniqueName(base)
base=base or""
local ran=tostring(math.random(0,1000000))
if base==""then
return ran
end
return base.."_"..ran
end
function string.startswith(str,value)
return string.sub(str,1,string.len(value))==value
end
function string.endswith(str,value)
return value==""or str:sub(-#value)==value
end
function string.split(input,separator)
local parts={}
for part in input:gmatch("[^"..separator.."]+")do
table.insert(parts,part)
end
return parts
end
function string.contains(str,value)
return string.match(str,value)
end
function table.move_object(obj,from_table,to_table)
local index
for i,v in pairs(from_table)do
if v==obj then
index=i
end
end
if index then
local moved=table.remove(from_table,index)
table.insert_unique(to_table,moved)
end
end
function table.contains(tbl,element)
if element==nil or tbl==nil then return false end
local index=1
while tbl[index]do
if tbl[index]==element then
return true
end
index=index+1
end
return false
end
function table.contains_key(tbl,key)
if tbl[key]~=nil then return true else return false end
end
function table.insert_unique(tbl,element)
if element==nil or tbl==nil then return end
if not table.contains(tbl,element)then
table.insert(tbl,element)
end
end
function table.remove_by_value(tbl,element)
local indices_to_remove={}
local index=1
for _,value in pairs(tbl)do
if value==element then
table.insert(indices_to_remove,index)
end
index=index+1
end
for _,idx in pairs(indices_to_remove)do
table.remove(tbl,idx)
end
end
function table.remove_key(table,key)
local element=table[key]
table[key]=nil
return element
end
function table.index_of(table,element)
for i,v in ipairs(table)do
if v==element then
return i
end
end
return nil
end
function table.length(T)
local count=0
for _ in pairs(T)do count=count+1 end
return count
end
function table.slice(tbl,first,last)
local sliced={}
local start=first or 1
local stop=last or table.length(tbl)
local count=1
for key,value in pairs(tbl)do
if count>=start and count<=stop then
sliced[key]=value
end
count=count+1
end
return sliced
end
function table.count_value(tbl,value)
local count=0
for _,item in pairs(tbl)do
if item==value then count=count+1 end
end
return count
end
function table.combine(t1,t2)
if t1==nil and t2==nil then
BASE:E("Both tables were empty!")
end
if t1==nil then return t2 end
if t2==nil then return t1 end
for i=1,#t2 do
t1[#t1+1]=t2[i]
end
return t1
end
function table.merge(t1,t2)
for k,v in pairs(t2)do
if(type(v)=="table")and(type(t1[k]or false)=="table")then
table.merge(t1[k],t2[k])
else
t1[k]=v
end
end
return t1
end
function table.add(tbl,item)
tbl[#tbl+1]=item
end
function table.shuffle(tbl)
local new_table={}
for _,value in ipairs(tbl)do
local pos=math.random(1,#new_table+1)
table.insert(new_table,pos,value)
end
return new_table
end
function table.find_key_value_pair(tbl,key,value)
for k,v in pairs(tbl)do
if type(v)=="table"then
local result=table.find_key_value_pair(v,key,value)
if result~=nil then
return result
end
elseif k==key and v==value then
return tbl
end
end
return nil
end
function UTILS.DecimalToOctal(Number)
if Number<8 then return Number end
local number=tonumber(Number)
local octal=""
local n=1
while number>7 do
local number1=number%8
octal=string.format("%d",number1)..octal
local number2=math.abs(number/8)
if number2<8 then
octal=string.format("%d",number2)..octal
end
number=number2
n=n+1
end
return tonumber(octal)
end
function UTILS.OctalToDecimal(Number)
return tonumber(Number,8)
end
function UTILS.HexToRGBA(hex_string)
local hexNumber=tonumber(string.sub(hex_string,3),16)
local alpha=hexNumber%256
hexNumber=(hexNumber-alpha)/256
local blue=hexNumber%256
hexNumber=(hexNumber-blue)/256
local green=hexNumber%256
hexNumber=(hexNumber-green)/256
local red=hexNumber%256
return{R=red,G=green,B=blue,A=alpha}
end
function UTILS.SaveSetOfOpsGroups(Set,Path,Filename,Structured)
local filename=Filename or"SetOfGroups"
local data="--Save SET of groups: (name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata) "..Filename.."\n"
local List=Set:GetSetObjects()
for _,_group in pairs(List)do
local group=_group:GetGroup()
if group and group:IsAlive()then
local name=group:GetName()
local template=string.gsub(name,"(.AID.%d+$","")
if string.find(template,"#")then
template=string.gsub(name,"#(%d+)$","")
end
local alttemplate=_group.templatename or"none"
local legiono=_group.legion
local legion="none"
if legiono and type(legiono)=="table"and legiono.ClassName then
legion=legiono:GetName()
local asset=legiono:GetAssetByName(name)
alttemplate=asset.templatename
end
local units=group:CountAliveUnits()
local position=group:GetVec3()
if Structured then
local structure=UTILS.GetCountPerTypeName(group)
local strucdata=""
for typen,anzahl in pairs(structure)do
strucdata=strucdata..typen.."=="..anzahl..";"
end
data=string.format("%s%s,%s,%s,%s,%d,%d,%d,%d,%s\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata)
else
data=string.format("%s%s,%s,%s,%s,%d,%d,%d,%d\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z)
end
end
end
local outcome=UTILS.SaveToFile(Path,Filename,data)
return outcome
end
function UTILS.LoadSetOfOpsGroups(Path,Filename)
local filename=Filename or"SetOfGroups"
local datatable={}
if UTILS.CheckFileExists(Path,filename)then
local outcome,loadeddata=UTILS.LoadFromFile(Path,Filename)
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local groupname=dataset[1]
local legion=dataset[2]
local template=dataset[3]
local alttemplate=dataset[4]
local size=tonumber(dataset[5])
local posx=tonumber(dataset[6])
local posy=tonumber(dataset[7])
local posz=tonumber(dataset[8])
local structure=dataset[9]
local coordinate=COORDINATE:NewFromVec3({x=posx,y=posy,z=posz})
if size>0 then
local data={groupname=groupname,size=size,coordinate=coordinate,template=template,structure=structure,legion=legion,alttemplate=alttemplate}
table.insert(datatable,data)
end
end
else
return nil
end
return datatable
end
function UTILS.ClockHeadingString(refHdg,tgtHdg)
local relativeAngle=tgtHdg-refHdg
if relativeAngle<0 then
relativeAngle=relativeAngle+360
end
local clockPos=math.ceil((relativeAngle%360)/30)
return clockPos.." o'clock"
end
function UTILS.MGRSStringToSRSFriendly(Text,Slow)
local Text=string.gsub(Text,"MGRS ","")
Text=string.gsub(Text,"%s+","")
Text=string.gsub(Text,"([%a%d])","%1;")
Text=string.gsub(Text,"A","Alpha")
Text=string.gsub(Text,"B","Bravo")
Text=string.gsub(Text,"C","Charlie")
Text=string.gsub(Text,"D","Delta")
Text=string.gsub(Text,"E","Echo")
Text=string.gsub(Text,"F","Foxtrot")
Text=string.gsub(Text,"G","Golf")
Text=string.gsub(Text,"H","Hotel")
Text=string.gsub(Text,"I","India")
Text=string.gsub(Text,"J","Juliett")
Text=string.gsub(Text,"K","Kilo")
Text=string.gsub(Text,"L","Lima")
Text=string.gsub(Text,"M","Mike")
Text=string.gsub(Text,"N","November")
Text=string.gsub(Text,"O","Oscar")
Text=string.gsub(Text,"P","Papa")
Text=string.gsub(Text,"Q","Quebec")
Text=string.gsub(Text,"R","Romeo")
Text=string.gsub(Text,"S","Sierra")
Text=string.gsub(Text,"T","Tango")
Text=string.gsub(Text,"U","Uniform")
Text=string.gsub(Text,"V","Victor")
Text=string.gsub(Text,"W","Whiskey")
Text=string.gsub(Text,"X","Xray")
Text=string.gsub(Text,"Y","Yankee")
Text=string.gsub(Text,"Z","Zulu")
Text=string.gsub(Text,"0","zero")
Text=string.gsub(Text,"9","niner")
if Slow then
Text='<prosody rate="slow">'..Text..'</prosody>'
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.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment)
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 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 newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country)
newfarp:InitShape(SShapeName)
newfarp:InitFARP(callsign,freq,mod)
local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name)
table.insert(ReturnObjects,spawnedfarp)
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 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
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
return ReturnObjects,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(px<newpoly[k].x+vt*(newpoly[k+1].x-newpoly[k].x))then
cn=cn+1
end
end
end
return cn%2==1
else
return false
end
end
function UTILS.ScalarMult(vec,mult)
return{x=vec.x*mult,y=vec.y*mult,z=vec.z*mult}
end
UTILS.Weather={}
function UTILS.Weather.GetFogThickness()
return world.weather.getFogThickness()
end
function UTILS.Weather.SetFogThickness(Thickness)
local value=Thickness
if value<100 then value=100
elseif value>5000 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
PROFILER={
ClassName="PROFILER",
Counters={},
dInfo={},
fTime={},
fTimeTotal={},
eventHandler={},
logUnknown=false,
ThreshCPS=0.0,
ThreshTtot=0.005,
fileNamePrefix="MooseProfiler",
fileNameSuffix="txt"
}
function PROFILER.Start(Delay,Duration)
local go=true
if not os then
env.error("ERROR: Profiler needs os to be de-sanitized!")
go=false
end
if not io then
env.error("ERROR: Profiler needs io to be desanitized!")
go=false
end
if not lfs then
env.error("ERROR: Profiler needs lfs to be desanitized!")
go=false
end
if not go then
return
end
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay,PROFILER.Start,0,Duration)
else
PROFILER.TstartGame=timer.getTime()
PROFILER.TstartOS=os.clock()
world.addEventHandler(PROFILER.eventHandler)
env.info('############################   Profiler Started   ############################')
if Duration then
env.info(string.format("- Will be running for %d seconds",Duration))
else
env.info(string.format("- Will be stopped when mission ends"))
end
env.info(string.format("- Calls per second threshold %.3f/sec",PROFILER.ThreshCPS))
env.info(string.format("- Total function time threshold %.3f sec",PROFILER.ThreshTtot))
env.info(string.format("- Output file \"%s\" in your DCS log file folder",PROFILER.getfilename(PROFILER.fileNameSuffix)))
env.info(string.format("- Output file \"%s\" in CSV format",PROFILER.getfilename("csv")))
env.info('###############################################################################')
local duration=Duration or 600
trigger.action.outText("### Profiler running ###",duration)
debug.sethook(PROFILER.hook,"cr")
if Duration then
PROFILER.Stop(Duration)
end
end
end
function PROFILER.Stop(Delay)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay,PROFILER.Stop)
end
end
function PROFILER.Stop(Delay)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay,PROFILER.Stop)
else
debug.sethook()
local runTimeGame=timer.getTime()-PROFILER.TstartGame
local runTimeOS=os.clock()-PROFILER.TstartOS
PROFILER.showInfo(runTimeGame,runTimeOS)
end
end
function PROFILER.eventHandler:onEvent(event)
if event.id==world.event.S_EVENT_MISSION_END then
PROFILER.Stop()
end
end
function PROFILER.hook(event)
local f=debug.getinfo(2,"f").func
if event=='call'then
if PROFILER.Counters[f]==nil then
PROFILER.Counters[f]=1
PROFILER.dInfo[f]=debug.getinfo(2,"Sn")
if PROFILER.fTimeTotal[f]==nil then
PROFILER.fTimeTotal[f]=0
end
else
PROFILER.Counters[f]=PROFILER.Counters[f]+1
end
if PROFILER.fTime[f]==nil then
PROFILER.fTime[f]=os.clock()
end
elseif(event=='return')then
if PROFILER.fTime[f]~=nil then
PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f])
PROFILER.fTime[f]=nil
end
end
end
function PROFILER.getData(func)
local n=PROFILER.dInfo[func]
if n.what=="C"then
return n.name,"?","?",PROFILER.fTimeTotal[func]
end
return n.name,n.short_src,n.linedefined,PROFILER.fTimeTotal[func]
end
function PROFILER._flog(f,txt)
f:write(txt.."\r\n")
end
function PROFILER.showTable(data,f,runTimeGame)
for i=1,#data do
local t=data[i]
local cps=t.count/runTimeGame
local threshCPS=cps>=PROFILER.ThreshCPS
local threshTot=t.tm>=PROFILER.ThreshTtot
if threshCPS and threshTot then
local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call  %s line %s",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line))
PROFILER._flog(f,text)
end
end
end
function PROFILER.printCSV(data,runTimeGame)
local file=PROFILER.getfilename("csv")
local g=io.open(file,'w')
local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
g:write(text.."\r\n")
for i=1,#data do
local t=data[i]
local cps=t.count/runTimeGame
local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line))
g:write(txt.."\r\n")
end
g:close()
end
function PROFILER.getfilename(ext)
local dir=lfs.writedir()..[[Logs\]]
ext=ext or PROFILER.fileNameSuffix
local file=dir..PROFILER.fileNamePrefix.."."..ext
if not UTILS.FileExists(file)then
return file
end
for i=1,999 do
local file=string.format("%s%s-%03d.%s",dir,PROFILER.fileNamePrefix,i,ext)
if not UTILS.FileExists(file)then
return file
end
end
end
function PROFILER.showInfo(runTimeGame,runTimeOS)
local file=PROFILER.getfilename(PROFILER.fileNameSuffix)
local f=io.open(file,'w')
local Ttot=0
local Calls=0
local t={}
local tcopy=nil
local tserialize=nil
local tforgen=nil
local tpairs=nil
for func,count in pairs(PROFILER.Counters)do
local s,src,line,tm=PROFILER.getData(func)
if PROFILER.logUnknown==true then
if s==nil then s="<Unknown>"end
end
if s~=nil then
local T=
{func=s,
src=src,
line=line,
count=count,
tm=tm,
}
if s=="_copy"then
if tcopy==nil then
tcopy=T
else
tcopy.count=tcopy.count+T.count
tcopy.tm=tcopy.tm+T.tm
end
elseif s=="_Serialize"then
if tserialize==nil then
tserialize=T
else
tserialize.count=tserialize.count+T.count
tserialize.tm=tserialize.tm+T.tm
end
elseif s=="(for generator)"then
if tforgen==nil then
tforgen=T
else
tforgen.count=tforgen.count+T.count
tforgen.tm=tforgen.tm+T.tm
end
elseif s=="pairs"then
if tpairs==nil then
tpairs=T
else
tpairs.count=tpairs.count+T.count
tpairs.tm=tpairs.tm+T.tm
end
else
table.insert(t,T)
end
Ttot=Ttot+tm
Calls=Calls+count
end
end
if tcopy then
table.insert(t,tcopy)
end
if tserialize then
table.insert(t,tserialize)
end
if tforgen then
table.insert(t,tforgen)
end
if tpairs then
table.insert(t,tpairs)
end
env.info('############################   Profiler Stopped   ############################')
env.info(string.format("* Runtime Game     : %s = %d sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame))
env.info(string.format("* Runtime Real     : %s = %d sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS))
env.info(string.format("* Function time    : %s = %.1f sec (%.1f percent of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100))
env.info(string.format("* Total functions  : %d",#t))
env.info(string.format("* Total func calls : %d",Calls))
env.info(string.format("* Writing to file  : \"%s\"",file))
env.info(string.format("* Writing to file  : \"%s\"",PROFILER.getfilename("csv")))
env.info("##############################################################################")
table.sort(t,function(a,b)return a.tm>b.tm end)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"---- Profiler Report ----")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Runtime Game     : %s = %.1f sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame))
PROFILER._flog(f,string.format("* Runtime Real     : %s = %.1f sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS))
PROFILER._flog(f,string.format("* Function time    : %s = %.1f sec (%.1f %% of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Total functions  = %d",#t))
PROFILER._flog(f,string.format("* Total func calls = %d",Calls))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec",PROFILER.ThreshCPS))
PROFILER._flog(f,string.format("* Total func time threshold  = %.3f sec",PROFILER.ThreshTtot))
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER.showTable(t,f,runTimeGame)
table.sort(t,function(a,b)return a.tm/a.count>b.tm/b.count end)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Time per Call ----")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t,f,runTimeGame)
table.sort(t,function(a,b)return a.count>b.count end)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Total Calls ----")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t,f,runTimeGame)
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
f:close()
PROFILER.printCSV(t,runTimeGame)
end
do
FIFO={
ClassName="FIFO",
lid="",
version="0.0.5",
counter=0,
pointer=0,
stackbypointer={},
stackbyid={}
}
function FIFO:New()
local self=BASE:Inherit(self,BASE:New())
self.pointer=0
self.counter=0
self.stackbypointer={}
self.stackbyid={}
self.uniquecounter=0
self.lid=string.format("%s (%s) | ","FiFo",self.version)
self:T(self.lid.."Created.")
return self
end
function FIFO:Clear()
self:T(self.lid.."Clear")
self.pointer=0
self.counter=0
self.stackbypointer=nil
self.stackbyid=nil
self.stackbypointer={}
self.stackbyid={}
self.uniquecounter=0
return self
end
function FIFO:Push(Object,UniqueID)
self:T(self.lid.."Push")
self:T({Object,UniqueID})
self.pointer=self.pointer+1
self.counter=self.counter+1
local uniID=UniqueID
if not UniqueID then
self.uniquecounter=self.uniquecounter+1
uniID=self.uniquecounter
end
self.stackbyid[uniID]={pointer=self.pointer,data=Object,uniqueID=uniID}
self.stackbypointer[self.pointer]={pointer=self.pointer,data=Object,uniqueID=uniID}
return self
end
function FIFO:Pull()
self:T(self.lid.."Pull")
if self.counter==0 then return nil end
local object=self.stackbypointer[1].data
self.stackbypointer[1]=nil
self.counter=self.counter-1
self:Flatten()
return object
end
function FIFO:PullByPointer(Pointer)
self:T(self.lid.."PullByPointer "..tostring(Pointer))
if self.counter==0 then return nil end
local object=self.stackbypointer[Pointer]
self.stackbypointer[Pointer]=nil
if object then self.stackbyid[object.uniqueID]=nil end
self.counter=self.counter-1
self:Flatten()
if object then
return object.data
else
return nil
end
end
function FIFO:ReadByPointer(Pointer)
self:T(self.lid.."ReadByPointer "..tostring(Pointer))
if self.counter==0 or not Pointer or not self.stackbypointer[Pointer]then return nil end
local object=self.stackbypointer[Pointer]
if object then
return object.data
else
return nil
end
end
function FIFO:ReadByID(UniqueID)
self:T(self.lid.."ReadByID "..tostring(UniqueID))
if self.counter==0 or not UniqueID or not self.stackbyid[UniqueID]then return nil end
local object=self.stackbyid[UniqueID]
if object then
return object.data
else
return nil
end
end
function FIFO:PullByID(UniqueID)
self:T(self.lid.."PullByID "..tostring(UniqueID))
if self.counter==0 then return nil end
local object=self.stackbyid[UniqueID]
if object then
return self:PullByPointer(object.pointer)
else
return nil
end
end
function FIFO:Flatten()
self:T(self.lid.."Flatten")
local pointerstack={}
local idstack={}
local counter=0
for _ID,_entry in pairs(self.stackbypointer)do
counter=counter+1
pointerstack[counter]={pointer=counter,data=_entry.data,uniqueID=_entry.uniqueID}
end
for _ID,_entry in pairs(pointerstack)do
idstack[_entry.uniqueID]={pointer=_entry.pointer,data=_entry.data,uniqueID=_entry.uniqueID}
end
self.stackbypointer=nil
self.stackbypointer=pointerstack
self.stackbyid=nil
self.stackbyid=idstack
self.counter=counter
self.pointer=counter
return self
end
function FIFO:IsEmpty()
self:T(self.lid.."IsEmpty")
return self.counter==0 and true or false
end
function FIFO:GetSize()
self:T(self.lid.."GetSize")
return self.counter
end
function FIFO:Count()
self:T(self.lid.."Count")
return self.counter
end
function FIFO:IsNotEmpty()
self:T(self.lid.."IsNotEmpty")
return not self:IsEmpty()
end
function FIFO:GetPointerStack()
self:T(self.lid.."GetPointerStack")
return self.stackbypointer
end
function FIFO:HasUniqueID(UniqueID)
self:T(self.lid.."HasUniqueID")
if self.stackbyid[UniqueID]~=nil then
return true
else
return false
end
end
function FIFO:GetIDStack()
self:T(self.lid.."GetIDStack")
return self.stackbyid
end
function FIFO:GetIDStackSorted()
self:T(self.lid.."GetIDStackSorted")
local stack=self:GetIDStack()
local idstack={}
for _id,_entry in pairs(stack)do
idstack[#idstack+1]=_id
self:T({"pre",_id})
end
local function sortID(a,b)
return a<b
end
table.sort(idstack)
return idstack
end
function FIFO:GetDataTable()
self:T(self.lid.."GetDataTable")
local datatable={}
for _,_entry in pairs(self.stackbypointer)do
datatable[#datatable+1]=_entry.data
end
return datatable
end
function FIFO:GetSortedDataTable()
self:T(self.lid.."GetSortedDataTable")
local datatable={}
local idtablesorted=self:GetIDStackSorted()
for _,_entry in pairs(idtablesorted)do
datatable[#datatable+1]=self:ReadByID(_entry)
end
return datatable
end
function FIFO:ForEach(IteratorFunction,Arg,Function,FunctionArguments)
self:T(self.lid.."ForEach")
local Set=self:GetPointerStack()or{}
Arg=Arg or{}
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData.data
self:T({Object})
if Function then
if Function(unpack(FunctionArguments or{}),Object)==true then
IteratorFunction(Object,unpack(Arg))
end
else
IteratorFunction(Object,unpack(Arg))
end
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
function FIFO:Flush()
self:T(self.lid.."FiFo Flush")
self:I("FIFO Flushing Stack by Pointer")
for _id,_data in pairs(self.stackbypointer)do
local data=_data
self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("FIFO Flushing Stack by ID")
for _id,_data in pairs(self.stackbyid)do
local data=_data
self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("Counter = "..self.counter)
self:I("Pointer = "..self.pointer)
return self
end
end
do
LIFO={
ClassName="LIFO",
lid="",
version="0.0.5",
counter=0,
pointer=0,
stackbypointer={},
stackbyid={}
}
function LIFO:New()
local self=BASE:Inherit(self,BASE:New())
self.pointer=0
self.counter=0
self.uniquecounter=0
self.stackbypointer={}
self.stackbyid={}
self.lid=string.format("%s (%s) | ","LiFo",self.version)
self:T(self.lid.."Created.")
return self
end
function LIFO:Clear()
self:T(self.lid.."Clear")
self.pointer=0
self.counter=0
self.stackbypointer=nil
self.stackbyid=nil
self.stackbypointer={}
self.stackbyid={}
self.uniquecounter=0
return self
end
function LIFO:Push(Object,UniqueID)
self:T(self.lid.."Push")
self:T({Object,UniqueID})
self.pointer=self.pointer+1
self.counter=self.counter+1
local uniID=UniqueID
if not UniqueID then
self.uniquecounter=self.uniquecounter+1
uniID=self.uniquecounter
end
self.stackbyid[uniID]={pointer=self.pointer,data=Object,uniqueID=uniID}
self.stackbypointer[self.pointer]={pointer=self.pointer,data=Object,uniqueID=uniID}
return self
end
function LIFO:Pull()
self:T(self.lid.."Pull")
if self.counter==0 then return nil end
local object=self.stackbypointer[self.pointer].data
self.stackbypointer[self.pointer]=nil
self.counter=self.counter-1
self.pointer=self.pointer-1
self:Flatten()
return object
end
function LIFO:PullByPointer(Pointer)
self:T(self.lid.."PullByPointer "..tostring(Pointer))
if self.counter==0 then return nil end
local object=self.stackbypointer[Pointer]
self.stackbypointer[Pointer]=nil
if object then self.stackbyid[object.uniqueID]=nil end
self.counter=self.counter-1
self:Flatten()
if object then
return object.data
else
return nil
end
end
function LIFO:ReadByPointer(Pointer)
self:T(self.lid.."ReadByPointer "..tostring(Pointer))
if self.counter==0 or not Pointer or not self.stackbypointer[Pointer]then return nil end
local object=self.stackbypointer[Pointer]
if object then
return object.data
else
return nil
end
end
function LIFO:ReadByID(UniqueID)
self:T(self.lid.."ReadByID "..tostring(UniqueID))
if self.counter==0 or not UniqueID or not self.stackbyid[UniqueID]then return nil end
local object=self.stackbyid[UniqueID]
if object then
return object.data
else
return nil
end
end
function LIFO:PullByID(UniqueID)
self:T(self.lid.."PullByID "..tostring(UniqueID))
if self.counter==0 then return nil end
local object=self.stackbyid[UniqueID]
if object then
return self:PullByPointer(object.pointer)
else
return nil
end
end
function LIFO:Flatten()
self:T(self.lid.."Flatten")
local pointerstack={}
local idstack={}
local counter=0
for _ID,_entry in pairs(self.stackbypointer)do
counter=counter+1
pointerstack[counter]={pointer=counter,data=_entry.data,uniqueID=_entry.uniqueID}
end
for _ID,_entry in pairs(pointerstack)do
idstack[_entry.uniqueID]={pointer=_entry.pointer,data=_entry.data,uniqueID=_entry.uniqueID}
end
self.stackbypointer=nil
self.stackbypointer=pointerstack
self.stackbyid=nil
self.stackbyid=idstack
self.counter=counter
self.pointer=counter
return self
end
function LIFO:IsEmpty()
self:T(self.lid.."IsEmpty")
return self.counter==0 and true or false
end
function LIFO:GetSize()
self:T(self.lid.."GetSize")
return self.counter
end
function LIFO:Count()
self:T(self.lid.."Count")
return self.counter
end
function LIFO:IsNotEmpty()
self:T(self.lid.."IsNotEmpty")
return not self:IsEmpty()
end
function LIFO:GetPointerStack()
self:T(self.lid.."GetPointerStack")
return self.stackbypointer
end
function LIFO:GetIDStack()
self:T(self.lid.."GetIDStack")
return self.stackbyid
end
function LIFO:GetIDStackSorted()
self:T(self.lid.."GetIDStackSorted")
local stack=self:GetIDStack()
local idstack={}
for _id,_entry in pairs(stack)do
idstack[#idstack+1]=_id
self:T({"pre",_id})
end
local function sortID(a,b)
return a<b
end
table.sort(idstack)
return idstack
end
function LIFO:HasUniqueID(UniqueID)
self:T(self.lid.."HasUniqueID")
return self.stackbyid[UniqueID]and true or false
end
function LIFO:Flush()
self:T(self.lid.."FiFo Flush")
self:I("LIFO Flushing Stack by Pointer")
for _id,_data in pairs(self.stackbypointer)do
local data=_data
self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("LIFO Flushing Stack by ID")
for _id,_data in pairs(self.stackbyid)do
local data=_data
self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("Counter = "..self.counter)
self:I("Pointer = "..self.pointer)
return self
end
function LIFO:GetDataTable()
self:T(self.lid.."GetDataTable")
local datatable={}
for _,_entry in pairs(self.stackbypointer)do
datatable[#datatable+1]=_entry.data
end
return datatable
end
function LIFO:GetSortedDataTable()
self:T(self.lid.."GetSortedDataTable")
local datatable={}
local idtablesorted=self:GetIDStackSorted()
for _,_entry in pairs(idtablesorted)do
datatable[#datatable+1]=self:ReadByID(_entry)
end
return datatable
end
function LIFO:ForEach(IteratorFunction,Arg,Function,FunctionArguments)
self:T(self.lid.."ForEach")
local Set=self:GetPointerStack()or{}
Arg=Arg or{}
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData.data
self:T({Object})
if Function then
if Function(unpack(FunctionArguments or{}),Object)==true then
IteratorFunction(Object,unpack(Arg))
end
else
IteratorFunction(Object,unpack(Arg))
end
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
end
SOCKET={
ClassName="SOCKET",
verbose=0,
lid=nil,
}
SOCKET.DataType={
TEXT="moose_text",
BOMBRESULT="moose_bomb_result",
STRAFERESULT="moose_strafe_result",
LSOGRADE="moose_lso_grade",
TTS="moose_text2speech"
}
SOCKET.version="0.3.0"
function SOCKET:New(Port,Host)
local self=BASE:Inherit(self,FSM:New())
package.path=package.path..";.\\LuaSocket\\?.lua;"
package.cpath=package.cpath..";.\\LuaSocket\\?.dll;"
self.socket=require("socket")
self.port=Port or 10042
self.host=Host or"127.0.0.1"
self.json=loadfile("Scripts\\JSON.lua")()
self.UDPSendSocket=self.socket.udp()
self.UDPSendSocket:settimeout(0)
return self
end
function SOCKET:SetPort(Port)
self.port=Port or 10042
end
function SOCKET:SetHost(Host)
self.host=Host or"127.0.0.1"
end
function SOCKET:SendTable(Table)
Table.server_name=BASE.ServerName or"Unknown"
local json=self.json:encode(Table)
self:T("Json table:")
self:T(json)
self.socket.try(self.UDPSendSocket:sendto(json,self.host,self.port))
return self
end
function SOCKET:SendText(Text)
local message={}
message.command=SOCKET.DataType.TEXT
message.text=Text
self:SendTable(message)
return self
end
function SOCKET:SendTextToSpeech(Text,Provider,Voice,Culture,Gender,Volume)
Text=Text or"Hello World!"
local message={}
message.command=SOCKET.DataType.TTS
message.text=Text
message.provider=Provider
message.voice=Voice
message.culture=Culture
message.gender=Gender
message.volume=Volume
self:SendTable(message)
return self
end
local _TraceOnOff=false
local _TraceLevel=1
local _TraceAll=false
local _TraceClass={}
local _TraceClassMethod={}
local _ClassID=0
BASE={
ClassName="BASE",
ClassID=0,
Events={},
States={},
Debug=debug,
Scheduler=nil,
Properties={},
}
BASE.__={}
BASE._={
Schedules={},
}
function BASE:New()
local self=UTILS.DeepCopy(self)
_ClassID=_ClassID+1
self.ClassID=_ClassID
return self
end
function BASE:Inherit(Child,Parent)
local Child=UTILS.DeepCopy(Child)
if Child~=nil then
if rawget(Child,"__")then
setmetatable(Child,{__index=Child.__})
setmetatable(Child.__,{__index=Parent})
else
setmetatable(Child,{__index=Parent})
end
end
return Child
end
local function getParent(Child)
local Parent=nil
if Child.ClassName=='BASE'then
Parent=nil
else
if rawget(Child,"__")then
Parent=getmetatable(Child.__).__index
else
Parent=getmetatable(Child).__index
end
end
return Parent
end
function BASE:GetParent(Child,FromClass)
local Parent
if Child.ClassName=='BASE'then
Parent=nil
else
if FromClass then
while(Child.ClassName~="BASE"and Child.ClassName~=FromClass.ClassName)do
Child=getParent(Child)
end
end
if Child.ClassName=='BASE'then
Parent=nil
else
Parent=getParent(Child)
end
end
return Parent
end
function BASE:IsInstanceOf(ClassName)
if type(ClassName)~='string'then
if type(ClassName)=='table'and ClassName.ClassName~=nil then
ClassName=ClassName.ClassName
else
local err_str='className parameter should be a string; parameter received: '..type(ClassName)
self:E(err_str)
return false
end
end
ClassName=string.upper(ClassName)
if string.upper(self.ClassName)==ClassName then
return true
end
local Parent=getParent(self)
while Parent do
if string.upper(Parent.ClassName)==ClassName then
return true
end
Parent=getParent(Parent)
end
return false
end
function BASE:GetClassNameAndID()
return string.format('%s#%09d',self.ClassName,self.ClassID)
end
function BASE:GetClassName()
return self.ClassName
end
function BASE:GetClassID()
return self.ClassID
end
do
function BASE:EventDispatcher()
return _EVENTDISPATCHER
end
function BASE:GetEventPriority()
return self._.EventPriority or 5
end
function BASE:SetEventPriority(EventPriority)
self._.EventPriority=EventPriority
end
function BASE:EventRemoveAll()
self:EventDispatcher():RemoveAll(self)
return self
end
function BASE:HandleEvent(EventID,EventFunction)
self:EventDispatcher():OnEventGeneric(EventFunction,self,EventID)
return self
end
function BASE:UnHandleEvent(EventID)
self:EventDispatcher():RemoveEvent(self,EventID)
return self
end
end
function BASE:CreateEventBirth(EventTime,Initiator,IniUnitName,place,subplace)
self:F({EventTime,Initiator,IniUnitName,place,subplace})
local Event={
id=world.event.S_EVENT_BIRTH,
time=EventTime,
initiator=Initiator,
IniUnitName=IniUnitName,
place=place,
subplace=subplace,
}
world.onEvent(Event)
end
function BASE:CreateEventCrash(EventTime,Initiator,IniObjectCategory)
self:F({EventTime,Initiator})
local Event={
id=world.event.S_EVENT_CRASH,
time=EventTime,
initiator=Initiator,
IniObjectCategory=IniObjectCategory,
}
world.onEvent(Event)
end
function BASE:CreateEventUnitLost(EventTime,Initiator)
self:F({EventTime,Initiator})
local Event={
id=world.event.S_EVENT_UNIT_LOST,
time=EventTime,
initiator=Initiator,
}
world.onEvent(Event)
end
function BASE:CreateEventDead(EventTime,Initiator,IniObjectCategory)
self:F({EventTime,Initiator,IniObjectCategory})
local Event={
id=world.event.S_EVENT_DEAD,
time=EventTime,
initiator=Initiator,
IniObjectCategory=IniObjectCategory,
}
world.onEvent(Event)
end
function BASE:CreateEventRemoveUnit(EventTime,Initiator)
self:F({EventTime,Initiator})
local Event={
id=EVENTS.RemoveUnit,
time=EventTime,
initiator=Initiator,
}
world.onEvent(Event)
end
function BASE:CreateEventTakeoff(EventTime,Initiator)
self:F({EventTime,Initiator})
local Event={
id=world.event.S_EVENT_TAKEOFF,
time=EventTime,
initiator=Initiator,
}
world.onEvent(Event)
end
function BASE:CreateEventPlayerEnterAircraft(PlayerUnit)
self:F({PlayerUnit})
local Event={
id=EVENTS.PlayerEnterAircraft,
time=timer.getTime(),
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
if EventObject.EventEnabled then
if event.id==EventObject.Event then
if self==EventObject.Self then
if event.initiator and event.initiator:isExist()then
event.IniUnitName=event.initiator:getName()
end
if event.target and event.target:isExist()then
event.TgtUnitName=event.target:getName()
end
end
end
end
end
end
end
do
function BASE:ScheduleOnce(Start,SchedulerFunction,...)
local ObjectName="-"
ObjectName=self.ClassName..self.ClassID
self:F3({"ScheduleOnce: ",ObjectName,Start})
if not self.Scheduler then
self.Scheduler=SCHEDULER:New(self)
end
local ScheduleID=self.Scheduler:Schedule(nil,SchedulerFunction,{...},Start)
self._.Schedules[#self._.Schedules+1]=ScheduleID
return self._.Schedules[#self._.Schedules]
end
function BASE:ScheduleRepeat(Start,Repeat,RandomizeFactor,Stop,SchedulerFunction,...)
self:F2({Start})
self:T3({...})
local ObjectName="-"
ObjectName=self.ClassName..self.ClassID
self:F3({"ScheduleRepeat: ",ObjectName,Start,Repeat,RandomizeFactor,Stop})
if not self.Scheduler then
self.Scheduler=SCHEDULER:New(self)
end
local ScheduleID=self.Scheduler:Schedule(
nil,
SchedulerFunction,
{...},
Start,
Repeat,
RandomizeFactor,
Stop,
4
)
self._.Schedules[#self._.Schedules+1]=ScheduleID
return self._.Schedules[#self._.Schedules]
end
function BASE:ScheduleStop(SchedulerID)
self:F3({"ScheduleStop:"})
if self.Scheduler then
_SCHEDULEDISPATCHER:Stop(self.Scheduler,SchedulerID)
end
end
end
function BASE:SetState(Object,Key,Value)
local ClassNameAndID=Object:GetClassNameAndID()
self.States[ClassNameAndID]=self.States[ClassNameAndID]or{}
self.States[ClassNameAndID][Key]=Value
return self.States[ClassNameAndID][Key]
end
function BASE:GetState(Object,Key)
local ClassNameAndID=Object:GetClassNameAndID()
if self.States[ClassNameAndID]then
local Value=self.States[ClassNameAndID][Key]or false
return Value
end
return nil
end
function BASE:ClearState(Object,StateName)
local ClassNameAndID=Object:GetClassNameAndID()
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
function BASE:TraceOff()
self:TraceOnOff(false)
end
function BASE:TraceOnOff(TraceOnOff)
if TraceOnOff==false then
self:I("Tracing in MOOSE is OFF")
_TraceOnOff=false
else
self:I("Tracing in MOOSE is ON")
_TraceOnOff=true
end
end
function BASE:IsTrace()
if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
return true
else
return false
end
end
function BASE:TraceLevel(Level)
_TraceLevel=Level or 1
self:I("Tracing level ".._TraceLevel)
end
function BASE:TraceAll(TraceAll)
if TraceAll==false then
_TraceAll=false
else
_TraceAll=true
end
if _TraceAll then
self:I("Tracing all methods in MOOSE ")
else
self:I("Switched off tracing all methods in MOOSE")
end
end
function BASE:TraceClass(Class)
_TraceClass[Class]=true
_TraceClassMethod[Class]={}
self:I("Tracing class "..Class)
end
function BASE:TraceClassMethod(Class,Method)
if not _TraceClassMethod[Class]then
_TraceClassMethod[Class]={}
_TraceClassMethod[Class].Method={}
end
_TraceClassMethod[Class].Method[Method]=true
self:I("Tracing method "..Method.." of class "..Class)
end
function BASE:_Serialize(Arguments)
local text=UTILS.PrintTableToLog({Arguments},0,true)
text=string.gsub(text,"(\n+)","")
text=string.gsub(text,"%(%(","%(")
text=string.gsub(text,"%)%)","%)")
text=string.gsub(text,"(%s+)"," ")
return text
end
function BASE:_F(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then
local LineCurrent=0
if DebugInfoCurrent.currentline then
LineCurrent=DebugInfoCurrent.currentline
end
local LineFrom=0
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"F",self.ClassName,self.ClassID,Function,BASE:_Serialize(Arguments)))
end
end
end
function BASE:F(Arguments)
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
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:F2(Arguments)
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
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:F3(Arguments)
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
self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:_T(Arguments,DebugInfoCurrentParam,DebugInfoFromParam)
if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then
local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then
local LineCurrent=0
if DebugInfoCurrent.currentline then
LineCurrent=DebugInfoCurrent.currentline
end
local LineFrom=0
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s",LineCurrent,LineFrom,"T",self.ClassName,self.ClassID,BASE:_Serialize(Arguments)))
end
end
end
function BASE:T(Arguments)
if BASE.Debug and _TraceOnOff==true then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=1 then
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:T2(Arguments)
if BASE.Debug and _TraceOnOff==true and _TraceLevel>=2 then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=2 then
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:T3(Arguments)
if BASE.Debug and _TraceOnOff==true and _TraceLevel>=3 then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
if _TraceLevel>=3 then
self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom)
end
end
end
function BASE:E(Arguments)
if BASE.Debug then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
local LineCurrent=DebugInfoCurrent.currentline
local LineFrom=-1
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"E",self.ClassName,self.ClassID,Function,UTILS.BasicSerialize(Arguments)))
else
env.info(string.format("%1s:%30s%05d(%s)","E",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments)))
end
end
function BASE:I(Arguments)
if BASE.Debug then
local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl")
local DebugInfoFrom=BASE.Debug.getinfo(3,"l")
local Function="function"
if DebugInfoCurrent.name then
Function=DebugInfoCurrent.name
end
local LineCurrent=DebugInfoCurrent.currentline
local LineFrom=-1
if DebugInfoFrom then
LineFrom=DebugInfoFrom.currentline
end
env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"I",self.ClassName,self.ClassID,Function,UTILS.BasicSerialize(Arguments)))
else
env.info(string.format("%1s:%30s%05d(%s)","I",self.ClassName,self.ClassID,UTILS.BasicSerialize(Arguments)))
end
end
ASTAR={
ClassName="ASTAR",
Debug=nil,
lid=nil,
nodes={},
counter=1,
Nnodes=0,
ncost=0,
ncostcache=0,
nvalid=0,
nvalidcache=0,
}
ASTAR.INF=1/0
ASTAR.version="0.4.0"
function ASTAR:New()
local self=BASE:Inherit(self,BASE:New())
self.lid="ASTAR | "
return self
end
function ASTAR:SetStartCoordinate(Coordinate)
self.startCoord=Coordinate
return self
end
function ASTAR:SetEndCoordinate(Coordinate)
self.endCoord=Coordinate
return self
end
function ASTAR:GetNodeFromCoordinate(Coordinate)
local node={}
node.coordinate=Coordinate
node.surfacetype=Coordinate:GetSurfaceType()
node.id=self.counter
node.valid={}
node.cost={}
self.counter=self.counter+1
return node
end
function ASTAR:AddNode(Node)
self.nodes[Node.id]=Node
self.Nnodes=self.Nnodes+1
return self
end
function ASTAR:AddNodeFromCoordinate(Coordinate)
local node=self:GetNodeFromCoordinate(Coordinate)
self:AddNode(node)
return node
end
function ASTAR:CheckValidSurfaceType(Node,SurfaceTypes)
if SurfaceTypes then
if type(SurfaceTypes)~="table"then
SurfaceTypes={SurfaceTypes}
end
for _,surface in pairs(SurfaceTypes)do
if surface==Node.surfacetype then
return true
end
end
return false
else
return true
end
end
function ASTAR:SetValidNeighbourFunction(NeighbourFunction,...)
self.ValidNeighbourFunc=NeighbourFunction
self.ValidNeighbourArg={}
if arg then
self.ValidNeighbourArg=arg
end
return self
end
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
self:SetValidNeighbourFunction(ASTAR.LoS,CorridorWidth)
return self
end
function ASTAR:SetValidNeighbourDistance(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.DistMax,MaxDistance)
return self
end
function ASTAR:SetValidNeighbourRoad(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.Road,MaxDistance)
return self
end
function ASTAR:SetCostFunction(CostFunction,...)
self.CostFunc=CostFunction
self.CostArg={}
if arg then
self.CostArg=arg
end
return self
end
function ASTAR:SetCostDist2D()
self:SetCostFunction(ASTAR.Dist2D)
return self
end
function ASTAR:SetCostDist3D()
self:SetCostFunction(ASTAR.Dist3D)
return self
end
function ASTAR:SetCostRoad()
self:SetCostFunction(ASTAR)
return self
end
function ASTAR:CreateGrid(ValidSurfaceTypes,BoxHY,SpaceX,deltaX,deltaY,MarkGrid)
local Dz=SpaceX or 10000
local Dx=BoxHY and BoxHY/2 or 20000
local dz=deltaX or 2000
local dx=deltaY or dz
local angle=self.startCoord:HeadingTo(self.endCoord)
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
local co=COORDINATE:New(0,0,0)
local do1=co:Get2DDistance(self.startCoord)
local ho1=co:HeadingTo(self.startCoord)
local xmin=-Dx
local zmin=-Dz
local nz=dist/dz+1
local nx=2*Dx/dx+1
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes",nx,nz,nx*nz)
self:T(self.lid..text)
for i=1,nx do
local x=xmin+dx*(i-1)
for j=1,nz do
local z=zmin+dz*(j-1)
local vec3=UTILS.Rotate2D({x=x,y=0,z=z},angle)
local c=COORDINATE:New(vec3.z,vec3.y,vec3.x):Translate(do1,ho1,true)
local node=self:GetNodeFromCoordinate(c)
if self:CheckValidSurfaceType(node,ValidSurfaceTypes)then
if MarkGrid then
c:MarkToAll(string.format("i=%d, j=%d surface=%d",i,j,node.surfacetype))
end
self:AddNode(node)
end
end
end
local text=string.format("Done building grid!")
self:T2(self.lid..text)
return self
end
function ASTAR.LoS(nodeA,nodeB,corridor)
local offset=1
local dx=corridor and corridor/2 or nil
local dy=dx
local cA=nodeA.coordinate:GetVec3()
local cB=nodeB.coordinate:GetVec3()
cA.y=offset
cB.y=offset
local los=land.isVisible(cA,cB)
if los and corridor then
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
local Ap=UTILS.VecTranslate(cA,dx,heading+90)
local Bp=UTILS.VecTranslate(cB,dx,heading+90)
los=land.isVisible(Ap,Bp)
if los then
local Am=UTILS.VecTranslate(cA,dx,heading-90)
local Bm=UTILS.VecTranslate(cB,dx,heading-90)
los=land.isVisible(Am,Bm)
end
end
return los
end
function ASTAR.Road(nodeA,nodeB)
local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z)
if path then
return true
else
return false
end
end
function ASTAR.DistMax(nodeA,nodeB,distmax)
distmax=distmax or 2000
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
return dist<=distmax
end
function ASTAR.Dist2D(nodeA,nodeB)
local dist=nodeA.coordinate:Get2DDistance(nodeB)
return dist
end
function ASTAR.Dist3D(nodeA,nodeB)
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
return dist
end
function ASTAR.DistRoad(nodeA,nodeB)
local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z)
if path then
local dist=0
for i=2,#path do
local b=path[i]
local a=path[i-1]
dist=dist+UTILS.VecDist2D(a,b)
end
return dist
end
return math.huge
end
function ASTAR:FindClosestNode(Coordinate)
local distMin=math.huge
local closeNode=nil
for _,_node in pairs(self.nodes)do
local node=_node
local dist=node.coordinate:Get2DDistance(Coordinate)
if dist<distMin then
distMin=dist
closeNode=node
end
end
return closeNode,distMin
end
function ASTAR:FindStartNode()
local node,dist=self:FindClosestNode(self.startCoord)
self.startNode=node
if dist>1000 then
self:T(self.lid.."Adding start node to node grid!")
self:AddNode(node)
end
return self
end
function ASTAR:FindEndNode()
local node,dist=self:FindClosestNode(self.endCoord)
self.endNode=node
if dist>1000 then
self:T(self.lid.."Adding end node to node grid!")
self:AddNode(node)
end
return self
end
function ASTAR:GetPath(ExcludeStartNode,ExcludeEndNode)
self:FindStartNode()
self:FindEndNode()
local nodes=self.nodes
local start=self.startNode
local goal=self.endNode
local openset={}
local closedset={}
local came_from={}
local g_score={}
local f_score={}
openset[start.id]=true
local Nopen=1
g_score[start.id]=0
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start,goal)
local T0=timer.getAbsTime()
local text=string.format("Starting A* pathfinding with %d Nodes",self.Nnodes)
self:T(self.lid..text)
local Tstart=UTILS.GetOSTime()
while Nopen>0 do
local current=self:_LowestFscore(openset,f_score)
if current.id==goal.id then
local path=self:_UnwindPath({},came_from,goal)
if not ExcludeEndNode then
table.insert(path,goal)
end
if ExcludeStartNode then
table.remove(path,1)
end
local Tstop=UTILS.GetOSTime()
local dT=nil
if Tstart and Tstop then
dT=Tstop-Tstart
end
local text=string.format("Found path with %d nodes (%d total)",#path,self.Nnodes)
if dT then
text=text..string.format(", OS Time %.6f sec",dT)
end
text=text..string.format(", Nvalid=%d [%d cached]",self.nvalid,self.nvalidcache)
text=text..string.format(", Ncost=%d [%d cached]",self.ncost,self.ncostcache)
self:T(self.lid..text)
return path
end
openset[current.id]=nil
Nopen=Nopen-1
closedset[current.id]=true
local neighbors=self:_NeighbourNodes(current,nodes)
for _,neighbor in pairs(neighbors)do
if self:_NotIn(closedset,neighbor.id)then
local tentative_g_score=g_score[current.id]+self:_DistNodes(current,neighbor)
if self:_NotIn(openset,neighbor.id)or tentative_g_score<g_score[neighbor.id]then
came_from[neighbor]=current
g_score[neighbor.id]=tentative_g_score
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor,goal)
if self:_NotIn(openset,neighbor.id)then
openset[neighbor.id]=true
Nopen=Nopen+1
end
end
end
end
end
local text=string.format("WARNING: Could NOT find valid path!")
self:E(self.lid..text)
MESSAGE:New(text,60,"ASTAR"):ToAllIf(self.Debug)
return nil
end
function ASTAR:_HeuristicCost(nodeA,nodeB)
self.ncost=self.ncost+1
local cost=nodeA.cost[nodeB.id]
if cost~=nil then
self.ncostcache=self.ncostcache+1
return cost
end
local cost=nil
if self.CostFunc then
cost=self.CostFunc(nodeA,nodeB,unpack(self.CostArg))
else
cost=self:_DistNodes(nodeA,nodeB)
end
nodeA.cost[nodeB.id]=cost
nodeB.cost[nodeA.id]=cost
return cost
end
function ASTAR:_IsValidNeighbour(node,neighbor)
self.nvalid=self.nvalid+1
local valid=node.valid[neighbor.id]
if valid~=nil then
self.nvalidcache=self.nvalidcache+1
return valid
end
local valid=nil
if self.ValidNeighbourFunc then
valid=self.ValidNeighbourFunc(node,neighbor,unpack(self.ValidNeighbourArg))
else
valid=true
end
node.valid[neighbor.id]=valid
neighbor.valid[node.id]=valid
return valid
end
function ASTAR:_DistNodes(nodeA,nodeB)
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
end
function ASTAR:_LowestFscore(set,f_score)
local lowest,bestNode=ASTAR.INF,nil
for nid,node in pairs(set)do
local score=f_score[nid]
if score<lowest then
lowest,bestNode=score,nid
end
end
return self.nodes[bestNode]
end
function ASTAR:_NeighbourNodes(theNode,nodes)
local neighbors={}
for _,node in pairs(nodes)do
if theNode.id~=node.id then
local isvalid=self:_IsValidNeighbour(theNode,node)
if isvalid then
table.insert(neighbors,node)
end
end
end
return neighbors
end
function ASTAR:_NotIn(set,theNode)
return set[theNode]==nil
end
function ASTAR:_UnwindPath(flat_path,map,current_node)
if map[current_node]then
table.insert(flat_path,1,map[current_node])
return self:_UnwindPath(flat_path,map,map[current_node])
else
return flat_path
end
end
BEACON={
ClassName="BEACON",
Positionable=nil,
name=nil,
UniqueName=0,
}
BEACON.Type={
NULL=0,
VOR=1,
DME=2,
VOR_DME=3,
TACAN=4,
VORTAC=5,
RSBN=128,
BROADCAST_STATION=1024,
HOMER=8,
AIRPORT_HOMER=4104,
AIRPORT_HOMER_WITH_MARKER=4136,
ILS_FAR_HOMER=16408,
ILS_NEAR_HOMER=16424,
ILS_LOCALIZER=16640,
ILS_GLIDESLOPE=16896,
PRMG_LOCALIZER=33024,
PRMG_GLIDESLOPE=33280,
ICLS=131584,
ICLS_LOCALIZER=131328,
ICLS_GLIDESLOPE=131584,
NAUTICAL_HOMER=65536,
}
BEACON.System={
PAR_10=1,
RSBN_5=2,
TACAN=3,
TACAN_TANKER_X=4,
TACAN_TANKER_Y=5,
VOR=6,
ILS_LOCALIZER=7,
ILS_GLIDESLOPE=8,
PRMG_LOCALIZER=9,
PRMG_GLIDESLOPE=10,
BROADCAST_STATION=11,
VORTAC=12,
TACAN_AA_MODE_X=13,
TACAN_AA_MODE_Y=14,
VORDME=15,
ICLS_LOCALIZER=16,
ICLS_GLIDESLOPE=17,
}
function BEACON:New(Positionable)
local self=BASE:Inherit(self,BASE:New())
self:F(Positionable)
if Positionable:GetPointVec2()then
self.Positionable=Positionable
self.name=Positionable:GetName()
self:I(string.format("New BEACON %s",tostring(self.name)))
return self
end
self:E({"The passed positionable is invalid, no BEACON created",Positionable})
return nil
end
function BEACON:ActivateTACAN(Channel,Mode,Message,Bearing,Duration)
self:T({channel=Channel,mode=Mode,callsign=Message,bearing=Bearing,duration=Duration})
Mode=Mode or"Y"
local Frequency=UTILS.TACANToFrequency(Channel,Mode)
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
return self
end
local Type=BEACON.Type.TACAN
local System=BEACON.System.TACAN
local AA=self.Positionable:IsAir()
if AA then
System=5
if Mode=="X"then
System=BEACON.System.TACAN_TANKER_X
else
System=BEACON.System.TACAN_TANKER_Y
end
end
local UnitID=self.Positionable:GetID()
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!",tostring(self.name),Channel,Mode,Message,tostring(Bearing),tostring(Duration))})
self.Positionable:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,Mode,AA,Message,Bearing)
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
function BEACON:ActivateICLS(Channel,Callsign,Duration)
self:F({Channel=Channel,Callsign=Callsign,Duration=Duration})
local UnitID=self.Positionable:GetID()
self:T2({"ICLS BEACON started!"})
self.Positionable:CommandActivateICLS(Channel,UnitID,Callsign)
if Duration then
self.Positionable:DeactivateBeacon(Duration)
end
return self
end
function BEACON:ActivateLink4(Frequency,Morse,Duration)
self:F({Frequency=Frequency,Morse=Morse,Duration=Duration})
local UnitID=self.Positionable:GetID()
self:T2({"LINK4 BEACON started!"})
self.Positionable:CommandActivateLink4(Frequency,UnitID,Morse)
if Duration then
self.Positionable:CommandDeactivateLink4(Duration)
end
return self
end
function BEACON:AATACAN(TACANChannel,Message,Bearing,BeaconDuration)
self:F({TACANChannel,Message,Bearing,BeaconDuration})
self:E("This method is DEPRECATED! Please use ActivateTACAN() instead.")
local IsValid=true
if not self.Positionable:IsAir()then
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting",self.Positionable})
IsValid=false
end
local Frequency=self:_TACANToFrequency(TACANChannel,"Y")
if not Frequency then
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
IsValid=false
end
local System
if Bearing then
System=BEACON.System.TACAN_TANKER_Y
else
System=BEACON.System.TACAN_AA_MODE_Y
end
if IsValid then
self:T2({"AA TACAN BEACON started !"})
self.Positionable:SetCommand({
id="ActivateBeacon",
params={
type=BEACON.Type.TACAN,
system=System,
callsign=Message,
AA=true,
frequency=Frequency,
bearing=Bearing,
modeChannel="Y",
}
})
if BeaconDuration then
SCHEDULER:New(nil,
function()
self:StopAATACAN()
end,{},BeaconDuration)
end
end
return self
end
function BEACON:StopAATACAN()
self:F()
if not self.Positionable then
self:E({"Start the beacon first before stoping it !"})
else
self.Positionable:SetCommand({
id='DeactivateBeacon',
params={
}
})
end
end
function BEACON:RadioBeacon(FileName,Frequency,Modulation,Power,BeaconDuration)
self:F({FileName,Frequency,Modulation,Power,BeaconDuration})
local IsValid=false
Modulation=Modulation or radio.modulation.AM
if type(FileName)=="string"then
if FileName:find(".ogg")or FileName:find(".wav")then
if not FileName:find("l10n/DEFAULT/")then
FileName="l10n/DEFAULT/"..FileName
end
IsValid=true
end
end
if not IsValid then
self:E({"File name invalid. Maybe something wrong with the extension? ",FileName})
end
if type(Frequency)~="number"and IsValid then
self:E({"Frequency invalid. ",Frequency})
IsValid=false
end
Frequency=Frequency*1000000
if Modulation~=radio.modulation.AM and Modulation~=radio.modulation.FM and IsValid then
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.",Modulation})
IsValid=false
end
if type(Power)~="number"and IsValid then
self:E({"Power is invalid. ",Power})
IsValid=false
end
Power=math.floor(math.abs(Power))
if IsValid then
self:T2({"Activating Beacon on ",Frequency,Modulation})
BEACON.UniqueName=BEACON.UniqueName+1
self.BeaconName="MooseBeacon"..tostring(BEACON.UniqueName)
trigger.action.radioTransmission(FileName,self.Positionable:GetPositionVec3(),Modulation,true,Frequency,Power,self.BeaconName)
if BeaconDuration then
SCHEDULER:New(nil,
function()
self:StopRadioBeacon()
end,{},BeaconDuration)
end
end
return self
end
function BEACON:StopRadioBeacon()
self:F()
trigger.action.stopRadioTransmission(self.BeaconName)
return self
end
function BEACON:_TACANToFrequency(TACANChannel,TACANMode)
self:F3({TACANChannel,TACANMode})
if type(TACANChannel)~="number"then
if TACANMode~="X"and TACANMode~="Y"then
return nil
end
end
local A=1151
local B=64
if TACANChannel<64 then
B=1
end
if TACANMode=='Y'then
A=1025
if TACANChannel<64 then
A=1088
end
else
if TACANChannel<64 then
A=962
end
end
return(A+TACANChannel-B)*1000000
end
CONDITION={
ClassName="CONDITION",
lid=nil,
functionsGen={},
functionsAny={},
functionsAll={},
functionCounter=0,
defaultPersist=false,
}
CONDITION.version="0.3.0"
function CONDITION:New(Name)
local self=BASE:Inherit(self,BASE:New())
self.name=Name or"Condition X"
self:SetNoneResult(false)
self.lid=string.format("%s | ",self.name)
return self
end
function CONDITION:SetAny(Any)
self.isAny=Any
return self
end
function CONDITION:SetNegateResult(Negate)
self.negateResult=Negate
return self
end
function CONDITION:SetNoneResult(ReturnValue)
if not ReturnValue then
self.noneResult=false
else
self.noneResult=true
end
return self
end
function CONDITION:SetDefaultPersistence(IsPersistent)
self.defaultPersist=IsPersistent
return self
end
function CONDITION:AddFunction(Function,...)
local condition=self:_CreateCondition(0,Function,...)
table.insert(self.functionsGen,condition)
return condition
end
function CONDITION:AddFunctionAny(Function,...)
local condition=self:_CreateCondition(1,Function,...)
table.insert(self.functionsAny,condition)
return condition
end
function CONDITION:AddFunctionAll(Function,...)
local condition=self:_CreateCondition(2,Function,...)
table.insert(self.functionsAll,condition)
return condition
end
function CONDITION:RemoveFunction(ConditionFunction)
if ConditionFunction then
local data=nil
if ConditionFunction.type==0 then
data=self.functionsGen
elseif ConditionFunction.type==1 then
data=self.functionsAny
elseif ConditionFunction.type==2 then
data=self.functionsAll
end
if data then
for i=#data,1,-1 do
local cf=data[i]
if cf.uid==ConditionFunction.uid then
self:T(self.lid..string.format("Removed ConditionFunction UID=%d",cf.uid))
table.remove(data,i)
return self
end
end
end
end
return self
end
function CONDITION:RemoveNonPersistant()
for i=#self.functionsGen,1,-1 do
local cf=self.functionsGen[i]
if not cf.persistence then
table.remove(self.functionsGen,i)
end
end
for i=#self.functionsAll,1,-1 do
local cf=self.functionsAll[i]
if not cf.persistence then
table.remove(self.functionsAll,i)
end
end
for i=#self.functionsAny,1,-1 do
local cf=self.functionsAny[i]
if not cf.persistence then
table.remove(self.functionsAny,i)
end
end
return self
end
function CONDITION:Evaluate(AnyTrue)
if#self.functionsAll+#self.functionsAny+#self.functionsAll==0 then
return self.noneResult
end
local evalAny=self.isAny
if AnyTrue~=nil then
evalAny=AnyTrue
end
local isGen=nil
if evalAny then
isGen=self:_EvalConditionsAny(self.functionsGen)
else
isGen=self:_EvalConditionsAll(self.functionsGen)
end
local isAny=self:_EvalConditionsAny(self.functionsAny)
local isAll=self:_EvalConditionsAll(self.functionsAll)
local result=isGen and isAny and isAll
if self.negateResult then
result=not result
end
self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s",tostring(isGen),tostring(isAny),tostring(isAll),tostring(self.negateResult),tostring(result)))
return result
end
function CONDITION:_EvalConditionsAll(functions)
local gotone=false
for _,_condition in pairs(functions or{})do
local condition=_condition
gotone=true
local istrue=condition.func(unpack(condition.arg))
if not istrue then
return false
end
end
return true
end
function CONDITION:_EvalConditionsAny(functions)
local gotone=false
for _,_condition in pairs(functions or{})do
local condition=_condition
gotone=true
local istrue=condition.func(unpack(condition.arg))
if istrue then
return true
end
end
if gotone then
return false
else
return true
end
end
function CONDITION:_CreateCondition(Ftype,Function,...)
self.functionCounter=self.functionCounter+1
local condition={}
condition.uid=self.functionCounter
condition.type=Ftype or 0
condition.persistence=self.defaultPersist
condition.func=Function
condition.arg={}
if arg then
condition.arg=arg
end
return condition
end
function CONDITION.IsTimeGreater(Time,Absolute)
local Tnow=nil
if Absolute then
Tnow=timer.getAbsTime()
else
Tnow=timer.getTime()
end
if Tnow>Time then
return true
else
return false
end
return nil
end
function CONDITION.IsRandomSuccess(Probability)
Probability=Probability or 50
math.random()
math.random()
math.random()
local N=math.random()*100
if N<Probability then
return true
else
return false
end
end
function CONDITION.ReturnTrue()
return true
end
function CONDITION.ReturnFalse()
return false
end
do
USERFLAG={
ClassName="USERFLAG",
UserFlagName=nil,
}
function USERFLAG:New(UserFlagName)
local self=BASE:Inherit(self,BASE:New())
self.UserFlagName=UserFlagName
return self
end
function USERFLAG:GetName()
return self.UserFlagName
end
function USERFLAG:Set(Number,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,USERFLAG.Set,self,Number)
else
trigger.action.setUserFlag(self.UserFlagName,Number)
end
return self
end
function USERFLAG:Get()
return trigger.misc.getUserFlag(self.UserFlagName)
end
function USERFLAG:Is(Number)
return trigger.misc.getUserFlag(self.UserFlagName)==Number
end
end
REPORT={
ClassName="REPORT",
Title="",
}
function REPORT:New(Title)
local self=BASE:Inherit(self,BASE:New())
self.Report={}
self:SetTitle(Title or"")
self:SetIndent(3)
return self
end
function REPORT:HasText()
return#self.Report>0
end
function REPORT:SetIndent(Indent)
self.Indent=Indent
return self
end
function REPORT:Add(Text)
self.Report[#self.Report+1]=Text
return self
end
function REPORT:AddIndent(Text,Separator)
self.Report[#self.Report+1]=((Separator and Separator..string.rep(" ",self.Indent-1))or string.rep(" ",self.Indent))..Text:gsub("\n","\n"..string.rep(" ",self.Indent))
return self
end
function REPORT:Text(Delimiter)
Delimiter=Delimiter or"\n"
local ReportText=(self.Title~=""and self.Title..Delimiter or self.Title)..table.concat(self.Report,Delimiter)or""
return ReportText
end
function REPORT:SetTitle(Title)
self.Title=Title
return self
end
function REPORT:GetCount()
return#self.Report
end
SCHEDULER={
ClassName="SCHEDULER",
Schedules={},
MasterObject=nil,
ShowTrace=nil,
}
function SCHEDULER:New(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop)
local self=BASE:Inherit(self,BASE:New())
self:F2({Start,Repeat,RandomizeFactor,Stop})
local ScheduleID=nil
self.MasterObject=MasterObject
self.ShowTrace=false
if SchedulerFunction then
ScheduleID=self:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,3)
end
return self,ScheduleID
end
function SCHEDULER:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,TraceLevel,Fsm)
self:F2({Start,Repeat,RandomizeFactor,Stop})
self:T3({SchedulerArguments})
local ObjectName="-"
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
ObjectName=MasterObject.ClassName..MasterObject.ClassID
end
self:F3({"Schedule :",ObjectName,tostring(MasterObject),Start,Repeat,RandomizeFactor,Stop})
self.MasterObject=MasterObject
local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule(self,
SchedulerFunction,
SchedulerArguments,
Start,
Repeat,
RandomizeFactor,
Stop,
TraceLevel or 3,
Fsm
)
self.Schedules[#self.Schedules+1]=ScheduleID
return ScheduleID
end
function SCHEDULER:Start(ScheduleID)
self:F3({ScheduleID})
self:T(string.format("Starting scheduler ID=%s",tostring(ScheduleID)))
_SCHEDULEDISPATCHER:Start(self,ScheduleID)
end
function SCHEDULER:Stop(ScheduleID)
self:F3({ScheduleID})
self:T(string.format("Stopping scheduler ID=%s",tostring(ScheduleID)))
_SCHEDULEDISPATCHER:Stop(self,ScheduleID)
end
function SCHEDULER:Remove(ScheduleID)
self:F3({ScheduleID})
self:T(string.format("Removing scheduler ID=%s",tostring(ScheduleID)))
_SCHEDULEDISPATCHER:RemoveSchedule(self,ScheduleID)
end
function SCHEDULER:Clear()
self:F3()
self:T(string.format("Clearing scheduler"))
_SCHEDULEDISPATCHER:Clear(self)
end
function SCHEDULER:ShowTrace()
_SCHEDULEDISPATCHER:ShowTrace(self)
end
function SCHEDULER:NoTrace()
_SCHEDULEDISPATCHER:NoTrace(self)
end
SCHEDULEDISPATCHER={
ClassName="SCHEDULEDISPATCHER",
CallID=0,
PersistentSchedulers={},
ObjectSchedulers={},
Schedule=nil,
}
function SCHEDULEDISPATCHER:New()
local self=BASE:Inherit(self,BASE:New())
self:F3()
return self
end
function SCHEDULEDISPATCHER:AddSchedule(Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm)
self:F2({Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm})
self.CallID=self.CallID+1
local CallID=self.CallID.."#"..(Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID()or"")or""
self:T2(string.format("Adding schedule #%d CallID=%s",self.CallID,CallID))
self.PersistentSchedulers=self.PersistentSchedulers or{}
self.ObjectSchedulers=self.ObjectSchedulers or setmetatable({},{__mode="v"})
if Scheduler.MasterObject then
self.ObjectSchedulers[CallID]=Scheduler
self:F3({CallID=CallID,ObjectScheduler=tostring(self.ObjectSchedulers[CallID]),MasterObject=tostring(Scheduler.MasterObject)})
else
self.PersistentSchedulers[CallID]=Scheduler
self:F3({CallID=CallID,PersistentScheduler=self.PersistentSchedulers[CallID]})
end
self.Schedule=self.Schedule or setmetatable({},{__mode="k"})
self.Schedule[Scheduler]=self.Schedule[Scheduler]or{}
self.Schedule[Scheduler][CallID]={}
self.Schedule[Scheduler][CallID].Function=ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments=ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime=timer.getTime()+(Start or 0)
self.Schedule[Scheduler][CallID].Start=Start+0.001
self.Schedule[Scheduler][CallID].Repeat=Repeat or 0
self.Schedule[Scheduler][CallID].Randomize=Randomize or 0
self.Schedule[Scheduler][CallID].Stop=Stop
local Info={}
if debug then
TraceLevel=TraceLevel or 2
Info=debug.getinfo(TraceLevel,"nlS")
local name_fsm=debug.getinfo(TraceLevel-1,"n").name
if name_fsm then
Info.name=name_fsm
end
end
self:T3(self.Schedule[Scheduler][CallID])
self.Schedule[Scheduler][CallID].CallHandler=function(Params)
local CallID=Params.CallID
local Info=Params.Info or{}
local Source=Info.source or"?"
local Line=Info.currentline or"?"
local Name=Info.name or"?"
local ErrorHandler=function(errmsg)
env.info("Error in timer function: "..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
local Scheduler=self.ObjectSchedulers[CallID]
if not Scheduler then
Scheduler=self.PersistentSchedulers[CallID]
end
if Scheduler then
local MasterObject=tostring(Scheduler.MasterObject)
local Schedule=self.Schedule[Scheduler][CallID]
local SchedulerObject=Scheduler.MasterObject
local ShowTrace=Scheduler.ShowTrace
local ScheduleFunction=Schedule.Function
local ScheduleArguments=Schedule.Arguments or{}
local Start=Schedule.Start
local Repeat=Schedule.Repeat or 0
local Randomize=Schedule.Randomize or 0
local Stop=Schedule.Stop or 0
local ScheduleID=Schedule.ScheduleID
local Prefix=(Repeat==0)and"--->"or"+++>"
local Status,Result
if SchedulerObject then
local function Timer()
if ShowTrace then
SchedulerObject:T(Prefix..Name..":"..Line.." ("..Source..")")
end
return ScheduleFunction(SchedulerObject,unpack(ScheduleArguments))
end
Status,Result=xpcall(Timer,ErrorHandler)
else
local function Timer()
if ShowTrace then
self:T(Prefix..Name..":"..Line.." ("..Source..")")
end
return ScheduleFunction(unpack(ScheduleArguments))
end
Status,Result=xpcall(Timer,ErrorHandler)
end
local CurrentTime=timer.getTime()
local StartTime=Schedule.StartTime
self:F3({CallID=CallID,ScheduleID=ScheduleID,Master=MasterObject,CurrentTime=CurrentTime,StartTime=StartTime,Start=Start,Repeat=Repeat,Randomize=Randomize,Stop=Stop})
if Status and((Result==nil)or(Result and Result~=false))then
if Repeat~=0 and((Stop==0)or(Stop~=0 and CurrentTime<=StartTime+Stop))then
local ScheduleTime=CurrentTime+Repeat+math.random(-(Randomize*Repeat/2),(Randomize*Repeat/2))+0.0001
return ScheduleTime
else
self:Stop(Scheduler,CallID)
end
else
self:Stop(Scheduler,CallID)
end
else
self:I("<<<>"..Name..":"..Line.." ("..Source..")")
end
return nil
end
self:Start(Scheduler,CallID,Info)
return CallID
end
function SCHEDULEDISPATCHER:RemoveSchedule(Scheduler,CallID)
self:F2({Remove=CallID,Scheduler=Scheduler})
if CallID then
self:Stop(Scheduler,CallID)
self.Schedule[Scheduler][CallID]=nil
end
end
function SCHEDULEDISPATCHER:Start(Scheduler,CallID,Info)
self:F2({Start=CallID,Scheduler=Scheduler})
if CallID then
local Schedule=self.Schedule[Scheduler][CallID]
if not Schedule.ScheduleID then
local Tnow=timer.getTime()
Schedule.StartTime=Tnow
Schedule.ScheduleID=timer.scheduleFunction(Schedule.CallHandler,{CallID=CallID,Info=Info},Tnow+Schedule.Start)
self:T(string.format("Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s",tostring(CallID),tostring(Schedule.ScheduleID)))
end
else
for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
self:Start(Scheduler,CallID,Info)
end
end
end
function SCHEDULEDISPATCHER:Stop(Scheduler,CallID)
self:F2({Stop=CallID,Scheduler=Scheduler})
if CallID then
local Schedule=self.Schedule[Scheduler][CallID]
if Schedule.ScheduleID then
self:T(string.format("SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s",tostring(CallID),tostring(Schedule.ScheduleID)))
timer.removeFunction(Schedule.ScheduleID)
Schedule.ScheduleID=nil
else
self:T(string.format("Error no ScheduleID for CallID=%s",tostring(CallID)))
end
else
for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
self:Stop(Scheduler,CallID)
end
end
end
function SCHEDULEDISPATCHER:Clear(Scheduler)
self:F2({Scheduler=Scheduler})
for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do
self:Stop(Scheduler,CallID)
end
end
function SCHEDULEDISPATCHER:ShowTrace(Scheduler)
self:F2({Scheduler=Scheduler})
Scheduler.ShowTrace=true
end
function SCHEDULEDISPATCHER:NoTrace(Scheduler)
self:F2({Scheduler=Scheduler})
Scheduler.ShowTrace=false
end
EVENT={
ClassName="EVENT",
ClassID=0,
MissionEnd=false,
}
world.event.S_EVENT_NEW_CARGO=world.event.S_EVENT_MAX+1000
world.event.S_EVENT_DELETE_CARGO=world.event.S_EVENT_MAX+1001
world.event.S_EVENT_NEW_ZONE=world.event.S_EVENT_MAX+1002
world.event.S_EVENT_DELETE_ZONE=world.event.S_EVENT_MAX+1003
world.event.S_EVENT_NEW_ZONE_GOAL=world.event.S_EVENT_MAX+1004
world.event.S_EVENT_DELETE_ZONE_GOAL=world.event.S_EVENT_MAX+1005
world.event.S_EVENT_REMOVE_UNIT=world.event.S_EVENT_MAX+1006
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT=world.event.S_EVENT_MAX+1007
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,
Takeoff=world.event.S_EVENT_TAKEOFF,
Land=world.event.S_EVENT_LAND,
Crash=world.event.S_EVENT_CRASH,
Ejection=world.event.S_EVENT_EJECTION,
Refueling=world.event.S_EVENT_REFUELING,
Dead=world.event.S_EVENT_DEAD,
PilotDead=world.event.S_EVENT_PILOT_DEAD,
BaseCaptured=world.event.S_EVENT_BASE_CAPTURED,
MissionStart=world.event.S_EVENT_MISSION_START,
MissionEnd=world.event.S_EVENT_MISSION_END,
TookControl=world.event.S_EVENT_TOOK_CONTROL,
RefuelingStop=world.event.S_EVENT_REFUELING_STOP,
Birth=world.event.S_EVENT_BIRTH,
HumanFailure=world.event.S_EVENT_HUMAN_FAILURE,
EngineStartup=world.event.S_EVENT_ENGINE_STARTUP,
EngineShutdown=world.event.S_EVENT_ENGINE_SHUTDOWN,
PlayerEnterUnit=world.event.S_EVENT_PLAYER_ENTER_UNIT,
PlayerLeaveUnit=world.event.S_EVENT_PLAYER_LEAVE_UNIT,
PlayerComment=world.event.S_EVENT_PLAYER_COMMENT,
ShootingStart=world.event.S_EVENT_SHOOTING_START,
ShootingEnd=world.event.S_EVENT_SHOOTING_END,
MarkAdded=world.event.S_EVENT_MARK_ADDED,
MarkChange=world.event.S_EVENT_MARK_CHANGE,
MarkRemoved=world.event.S_EVENT_MARK_REMOVED,
NewCargo=world.event.S_EVENT_NEW_CARGO,
DeleteCargo=world.event.S_EVENT_DELETE_CARGO,
NewZone=world.event.S_EVENT_NEW_ZONE,
DeleteZone=world.event.S_EVENT_DELETE_ZONE,
NewZoneGoal=world.event.S_EVENT_NEW_ZONE_GOAL,
DeleteZoneGoal=world.event.S_EVENT_DELETE_ZONE_GOAL,
RemoveUnit=world.event.S_EVENT_REMOVE_UNIT,
PlayerEnterAircraft=world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT,
DetailedFailure=world.event.S_EVENT_DETAILED_FAILURE or-1,
Kill=world.event.S_EVENT_KILL or-1,
Score=world.event.S_EVENT_SCORE or-1,
UnitLost=world.event.S_EVENT_UNIT_LOST or-1,
LandingAfterEjection=world.event.S_EVENT_LANDING_AFTER_EJECTION or-1,
ParatrooperLanding=world.event.S_EVENT_PARATROOPER_LENDING or-1,
DiscardChairAfterEjection=world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or-1,
WeaponAdd=world.event.S_EVENT_WEAPON_ADD or-1,
TriggerZone=world.event.S_EVENT_TRIGGER_ZONE or-1,
LandingQualityMark=world.event.S_EVENT_LANDING_QUALITY_MARK or-1,
BDA=world.event.S_EVENT_BDA or-1,
AIAbortMission=world.event.S_EVENT_AI_ABORT_MISSION or-1,
DayNight=world.event.S_EVENT_DAYNIGHT or-1,
FlightTime=world.event.S_EVENT_FLIGHT_TIME or-1,
SelfKillPilot=world.event.S_EVENT_PLAYER_SELF_KILL_PILOT or-1,
PlayerCaptureAirfield=world.event.S_EVENT_PLAYER_CAPTURE_AIRFIELD or-1,
EmergencyLanding=world.event.S_EVENT_EMERGENCY_LANDING or-1,
UnitCreateTask=world.event.S_EVENT_UNIT_CREATE_TASK or-1,
UnitDeleteTask=world.event.S_EVENT_UNIT_DELETE_TASK or-1,
SimulationStart=world.event.S_EVENT_SIMULATION_START or-1,
WeaponRearm=world.event.S_EVENT_WEAPON_REARM or-1,
WeaponDrop=world.event.S_EVENT_WEAPON_DROP or-1,
UnitTaskComplete=world.event.S_EVENT_UNIT_TASK_COMPLETE or-1,
UnitTaskStage=world.event.S_EVENT_UNIT_TASK_STAGE 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,
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]={
Order=1,
Side="I",
Event="OnEventShot",
Text="S_EVENT_SHOT"
},
[world.event.S_EVENT_HIT]={
Order=1,
Side="T",
Event="OnEventHit",
Text="S_EVENT_HIT"
},
[world.event.S_EVENT_TAKEOFF]={
Order=1,
Side="I",
Event="OnEventTakeoff",
Text="S_EVENT_TAKEOFF"
},
[world.event.S_EVENT_LAND]={
Order=1,
Side="I",
Event="OnEventLand",
Text="S_EVENT_LAND"
},
[world.event.S_EVENT_CRASH]={
Order=-1,
Side="I",
Event="OnEventCrash",
Text="S_EVENT_CRASH"
},
[world.event.S_EVENT_EJECTION]={
Order=1,
Side="I",
Event="OnEventEjection",
Text="S_EVENT_EJECTION"
},
[world.event.S_EVENT_REFUELING]={
Order=1,
Side="I",
Event="OnEventRefueling",
Text="S_EVENT_REFUELING"
},
[world.event.S_EVENT_DEAD]={
Order=-1,
Side="I",
Event="OnEventDead",
Text="S_EVENT_DEAD"
},
[world.event.S_EVENT_PILOT_DEAD]={
Order=1,
Side="I",
Event="OnEventPilotDead",
Text="S_EVENT_PILOT_DEAD"
},
[world.event.S_EVENT_BASE_CAPTURED]={
Order=1,
Side="I",
Event="OnEventBaseCaptured",
Text="S_EVENT_BASE_CAPTURED"
},
[world.event.S_EVENT_MISSION_START]={
Order=1,
Side="N",
Event="OnEventMissionStart",
Text="S_EVENT_MISSION_START"
},
[world.event.S_EVENT_MISSION_END]={
Order=1,
Side="N",
Event="OnEventMissionEnd",
Text="S_EVENT_MISSION_END"
},
[world.event.S_EVENT_TOOK_CONTROL]={
Order=1,
Side="N",
Event="OnEventTookControl",
Text="S_EVENT_TOOK_CONTROL"
},
[world.event.S_EVENT_REFUELING_STOP]={
Order=1,
Side="I",
Event="OnEventRefuelingStop",
Text="S_EVENT_REFUELING_STOP"
},
[world.event.S_EVENT_BIRTH]={
Order=1,
Side="I",
Event="OnEventBirth",
Text="S_EVENT_BIRTH"
},
[world.event.S_EVENT_HUMAN_FAILURE]={
Order=1,
Side="I",
Event="OnEventHumanFailure",
Text="S_EVENT_HUMAN_FAILURE"
},
[world.event.S_EVENT_ENGINE_STARTUP]={
Order=1,
Side="I",
Event="OnEventEngineStartup",
Text="S_EVENT_ENGINE_STARTUP"
},
[world.event.S_EVENT_ENGINE_SHUTDOWN]={
Order=1,
Side="I",
Event="OnEventEngineShutdown",
Text="S_EVENT_ENGINE_SHUTDOWN"
},
[world.event.S_EVENT_PLAYER_ENTER_UNIT]={
Order=1,
Side="I",
Event="OnEventPlayerEnterUnit",
Text="S_EVENT_PLAYER_ENTER_UNIT"
},
[world.event.S_EVENT_PLAYER_LEAVE_UNIT]={
Order=-1,
Side="I",
Event="OnEventPlayerLeaveUnit",
Text="S_EVENT_PLAYER_LEAVE_UNIT"
},
[world.event.S_EVENT_PLAYER_COMMENT]={
Order=1,
Side="I",
Event="OnEventPlayerComment",
Text="S_EVENT_PLAYER_COMMENT"
},
[world.event.S_EVENT_SHOOTING_START]={
Order=1,
Side="I",
Event="OnEventShootingStart",
Text="S_EVENT_SHOOTING_START"
},
[world.event.S_EVENT_SHOOTING_END]={
Order=1,
Side="I",
Event="OnEventShootingEnd",
Text="S_EVENT_SHOOTING_END"
},
[world.event.S_EVENT_MARK_ADDED]={
Order=1,
Side="I",
Event="OnEventMarkAdded",
Text="S_EVENT_MARK_ADDED"
},
[world.event.S_EVENT_MARK_CHANGE]={
Order=1,
Side="I",
Event="OnEventMarkChange",
Text="S_EVENT_MARK_CHANGE"
},
[world.event.S_EVENT_MARK_REMOVED]={
Order=1,
Side="I",
Event="OnEventMarkRemoved",
Text="S_EVENT_MARK_REMOVED"
},
[EVENTS.NewCargo]={
Order=1,
Event="OnEventNewCargo",
Text="S_EVENT_NEW_CARGO"
},
[EVENTS.DeleteCargo]={
Order=1,
Event="OnEventDeleteCargo",
Text="S_EVENT_DELETE_CARGO"
},
[EVENTS.NewZone]={
Order=1,
Event="OnEventNewZone",
Text="S_EVENT_NEW_ZONE"
},
[EVENTS.DeleteZone]={
Order=1,
Event="OnEventDeleteZone",
Text="S_EVENT_DELETE_ZONE"
},
[EVENTS.NewZoneGoal]={
Order=1,
Event="OnEventNewZoneGoal",
Text="S_EVENT_NEW_ZONE_GOAL"
},
[EVENTS.DeleteZoneGoal]={
Order=1,
Event="OnEventDeleteZoneGoal",
Text="S_EVENT_DELETE_ZONE_GOAL"
},
[EVENTS.RemoveUnit]={
Order=-1,
Event="OnEventRemoveUnit",
Text="S_EVENT_REMOVE_UNIT"
},
[EVENTS.PlayerEnterAircraft]={
Order=1,
Event="OnEventPlayerEnterAircraft",
Text="S_EVENT_PLAYER_ENTER_AIRCRAFT"
},
[EVENTS.DetailedFailure]={
Order=1,
Event="OnEventDetailedFailure",
Text="S_EVENT_DETAILED_FAILURE"
},
[EVENTS.Kill]={
Order=1,
Event="OnEventKill",
Text="S_EVENT_KILL"
},
[EVENTS.Score]={
Order=1,
Event="OnEventScore",
Text="S_EVENT_SCORE"
},
[EVENTS.UnitLost]={
Order=1,
Event="OnEventUnitLost",
Text="S_EVENT_UNIT_LOST"
},
[EVENTS.LandingAfterEjection]={
Order=1,
Event="OnEventLandingAfterEjection",
Text="S_EVENT_LANDING_AFTER_EJECTION"
},
[EVENTS.ParatrooperLanding]={
Order=1,
Event="OnEventParatrooperLanding",
Text="S_EVENT_PARATROOPER_LENDING"
},
[EVENTS.DiscardChairAfterEjection]={
Order=1,
Event="OnEventDiscardChairAfterEjection",
Text="S_EVENT_DISCARD_CHAIR_AFTER_EJECTION"
},
[EVENTS.WeaponAdd]={
Order=1,
Event="OnEventWeaponAdd",
Text="S_EVENT_WEAPON_ADD"
},
[EVENTS.TriggerZone]={
Order=1,
Event="OnEventTriggerZone",
Text="S_EVENT_TRIGGER_ZONE"
},
[EVENTS.LandingQualityMark]={
Order=1,
Event="OnEventLandingQualityMark",
Text="S_EVENT_LANDING_QUALITYMARK"
},
[EVENTS.BDA]={
Order=1,
Event="OnEventBDA",
Text="S_EVENT_BDA"
},
[EVENTS.AIAbortMission]={
Order=1,
Side="I",
Event="OnEventAIAbortMission",
Text="S_EVENT_AI_ABORT_MISSION"
},
[EVENTS.DayNight]={
Order=1,
Event="OnEventDayNight",
Text="S_EVENT_DAYNIGHT"
},
[EVENTS.FlightTime]={
Order=1,
Event="OnEventFlightTime",
Text="S_EVENT_FLIGHT_TIME"
},
[EVENTS.SelfKillPilot]={
Order=1,
Side="I",
Event="OnEventSelfKillPilot",
Text="S_EVENT_PLAYER_SELF_KILL_PILOT"
},
[EVENTS.PlayerCaptureAirfield]={
Order=1,
Event="OnEventPlayerCaptureAirfield",
Text="S_EVENT_PLAYER_CAPTURE_AIRFIELD"
},
[EVENTS.EmergencyLanding]={
Order=1,
Side="I",
Event="OnEventEmergencyLanding",
Text="S_EVENT_EMERGENCY_LANDING"
},
[EVENTS.UnitCreateTask]={
Order=1,
Event="OnEventUnitCreateTask",
Text="S_EVENT_UNIT_CREATE_TASK"
},
[EVENTS.UnitDeleteTask]={
Order=1,
Event="OnEventUnitDeleteTask",
Text="S_EVENT_UNIT_DELETE_TASK"
},
[EVENTS.SimulationStart]={
Order=1,
Event="OnEventSimulationStart",
Text="S_EVENT_SIMULATION_START"
},
[EVENTS.WeaponRearm]={
Order=1,
Side="I",
Event="OnEventWeaponRearm",
Text="S_EVENT_WEAPON_REARM"
},
[EVENTS.WeaponDrop]={
Order=1,
Side="I",
Event="OnEventWeaponDrop",
Text="S_EVENT_WEAPON_DROP"
},
[EVENTS.UnitTaskStage]={
Order=1,
Side="I",
Event="OnEventUnitTaskStage",
Text="S_EVENT_UNIT_TASK_STAGE "
},
[EVENTS.MacExtraScore]={
Order=1,
Side="I",
Event="OnEventMacExtraScore",
Text="S_EVENT_MAC_EXTRA_SCOREP"
},
[EVENTS.MissionRestart]={
Order=1,
Side="I",
Event="OnEventMissionRestart",
Text="S_EVENT_MISSION_RESTART"
},
[EVENTS.MissionWinner]={
Order=1,
Side="I",
Event="OnEventMissionWinner",
Text="S_EVENT_MISSION_WINNER"
},
[EVENTS.RunwayTakeoff]={
Order=1,
Side="I",
Event="OnEventRunwayTakeoff",
Text="S_EVENT_RUNWAY_TAKEOFF"
},
[EVENTS.RunwayTouch]={
Order=1,
Side="I",
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()
local self=BASE:Inherit(self,BASE:New())
self.EventHandler=world.addEventHandler(self)
return self
end
function EVENT:Init(EventID,EventClass)
self:F3({_EVENTMETA[EventID].Text,EventClass})
if not self.Events[EventID]then
self.Events[EventID]={}
end
local EventPriority=EventClass:GetEventPriority()
if not self.Events[EventID][EventPriority]then
self.Events[EventID][EventPriority]=setmetatable({},{__mode="k"})
end
if not self.Events[EventID][EventPriority][EventClass]then
self.Events[EventID][EventPriority][EventClass]={}
end
return self.Events[EventID][EventPriority][EventClass]
end
function EVENT:RemoveEvent(EventClass,EventID)
self:F2({"Removing subscription for class: ",EventClass:GetClassNameAndID()})
local EventPriority=EventClass:GetEventPriority()
self.Events=self.Events or{}
self.Events[EventID]=self.Events[EventID]or{}
self.Events[EventID][EventPriority]=self.Events[EventID][EventPriority]or{}
self.Events[EventID][EventPriority][EventClass]=nil
return self
end
function EVENT:Reset(EventObject)
self:F({"Resetting subscriptions for class: ",EventObject:GetClassNameAndID()})
local EventPriority=EventObject:GetEventPriority()
for EventID,EventData in pairs(self.Events)do
if self.EventsDead then
if self.EventsDead[EventID]then
if self.EventsDead[EventID][EventPriority]then
if self.EventsDead[EventID][EventPriority][EventObject]then
self.Events[EventID][EventPriority][EventObject]=self.EventsDead[EventID][EventPriority][EventObject]
end
end
end
end
end
end
function EVENT:RemoveAll(EventClass)
local EventClassName=EventClass:GetClassNameAndID()
local EventPriority=EventClass:GetEventPriority()
for EventID,EventData in pairs(self.Events)do
self.Events[EventID][EventPriority][EventClass]=nil
end
return self
end
function EVENT:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EventID)
self:F2(EventTemplate.name)
for EventUnitID,EventUnit in pairs(EventTemplate.units)do
self:OnEventForUnit(EventUnit.name,EventFunction,EventClass,EventID)
end
return self
end
function EVENT:OnEventGeneric(EventFunction,EventClass,EventID)
self:F2({EventID,EventClass,EventFunction})
local EventData=self:Init(EventID,EventClass)
EventData.EventFunction=EventFunction
return self
end
function EVENT:OnEventForUnit(UnitName,EventFunction,EventClass,EventID)
self:F2(UnitName)
local EventData=self:Init(EventID,EventClass)
EventData.EventUnit=true
EventData.EventFunction=EventFunction
return self
end
function EVENT:OnEventForGroup(GroupName,EventFunction,EventClass,EventID,...)
local Event=self:Init(EventID,EventClass)
Event.EventGroup=true
Event.EventFunction=EventFunction
Event.Params=arg
return self
end
do
function EVENT:OnBirthForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Birth)
return self
end
end
do
function EVENT:OnCrashForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Crash)
return self
end
end
do
function EVENT:OnDeadForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Dead)
return self
end
end
do
function EVENT:OnLandForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Land)
return self
end
end
do
function EVENT:OnTakeOffForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Takeoff)
return self
end
end
do
function EVENT:OnEngineShutDownForTemplate(EventTemplate,EventFunction,EventClass)
self:F2(EventTemplate.name)
self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.EngineShutdown)
return self
end
end
do
function EVENT:CreateEventNewCargo(Cargo)
self:F({Cargo})
local Event={
id=EVENTS.NewCargo,
time=timer.getTime(),
cargo=Cargo,
}
world.onEvent(Event)
end
function EVENT:CreateEventDeleteCargo(Cargo)
self:F({Cargo})
local Event={
id=EVENTS.DeleteCargo,
time=timer.getTime(),
cargo=Cargo,
}
world.onEvent(Event)
end
function EVENT:CreateEventNewZone(Zone)
self:F({Zone})
local Event={
id=EVENTS.NewZone,
time=timer.getTime(),
zone=Zone,
}
world.onEvent(Event)
end
function EVENT:CreateEventDeleteZone(Zone)
self:F({Zone})
local Event={
id=EVENTS.DeleteZone,
time=timer.getTime(),
zone=Zone,
}
world.onEvent(Event)
end
function EVENT:CreateEventNewZoneGoal(ZoneGoal)
self:F({ZoneGoal})
local Event={
id=EVENTS.NewZoneGoal,
time=timer.getTime(),
ZoneGoal=ZoneGoal,
}
world.onEvent(Event)
end
function EVENT:CreateEventDeleteZoneGoal(ZoneGoal)
self:F({ZoneGoal})
local Event={
id=EVENTS.DeleteZoneGoal,
time=timer.getTime(),
ZoneGoal=ZoneGoal,
}
world.onEvent(Event)
end
function EVENT:CreateEventPlayerEnterUnit(PlayerUnit)
self:F({PlayerUnit})
local Event={
id=EVENTS.PlayerEnterUnit,
time=timer.getTime(),
initiator=PlayerUnit:GetDCSObject()
}
world.onEvent(Event)
end
function EVENT:CreateEventPlayerEnterAircraft(PlayerUnit)
self:F({PlayerUnit})
local Event={
id=EVENTS.PlayerEnterAircraft,
time=timer.getTime(),
initiator=PlayerUnit:GetDCSObject()
}
world.onEvent(Event)
end
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)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(debug.traceback())
end
return errmsg
end
local EventMeta=_EVENTMETA[Event.id]
if EventMeta then
if self and self.Events and self.Events[Event.id]and self.MissionEnd==false and(Event.initiator~=nil or(Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit))then
if Event.id and Event.id==EVENTS.MissionEnd then
self.MissionEnd=true
end
if Event.initiator then
Event.IniObjectCategory=Object.getCategory(Event.initiator)
if Event.IniObjectCategory==Object.Category.STATIC then
if Event.id==31 then
Event.IniDCSUnit=Event.initiator
local ID=Event.initiator.id_
Event.IniDCSUnitName=string.format("Ejected Pilot ID %s",tostring(ID))
Event.IniUnitName=Event.IniDCSUnitName
Event.IniCoalition=0
Event.IniCategory=0
Event.IniTypeName="Ejected Pilot"
elseif Event.id==33 then
Event.IniDCSUnit=Event.initiator
local ID=Event.initiator.id_
Event.IniDCSUnitName=string.format("Ejection Seat ID %s",tostring(ID))
Event.IniUnitName=Event.IniDCSUnitName
Event.IniCoalition=0
Event.IniCategory=0
Event.IniTypeName="Ejection Seat"
else
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniUnit=STATIC:FindByName(Event.IniDCSUnitName,false)
Event.IniCoalition=Event.IniDCSUnit:getCoalition()
Event.IniCategory=Event.IniDCSUnit:getDesc().category
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
end
local Unit=UNIT:FindByName(Event.IniDCSUnitName)
if Unit then
Event.IniObjectCategory=Object.Category.UNIT
end
elseif Event.IniObjectCategory==Object.Category.UNIT then
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniDCSGroup=Event.IniDCSUnit:getGroup()
Event.IniUnit=UNIT:FindByName(Event.IniDCSUnitName)
if not Event.IniUnit then
Event.IniUnit=CLIENT:FindByName(Event.IniDCSUnitName,'',true)
end
Event.IniDCSGroupName=Event.IniUnit and Event.IniUnit.GroupName or""
Event.IniGroupName=Event.IniDCSGroupName
if Event.IniDCSGroup and Event.IniDCSGroup:isExist()then
Event.IniDCSGroupName=Event.IniDCSGroup:getName()
Event.IniGroup=GROUP:FindByName(Event.IniDCSGroupName)
Event.IniGroupName=Event.IniDCSGroupName
end
Event.IniPlayerName=Event.IniDCSUnit:getPlayerName()
if Event.IniPlayerName then
local PID=NET.GetPlayerIDByName(nil,Event.IniPlayerName)
if PID then
Event.IniPlayerUCID=net.get_player_info(tonumber(PID),'ucid')
end
end
Event.IniCoalition=Event.IniDCSUnit:getCoalition()
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
Event.IniCategory=Event.IniDCSUnit:getDesc().category
elseif Event.IniObjectCategory==Object.Category.CARGO then
Event.IniDCSUnit=Event.initiator
Event.IniDCSUnitName=Event.IniDCSUnit:getName()
Event.IniUnitName=Event.IniDCSUnitName
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=STATIC:FindByName(Event.IniDCSUnitName,false)
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 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 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()
Event.IniUnitName=Event.IniDCSUnitName
Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName)
Event.IniCoalition=Event.IniDCSUnit:getCoalition()
Event.IniCategory=Event.IniDCSUnit:getDesc().category
Event.IniTypeName=Event.IniDCSUnit:getTypeName()
if not Event.IniUnit then
_DATABASE:_RegisterAirbase(Event.initiator)
Event.IniUnit=AIRBASE:FindByName(Event.IniDCSUnitName)
end
end
end
if Event.target then
Event.TgtObjectCategory=Object.getCategory(Event.target)
if Event.TgtObjectCategory==Object.Category.UNIT then
Event.TgtDCSUnit=Event.target
Event.TgtDCSGroup=Event.TgtDCSUnit:getGroup()
Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=UNIT:FindByName(Event.TgtDCSUnitName)
Event.TgtDCSGroupName=""
if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist()then
Event.TgtDCSGroupName=Event.TgtDCSGroup:getName()
Event.TgtGroup=GROUP:FindByName(Event.TgtDCSGroupName)
Event.TgtGroupName=Event.TgtDCSGroupName
end
Event.TgtPlayerName=Event.TgtDCSUnit:getPlayerName()
if Event.TgtPlayerName then
local PID=NET.GetPlayerIDByName(nil,Event.TgtPlayerName)
if PID then
Event.TgtPlayerUCID=net.get_player_info(tonumber(PID),'ucid')
end
end
Event.TgtCoalition=Event.TgtDCSUnit:getCoalition()
Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
elseif Event.TgtObjectCategory==Object.Category.STATIC then
Event.TgtDCSUnit=Event.target
if Event.target.isExist and Event.target:isExist()and Event.id~=33 then
Event.TgtDCSUnitName=Event.TgtDCSUnit:getName()
if Event.TgtDCSUnitName and Event.TgtDCSUnitName~=""then
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=STATIC:FindByName(Event.TgtDCSUnitName,false)
Event.TgtCoalition=Event.TgtDCSUnit:getCoalition()
Event.TgtCategory=Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName=Event.TgtDCSUnit:getTypeName()
end
else
Event.TgtDCSUnitName=string.format("No target object for Event ID %s",tostring(Event.id))
Event.TgtUnitName=Event.TgtDCSUnitName
Event.TgtUnit=nil
Event.TgtCoalition=0
Event.TgtCategory=0
if Event.id==6 then
Event.TgtTypeName="Ejected Pilot"
Event.TgtDCSUnitName=string.format("Ejected Pilot ID %s",tostring(Event.IniDCSUnitName))
Event.TgtUnitName=Event.TgtDCSUnitName
elseif Event.id==33 then
Event.TgtTypeName="Ejection Seat"
Event.TgtDCSUnitName=string.format("Ejection Seat ID %s",tostring(Event.IniDCSUnitName))
Event.TgtUnitName=Event.TgtDCSUnitName
else
Event.TgtTypeName="Static"
end
end
elseif Event.TgtObjectCategory==Object.Category.SCENERY then
Event.TgtDCSUnit=Event.target
Event.TgtDCSUnitName=Event.TgtDCSUnit.getName 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
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: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 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)
Event.PlaceName=Event.Place:GetName()
end
end
end
if Event.idx then
Event.MarkID=Event.idx
Event.MarkVec3=Event.pos
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition
Event.IniCoalition=Event.coalition
Event.MarkGroupID=Event.groupID
end
if Event.cargo then
Event.Cargo=Event.cargo
Event.CargoName=Event.cargo.Name
end
if Event.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
end
local PriorityOrder=EventMeta.Order
local PriorityBegin=PriorityOrder==-1 and 5 or 1
local PriorityEnd=PriorityOrder==-1 and 1 or 5
for EventPriority=PriorityBegin,PriorityEnd,PriorityOrder do
if self.Events[Event.id][EventPriority]then
for EventClass,EventData in pairs(self.Events[Event.id][EventPriority])do
Event.IniGroup=Event.IniGroup or GROUP:FindByName(Event.IniDCSGroupName)
Event.TgtGroup=Event.TgtGroup or GROUP:FindByName(Event.TgtDCSGroupName)
if EventData.EventUnit then
if EventClass:IsAlive()or
Event.id==EVENTS.PlayerEnterUnit or
Event.id==EVENTS.Crash or
Event.id==EVENTS.Dead or
Event.id==EVENTS.RemoveUnit or
Event.id==EVENTS.UnitLost then
local UnitName=EventClass:GetName()
if(EventMeta.Side=="I"and UnitName==Event.IniDCSUnitName)or
(EventMeta.Side=="T"and UnitName==Event.TgtDCSUnitName)then
if EventData.EventFunction then
local Result,Value=xpcall(
function()
return EventData.EventFunction(EventClass,Event)
end,ErrorHandler)
else
local EventFunction=EventClass[EventMeta.Event]
if EventFunction and type(EventFunction)=="function"then
local Result,Value=xpcall(
function()
return EventFunction(EventClass,Event)
end,ErrorHandler)
end
end
end
else
self:RemoveEvent(EventClass,Event.id)
end
else
if EventData.EventGroup then
if EventClass:IsAlive()or
Event.id==EVENTS.PlayerEnterUnit or
Event.id==EVENTS.Crash or
Event.id==EVENTS.Dead or
Event.id==EVENTS.RemoveUnit or
Event.id==EVENTS.UnitLost then
local GroupName=EventClass:GetName()
if(EventMeta.Side=="I"and GroupName==Event.IniDCSGroupName)or
(EventMeta.Side=="T"and GroupName==Event.TgtDCSGroupName)then
if EventData.EventFunction then
local Result,Value=xpcall(
function()
return EventData.EventFunction(EventClass,Event,unpack(EventData.Params))
end,ErrorHandler)
else
local EventFunction=EventClass[EventMeta.Event]
if EventFunction and type(EventFunction)=="function"then
local Result,Value=xpcall(
function()
return EventFunction(EventClass,Event,unpack(EventData.Params))
end,ErrorHandler)
end
end
end
else
end
else
if not EventData.EventUnit then
if EventData.EventFunction then
local Result,Value=xpcall(
function()
return EventData.EventFunction(EventClass,Event)
end,ErrorHandler)
else
local EventFunction=EventClass[EventMeta.Event]
if EventFunction and type(EventFunction)=="function"then
local Result,Value=xpcall(
function()
local Result,Value=EventFunction(EventClass,Event)
return Result,Value
end,ErrorHandler)
end
end
end
end
end
end
end
end
if Event.id==EVENTS.DeleteCargo then
Event.Cargo.NoDestroy=nil
end
else
self:T({EventMeta.Text,Event})
end
else
self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?",tostring(Event.id)))
end
Event=nil
end
EVENTHANDLER={
ClassName="EVENTHANDLER",
ClassID=0,
}
function EVENTHANDLER:New()
self=BASE:Inherit(self,BASE:New())
return self
end
SETTINGS={
ClassName="SETTINGS",
ShowPlayerMenu=true,
MenuShort=false,
MenuStatic=false,
}
SETTINGS.__Enum={}
SETTINGS.__Enum.Era={
WWII=1,
Korea=2,
Cold=3,
Modern=4,
}
do
function SETTINGS:Set(PlayerName)
if PlayerName==nil then
local self=BASE:Inherit(self,BASE:New())
self:SetMetric()
self:SetA2G_BR()
self:SetA2A_BRAA()
self:SetLL_Accuracy(3)
self:SetMGRS_Accuracy(5)
self:SetMessageTime(MESSAGE.Type.Briefing,180)
self:SetMessageTime(MESSAGE.Type.Detailed,60)
self:SetMessageTime(MESSAGE.Type.Information,30)
self:SetMessageTime(MESSAGE.Type.Overview,60)
self:SetMessageTime(MESSAGE.Type.Update,15)
self:SetEraModern()
self:SetLocale("en")
return self
else
local Settings=_DATABASE:GetPlayerSettings(PlayerName)
if not Settings then
Settings=BASE:Inherit(self,BASE:New())
_DATABASE:SetPlayerSettings(PlayerName,Settings)
end
return Settings
end
end
function SETTINGS:SetMenutextShort(onoff)
_SETTINGS.MenuShort=onoff
end
function SETTINGS:SetMenuStatic(onoff)
_SETTINGS.MenuStatic=onoff
end
function SETTINGS:SetMetric()
self.Metric=true
end
function SETTINGS:SetLocale(Locale)
self.Locale=Locale or"en"
end
function SETTINGS:GetLocale()
return self.Locale or _SETTINGS:GetLocale()
end
function SETTINGS:IsMetric()
return(self.Metric~=nil and self.Metric==true)or(self.Metric==nil and _SETTINGS:IsMetric())
end
function SETTINGS:SetImperial()
self.Metric=false
end
function SETTINGS:IsImperial()
return(self.Metric~=nil and self.Metric==false)or(self.Metric==nil and _SETTINGS:IsImperial())
end
function SETTINGS:SetLL_Accuracy(LL_Accuracy)
self.LL_Accuracy=LL_Accuracy
end
function SETTINGS:GetLL_DDM_Accuracy()
return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy()
end
function SETTINGS:SetMGRS_Accuracy(MGRS_Accuracy)
self.MGRS_Accuracy=MGRS_Accuracy
end
function SETTINGS:GetMGRS_Accuracy()
return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy()
end
function SETTINGS:SetMessageTime(MessageType,MessageTime)
self.MessageTypeTimings=self.MessageTypeTimings or{}
self.MessageTypeTimings[MessageType]=MessageTime
end
function SETTINGS:GetMessageTime(MessageType)
return(self.MessageTypeTimings and self.MessageTypeTimings[MessageType])or _SETTINGS:GetMessageTime(MessageType)
end
function SETTINGS:SetA2G_LL_DMS()
self.A2GSystem="LL DMS"
end
function SETTINGS:SetA2G_LL_DDM()
self.A2GSystem="LL DDM"
end
function SETTINGS:IsA2G_LL_DMS()
return(self.A2GSystem and self.A2GSystem=="LL DMS")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
end
function SETTINGS:IsA2G_LL_DDM()
return(self.A2GSystem and self.A2GSystem=="LL DDM")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
end
function SETTINGS:SetA2G_MGRS()
self.A2GSystem="MGRS"
end
function SETTINGS:IsA2G_MGRS()
return(self.A2GSystem and self.A2GSystem=="MGRS")or(not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
end
function SETTINGS:SetA2G_BR()
self.A2GSystem="BR"
end
function SETTINGS:IsA2G_BR()
return(self.A2GSystem and self.A2GSystem=="BR")or(not self.A2GSystem and _SETTINGS:IsA2G_BR())
end
function SETTINGS:SetA2A_BRAA()
self.A2ASystem="BRAA"
end
function SETTINGS:IsA2A_BRAA()
return(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
end
function SETTINGS:SetA2A_BULLS()
self.A2ASystem="BULLS"
end
function SETTINGS:IsA2A_BULLS()
return(self.A2ASystem and self.A2ASystem=="BULLS")or(not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
end
function SETTINGS:SetA2A_LL_DMS()
self.A2ASystem="LL DMS"
end
function SETTINGS:SetA2A_LL_DDM()
self.A2ASystem="LL DDM"
end
function SETTINGS:IsA2A_LL_DMS()
return(self.A2ASystem and self.A2ASystem=="LL DMS")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
end
function SETTINGS:IsA2A_LL_DDM()
return(self.A2ASystem and self.A2ASystem=="LL DDM")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
end
function SETTINGS:SetA2A_MGRS()
self.A2ASystem="MGRS"
end
function SETTINGS:IsA2A_MGRS()
return(self.A2ASystem and self.A2ASystem=="MGRS")or(not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
end
function SETTINGS:SetSystemMenu(MenuGroup,RootMenu)
local MenuText="System Settings"
local MenuTime=timer.getTime()
local SettingsMenu=MENU_GROUP:New(MenuGroup,MenuText,RootMenu):SetTime(MenuTime)
local text="A2G Coordinate System"
if _SETTINGS.MenuShort then
text="A2G Coordinates"
end
local A2GCoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
if not self:IsA2G_LL_DMS()then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="LL DMS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
end
if not self:IsA2G_LL_DDM()then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="LL DDM"
end
MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime)
end
if self:IsA2G_LL_DDM()then
local text1="LL DDM Accuracy 1"
local text2="LL DDM Accuracy 2"
local text3="LL DDM Accuracy 3"
if _SETTINGS.MenuShort then
text1="LL DDM"
end
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
end
if not self:IsA2G_BR()then
local text="Bearing, Range (BR)"
if _SETTINGS.MenuShort then
text="BR"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"BR"):SetTime(MenuTime)
end
if not self:IsA2G_MGRS()then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="MGRS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime)
end
if self:IsA2G_MGRS()then
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime)
end
local text="A2A Coordinate System"
if _SETTINGS.MenuShort then
text="A2A Coordinates"
end
local A2ACoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
if not self:IsA2A_LL_DMS()then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="LL DMS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime)
end
if not self:IsA2A_LL_DDM()then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="LL DDM"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime)
end
if self:IsA2A_LL_DDM()or self:IsA2A_LL_DMS()then
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 0",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,0):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 1",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 2",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 3",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
end
if not self:IsA2A_BULLS()then
local text="Bullseye (BULLS)"
if _SETTINGS.MenuShort then
text="Bulls"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BULLS"):SetTime(MenuTime)
end
if not self:IsA2A_BRAA()then
local text="Bearing Range Altitude Aspect (BRAA)"
if _SETTINGS.MenuShort then
text="BRAA"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BRAA"):SetTime(MenuTime)
end
if not self:IsA2A_MGRS()then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="MGRS"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime)
end
if self:IsA2A_MGRS()then
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime)
end
local text="Measures and Weights System"
if _SETTINGS.MenuShort then
text="Unit System"
end
local MetricsMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
if self:IsMetric()then
local text="Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then
text="Imperial"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,false):SetTime(MenuTime)
end
if self:IsImperial()then
local text="Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then
text="Metric"
end
MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,true):SetTime(MenuTime)
end
local text="Messages and Reports"
if _SETTINGS.MenuShort then
text="Messages & Reports"
end
local MessagesMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime)
local UpdateMessagesMenu=MENU_GROUP:New(MenuGroup,"Update Messages",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"Off",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,0):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,5):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,10):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,60):SetTime(MenuTime)
local InformationMessagesMenu=MENU_GROUP:New(MenuGroup,"Information Messages",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,5):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,10):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,120):SetTime(MenuTime)
local BriefingReportsMenu=MENU_GROUP:New(MenuGroup,"Briefing Reports",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,120):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,180):SetTime(MenuTime)
local OverviewReportsMenu=MENU_GROUP:New(MenuGroup,"Overview Reports",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,120):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,180):SetTime(MenuTime)
local DetailedReportsMenu=MENU_GROUP:New(MenuGroup,"Detailed Reports",MessagesMenu):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,15):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,30):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,60):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,120):SetTime(MenuTime)
MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,180):SetTime(MenuTime)
SettingsMenu:Remove(MenuTime)
return self
end
function SETTINGS:SetPlayerMenuOn()
self.ShowPlayerMenu=true
end
function SETTINGS:SetPlayerMenuOff()
self.ShowPlayerMenu=false
end
function SETTINGS:SetPlayerMenu(PlayerUnit)
if _SETTINGS.ShowPlayerMenu==true then
local PlayerGroup=PlayerUnit:GetGroup()
local PlayerName=PlayerUnit:GetPlayerName()or"None"
local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"')
self.PlayerMenu=PlayerMenu
self:T(string.format("Setting menu for player %s",tostring(PlayerName)))
local submenu=MENU_GROUP:New(PlayerGroup,"LL Accuracy",PlayerMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 0 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 1 Decimal",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 2 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 3 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
MENU_GROUP_COMMAND:New(PlayerGroup,"LL 4 Decimals",submenu,self.MenuGroupLL_DDM_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
local submenu=MENU_GROUP:New(PlayerGroup,"MGRS Accuracy",PlayerMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 0",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,0)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 1",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,1)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 2",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,2)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 3",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,3)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 4",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,4)
MENU_GROUP_COMMAND:New(PlayerGroup,"MRGS Accuracy 5",submenu,self.MenuGroupMGRS_AccuracySystem,self,PlayerUnit,PlayerGroup,PlayerName,5)
local text="A2G Coordinate System"
if _SETTINGS.MenuShort then
text="A2G Coordinates"
end
local A2GCoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
if not self:IsA2G_LL_DMS()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="A2G LL DMS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
end
if not self:IsA2G_LL_DDM()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="A2G LL DDM"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
end
if not self:IsA2G_BR()or _SETTINGS.MenuStatic then
local text="Bearing, Range (BR)"
if _SETTINGS.MenuShort then
text="A2G BR"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"BR")
end
if not self:IsA2G_MGRS()or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="A2G MGRS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2GCoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
end
local text="A2A Coordinate System"
if _SETTINGS.MenuShort then
text="A2A Coordinates"
end
local A2ACoordinateMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
if not self:IsA2A_LL_DMS()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Min Sec (LL DMS)"
if _SETTINGS.MenuShort then
text="A2A LL DMS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2GSystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DMS")
end
if not self:IsA2A_LL_DDM()or _SETTINGS.MenuStatic then
local text="Lat/Lon Degree Dec Min (LL DDM)"
if _SETTINGS.MenuShort then
text="A2A LL DDM"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"LL DDM")
end
if not self:IsA2A_BULLS()or _SETTINGS.MenuStatic then
local text="Bullseye (BULLS)"
if _SETTINGS.MenuShort then
text="A2A BULLS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BULLS")
end
if not self:IsA2A_BRAA()or _SETTINGS.MenuStatic then
local text="Bearing Range Altitude Aspect (BRAA)"
if _SETTINGS.MenuShort then
text="A2A BRAA"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"BRAA")
end
if not self:IsA2A_MGRS()or _SETTINGS.MenuStatic then
local text="Military Grid (MGRS)"
if _SETTINGS.MenuShort then
text="A2A MGRS"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,A2ACoordinateMenu,self.MenuGroupA2ASystem,self,PlayerUnit,PlayerGroup,PlayerName,"MGRS")
end
local text="Measures and Weights System"
if _SETTINGS.MenuShort then
text="Unit System"
end
local MetricsMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
if self:IsMetric()or _SETTINGS.MenuStatic then
local text="Imperial (Miles,Feet)"
if _SETTINGS.MenuShort then
text="Imperial"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,false)
end
if self:IsImperial()or _SETTINGS.MenuStatic then
local text="Metric (Kilometers,Meters)"
if _SETTINGS.MenuShort then
text="Metric"
end
MENU_GROUP_COMMAND:New(PlayerGroup,text,MetricsMenu,self.MenuGroupMWSystem,self,PlayerUnit,PlayerGroup,PlayerName,true)
end
local text="Messages and Reports"
if _SETTINGS.MenuShort then
text="Messages & Reports"
end
local MessagesMenu=MENU_GROUP:New(PlayerGroup,text,PlayerMenu)
local UpdateMessagesMenu=MENU_GROUP:New(PlayerGroup,"Update Messages",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates Off",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,0)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 5 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,5)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 10 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,10)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 15 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 30 sec",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Updates 1 min",UpdateMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Update,60)
local InformationMessagesMenu=MENU_GROUP:New(PlayerGroup,"Info Messages",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 5 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,5)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 10 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,10)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 15 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 30 sec",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 1 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Info 2 min",InformationMessagesMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Information,120)
local BriefingReportsMenu=MENU_GROUP:New(PlayerGroup,"Briefing Reports",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 15 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 30 sec",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 1 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 2 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,120)
MENU_GROUP_COMMAND:New(PlayerGroup,"Brief 3 min",BriefingReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Briefing,180)
local OverviewReportsMenu=MENU_GROUP:New(PlayerGroup,"Overview Reports",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 15 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 30 sec",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 1 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 2 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,120)
MENU_GROUP_COMMAND:New(PlayerGroup,"Overview 3 min",OverviewReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.Overview,180)
local DetailedReportsMenu=MENU_GROUP:New(PlayerGroup,"Detailed Reports",MessagesMenu)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 15 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,15)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 30 sec",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,30)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 1 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,60)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 2 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,120)
MENU_GROUP_COMMAND:New(PlayerGroup,"Detailed 3 min",DetailedReportsMenu,self.MenuGroupMessageTimingsSystem,self,PlayerUnit,PlayerGroup,PlayerName,MESSAGE.Type.DetailedReportsMenu,180)
end
return self
end
function SETTINGS:RemovePlayerMenu(PlayerUnit)
if self.PlayerMenu then
self.PlayerMenu:Remove()
self.PlayerMenu=nil
end
return self
end
function SETTINGS:A2GMenuSystem(MenuGroup,RootMenu,A2GSystem)
self.A2GSystem=A2GSystem
MESSAGE:New(string.format("Settings: Default A2G coordinate system set to %s for all players!",A2GSystem),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:A2AMenuSystem(MenuGroup,RootMenu,A2ASystem)
self.A2ASystem=A2ASystem
MESSAGE:New(string.format("Settings: Default A2A coordinate system set to %s for all players!",A2ASystem),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuLL_DDM_Accuracy(MenuGroup,RootMenu,LL_Accuracy)
self.LL_Accuracy=LL_Accuracy
MESSAGE:New(string.format("Settings: Default LL accuracy set to %s for all players!",LL_Accuracy),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuMGRS_Accuracy(MenuGroup,RootMenu,MGRS_Accuracy)
self.MGRS_Accuracy=MGRS_Accuracy
MESSAGE:New(string.format("Settings: Default MGRS accuracy set to %s for all players!",MGRS_Accuracy),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuMWSystem(MenuGroup,RootMenu,MW)
self.Metric=MW
MESSAGE:New(string.format("Settings: Default measurement format set to %s for all players!",MW and"Metric"or"Imperial"),5):ToAll()
self:SetSystemMenu(MenuGroup,RootMenu)
end
function SETTINGS:MenuMessageTimingsSystem(MenuGroup,RootMenu,MessageType,MessageTime)
self:SetMessageTime(MessageType,MessageTime)
MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToAll()
end
do
function SETTINGS:MenuGroupA2GSystem(PlayerUnit,PlayerGroup,PlayerName,A2GSystem)
self.A2GSystem=A2GSystem
MESSAGE:New(string.format("Settings: A2G format set to %s for player %s.",A2GSystem,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupA2ASystem(PlayerUnit,PlayerGroup,PlayerName,A2ASystem)
self.A2ASystem=A2ASystem
MESSAGE:New(string.format("Settings: A2A format set to %s for player %s.",A2ASystem,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,LL_Accuracy)
self.LL_Accuracy=LL_Accuracy
MESSAGE:New(string.format("Settings: LL format accuracy set to %d decimal places for player %s.",LL_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupMGRS_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,MGRS_Accuracy)
self.MGRS_Accuracy=MGRS_Accuracy
MESSAGE:New(string.format("Settings: MGRS format accuracy set to %d for player %s.",MGRS_Accuracy,PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupMWSystem(PlayerUnit,PlayerGroup,PlayerName,MW)
self.Metric=MW
MESSAGE:New(string.format("Settings: Measurement format set to %s for player %s.",MW and"Metric"or"Imperial",PlayerName),5):ToGroup(PlayerGroup)
if _SETTINGS.MenuStatic==false then
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
end
function SETTINGS:MenuGroupMessageTimingsSystem(PlayerUnit,PlayerGroup,PlayerName,MessageType,MessageTime)
self:SetMessageTime(MessageType,MessageTime)
MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToGroup(PlayerGroup)
end
end
function SETTINGS:SetEraWWII()
self.Era=SETTINGS.__Enum.Era.WWII
end
function SETTINGS:SetEraKorea()
self.Era=SETTINGS.__Enum.Era.Korea
end
function SETTINGS:SetEraCold()
self.Era=SETTINGS.__Enum.Era.Cold
end
function SETTINGS:SetEraModern()
self.Era=SETTINGS.__Enum.Era.Modern
end
end
MENU_INDEX={}
MENU_INDEX.MenuMission={}
MENU_INDEX.MenuMission.Menus={}
MENU_INDEX.Coalition={}
MENU_INDEX.Coalition[coalition.side.BLUE]={}
MENU_INDEX.Coalition[coalition.side.BLUE].Menus={}
MENU_INDEX.Coalition[coalition.side.RED]={}
MENU_INDEX.Coalition[coalition.side.RED].Menus={}
MENU_INDEX.Group={}
function MENU_INDEX:ParentPath(ParentMenu,MenuText)
local Path=ParentMenu and"@"..table.concat(ParentMenu.MenuPath or{},"@")or""
if ParentMenu then
if ParentMenu:IsInstanceOf("MENU_GROUP")or ParentMenu:IsInstanceOf("MENU_GROUP_COMMAND")then
local GroupName=ParentMenu.Group:GetName()
if not self.Group[GroupName].Menus[Path]then
BASE:E({Path=Path,GroupName=GroupName})
error("Parent path not found in menu index for group menu")
return nil
end
elseif ParentMenu:IsInstanceOf("MENU_COALITION")or ParentMenu:IsInstanceOf("MENU_COALITION_COMMAND")then
local Coalition=ParentMenu.Coalition
if not self.Coalition[Coalition].Menus[Path]then
BASE:E({Path=Path,Coalition=Coalition})
error("Parent path not found in menu index for coalition menu")
return nil
end
elseif ParentMenu:IsInstanceOf("MENU_MISSION")or ParentMenu:IsInstanceOf("MENU_MISSION_COMMAND")then
if not self.MenuMission.Menus[Path]then
BASE:E({Path=Path})
error("Parent path not found in menu index for mission menu")
return nil
end
end
end
Path=Path.."@"..MenuText
return Path
end
function MENU_INDEX:PrepareMission()
self.MenuMission.Menus=self.MenuMission.Menus or{}
end
function MENU_INDEX:PrepareCoalition(CoalitionSide)
self.Coalition[CoalitionSide]=self.Coalition[CoalitionSide]or{}
self.Coalition[CoalitionSide].Menus=self.Coalition[CoalitionSide].Menus or{}
end
function MENU_INDEX:PrepareGroup(Group)
if Group and Group:IsAlive()~=nil then
local GroupName=Group:GetName()
self.Group[GroupName]=self.Group[GroupName]or{}
self.Group[GroupName].Menus=self.Group[GroupName].Menus or{}
end
end
function MENU_INDEX:HasMissionMenu(Path)
return self.MenuMission.Menus[Path]
end
function MENU_INDEX:SetMissionMenu(Path,Menu)
self.MenuMission.Menus[Path]=Menu
end
function MENU_INDEX:ClearMissionMenu(Path)
self.MenuMission.Menus[Path]=nil
end
function MENU_INDEX:HasCoalitionMenu(Coalition,Path)
return self.Coalition[Coalition].Menus[Path]
end
function MENU_INDEX:SetCoalitionMenu(Coalition,Path,Menu)
self.Coalition[Coalition].Menus[Path]=Menu
end
function MENU_INDEX:ClearCoalitionMenu(Coalition,Path)
self.Coalition[Coalition].Menus[Path]=nil
end
function MENU_INDEX:HasGroupMenu(Group,Path)
if Group and Group:IsAlive()then
local MenuGroupName=Group:GetName()
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()
self.Group[MenuGroupName].Menus[Path]=Menu
end
function MENU_INDEX:ClearGroupMenu(Group,Path)
local MenuGroupName=Group:GetName()
self.Group[MenuGroupName].Menus[Path]=nil
end
function MENU_INDEX:Refresh(Group)
for MenuID,Menu in pairs(self.MenuMission.Menus)do
Menu:Refresh()
end
for MenuID,Menu in pairs(self.Coalition[coalition.side.BLUE].Menus)do
Menu:Refresh()
end
for MenuID,Menu in pairs(self.Coalition[coalition.side.RED].Menus)do
Menu:Refresh()
end
local GroupName=Group:GetName()
for MenuID,Menu in pairs(self.Group[GroupName].Menus)do
Menu:Refresh()
end
return self
end
do
MENU_BASE={
ClassName="MENU_BASE",
MenuPath=nil,
MenuText="",
MenuParentPath=nil,
}
function MENU_BASE:New(MenuText,ParentMenu)
local MenuParentPath={}
if ParentMenu~=nil then
MenuParentPath=ParentMenu.MenuPath
end
local self=BASE:Inherit(self,BASE:New())
self.MenuPath=nil
self.MenuText=MenuText
self.ParentMenu=ParentMenu
self.MenuParentPath=MenuParentPath
self.Path=(self.ParentMenu and"@"..table.concat(self.MenuParentPath or{},"@")or"").."@"..self.MenuText
self.Menus={}
self.MenuCount=0
self.MenuStamp=timer.getTime()
self.MenuRemoveParent=false
if self.ParentMenu then
self.ParentMenu.Menus=self.ParentMenu.Menus or{}
self.ParentMenu.Menus[MenuText]=self
end
return self
end
function MENU_BASE:SetParentMenu(MenuText,Menu)
if self.ParentMenu then
self.ParentMenu.Menus=self.ParentMenu.Menus or{}
self.ParentMenu.Menus[MenuText]=Menu
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount+1
end
end
function MENU_BASE:ClearParentMenu(MenuText)
if self.ParentMenu and self.ParentMenu.Menus[MenuText]then
self.ParentMenu.Menus[MenuText]=nil
self.ParentMenu.MenuCount=self.ParentMenu.MenuCount-1
if self.ParentMenu.MenuCount==0 then
end
end
end
function MENU_BASE:SetRemoveParent(RemoveParent)
self.MenuRemoveParent=RemoveParent
return self
end
function MENU_BASE:GetMenu(MenuText)
return self.Menus[MenuText]
end
function MENU_BASE:SetStamp(MenuStamp)
self.MenuStamp=MenuStamp
return self
end
function MENU_BASE:GetStamp()
return timer.getTime()
end
function MENU_BASE:SetTime(MenuStamp)
self.MenuStamp=MenuStamp
return self
end
function MENU_BASE:SetTag(MenuTag)
self.MenuTag=MenuTag
return self
end
end
do
MENU_COMMAND_BASE={
ClassName="MENU_COMMAND_BASE",
CommandMenuFunction=nil,
CommandMenuArgument=nil,
MenuCallHandler=nil,
}
function MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,CommandMenuArguments)
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
local ErrorHandler=function(errmsg)
env.info("MOOSE error in MENU COMMAND function: "..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
self:SetCommandMenuFunction(CommandMenuFunction)
self:SetCommandMenuArguments(CommandMenuArguments)
self.MenuCallHandler=function()
local function MenuFunction()
return self.CommandMenuFunction(unpack(self.CommandMenuArguments))
end
local Status,Result=xpcall(MenuFunction,ErrorHandler)
end
return self
end
function MENU_COMMAND_BASE:SetCommandMenuFunction(CommandMenuFunction)
self.CommandMenuFunction=CommandMenuFunction
return self
end
function MENU_COMMAND_BASE:SetCommandMenuArguments(CommandMenuArguments)
self.CommandMenuArguments=CommandMenuArguments
return self
end
end
do
MENU_MISSION={
ClassName="MENU_MISSION",
}
function MENU_MISSION:New(MenuText,ParentMenu)
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu then
return MissionMenu
else
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetMissionMenu(Path,self)
self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_MISSION:Refresh()
do
missionCommands.removeItem(self.MenuPath)
self.MenuPath=missionCommands.addSubMenu(self.MenuText,self.MenuParentPath)
end
return self
end
function MENU_MISSION:RemoveSubMenus()
for MenuID,Menu in pairs(self.Menus or{})do
Menu:Remove()
end
self.Menus=nil
end
function MENU_MISSION:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu==self then
self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItem(self.MenuPath)
end
MENU_INDEX:ClearMissionMenu(self.Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_MISSION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText})
end
return self
end
end
do
MENU_MISSION_COMMAND={
ClassName="MENU_MISSION_COMMAND",
}
function MENU_MISSION_COMMAND:New(MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu then
MissionMenu:SetCommandMenuFunction(CommandMenuFunction)
MissionMenu:SetCommandMenuArguments(arg)
return MissionMenu
else
local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetMissionMenu(Path,self)
self.MenuPath=missionCommands.addCommand(MenuText,self.MenuParentPath,self.MenuCallHandler)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_MISSION_COMMAND:Refresh()
do
missionCommands.removeItem(self.MenuPath)
missionCommands.addCommand(self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_MISSION_COMMAND:Remove()
MENU_INDEX:PrepareMission()
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local MissionMenu=MENU_INDEX:HasMissionMenu(Path)
if MissionMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItem(self.MenuPath)
end
MENU_INDEX:ClearMissionMenu(self.Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_MISSION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText})
end
return self
end
end
do
MENU_COALITION={
ClassName="MENU_COALITION"
}
function MENU_COALITION:New(Coalition,MenuText,ParentMenu)
MENU_INDEX:PrepareCoalition(Coalition)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path)
if CoalitionMenu then
return CoalitionMenu
else
local self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetCoalitionMenu(Coalition,Path,self)
self.Coalition=Coalition
self.MenuPath=missionCommands.addSubMenuForCoalition(Coalition,MenuText,self.MenuParentPath)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_COALITION:Refresh()
do
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
missionCommands.addSubMenuForCoalition(self.Coalition,self.MenuText,self.MenuParentPath)
end
return self
end
function MENU_COALITION:RemoveSubMenus()
for MenuID,Menu in pairs(self.Menus or{})do
Menu:Remove()
end
self.Menus=nil
end
function MENU_COALITION:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareCoalition(self.Coalition)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path)
if CoalitionMenu==self then
self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
end
MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_COALITION",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition})
end
return self
end
end
do
MENU_COALITION_COMMAND={
ClassName="MENU_COALITION_COMMAND"
}
function MENU_COALITION_COMMAND:New(Coalition,MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareCoalition(Coalition)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(Coalition,Path)
if CoalitionMenu then
CoalitionMenu:SetCommandMenuFunction(CommandMenuFunction)
CoalitionMenu:SetCommandMenuArguments(arg)
return CoalitionMenu
else
local self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetCoalitionMenu(Coalition,Path,self)
self.Coalition=Coalition
self.MenuPath=missionCommands.addCommandForCoalition(self.Coalition,MenuText,self.MenuParentPath,self.MenuCallHandler)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_COALITION_COMMAND:Refresh()
do
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
missionCommands.addCommandForCoalition(self.Coalition,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_COALITION_COMMAND:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareCoalition(self.Coalition)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local CoalitionMenu=MENU_INDEX:HasCoalitionMenu(self.Coalition,Path)
if CoalitionMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
self:F({Coalition=self.Coalition,Text=self.MenuText,Path=self.MenuPath})
if self.MenuPath~=nil then
missionCommands.removeItemForCoalition(self.Coalition,self.MenuPath)
end
MENU_INDEX:ClearCoalitionMenu(self.Coalition,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_COALITION_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Coalition=self.Coalition})
end
return self
end
end
do
local _MENUGROUPS={}
MENU_GROUP={
ClassName="MENU_GROUP"
}
function MENU_GROUP:New(Group,MenuText,ParentMenu)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
return GroupMenu
else
self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
self.MenuPath=missionCommands.addSubMenuForGroup(self.GroupID,MenuText,self.MenuParentPath)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_GROUP:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Refresh()
end
end
return self
end
function MENU_GROUP:RefreshAndOrderByTag()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath)
local MenuTable={}
for MenuText,Menu in pairs(self.Menus or{})do
local tag=Menu.MenuTag or math.random(1,10000)
MenuTable[#MenuTable+1]={Tag=tag,Enty=Menu}
end
table.sort(MenuTable,function(k1,k2)return k1.tag<k2.tag end)
for _,Menu in pairs(MenuTable)do
Menu.Entry:Refresh()
end
end
return self
end
function MENU_GROUP:RemoveSubMenus(MenuStamp,MenuTag)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Remove(MenuStamp,MenuTag)
end
self.Menus=nil
end
function MENU_GROUP:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
self:RemoveSubMenus(MenuStamp,MenuTag)
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
return nil
end
return self
end
MENU_GROUP_COMMAND={
ClassName="MENU_GROUP_COMMAND"
}
function MENU_GROUP_COMMAND:New(Group,MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
GroupMenu:SetCommandMenuFunction(CommandMenuFunction)
GroupMenu:SetCommandMenuArguments(arg)
return GroupMenu
else
self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
self.MenuPath=missionCommands.addCommandForGroup(self.GroupID,MenuText,self.MenuParentPath,self.MenuCallHandler)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_GROUP_COMMAND:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addCommandForGroup(self.GroupID,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_GROUP_COMMAND:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP_COMMAND",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
end
return self
end
end
do
MENU_GROUP_DELAYED={
ClassName="MENU_GROUP_DELAYED"
}
function MENU_GROUP_DELAYED:New(Group,MenuText,ParentMenu)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
return GroupMenu
else
self=BASE:Inherit(self,MENU_BASE:New(MenuText,ParentMenu))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
if self.MenuParentPath then
self.MenuPath=UTILS.DeepCopy(self.MenuParentPath)
else
self.MenuPath={}
end
table.insert(self.MenuPath,self.MenuText)
self:SetParentMenu(self.MenuText,self)
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)
self.MenuSet=true
end
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Set()
end
end
end
function MENU_GROUP_DELAYED:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addSubMenuForGroup(self.GroupID,self.MenuText,self.MenuParentPath)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Refresh()
end
end
return self
end
function MENU_GROUP_DELAYED:RemoveSubMenus(MenuStamp,MenuTag)
for MenuText,Menu in pairs(self.Menus or{})do
Menu:Remove(MenuStamp,MenuTag)
end
self.Menus=nil
end
function MENU_GROUP_DELAYED:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
self:RemoveSubMenus(MenuStamp,MenuTag)
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP_DELAYED",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
return nil
end
return self
end
MENU_GROUP_COMMAND_DELAYED={
ClassName="MENU_GROUP_COMMAND_DELAYED"
}
function MENU_GROUP_COMMAND_DELAYED:New(Group,MenuText,ParentMenu,CommandMenuFunction,...)
MENU_INDEX:PrepareGroup(Group)
local Path=MENU_INDEX:ParentPath(ParentMenu,MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(Group,Path)
if GroupMenu then
GroupMenu:SetCommandMenuFunction(CommandMenuFunction)
GroupMenu:SetCommandMenuArguments(arg)
return GroupMenu
else
self=BASE:Inherit(self,MENU_COMMAND_BASE:New(MenuText,ParentMenu,CommandMenuFunction,arg))
MENU_INDEX:SetGroupMenu(Group,Path,self)
self.Group=Group
self.GroupID=Group:GetID()
if self.MenuParentPath then
self.MenuPath=UTILS.DeepCopy(self.MenuParentPath)
else
self.MenuPath={}
end
table.insert(self.MenuPath,self.MenuText)
self:SetParentMenu(self.MenuText,self)
return self
end
end
function MENU_GROUP_COMMAND_DELAYED:Set()
do
if not self.MenuSet then
self.MenuPath=missionCommands.addCommandForGroup(self.GroupID,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
self.MenuSet=true
end
end
end
function MENU_GROUP_COMMAND_DELAYED:Refresh()
do
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
missionCommands.addCommandForGroup(self.GroupID,self.MenuText,self.MenuParentPath,self.MenuCallHandler)
end
return self
end
function MENU_GROUP_COMMAND_DELAYED:Remove(MenuStamp,MenuTag)
MENU_INDEX:PrepareGroup(self.Group)
local Path=MENU_INDEX:ParentPath(self.ParentMenu,self.MenuText)
local GroupMenu=MENU_INDEX:HasGroupMenu(self.Group,Path)
if GroupMenu==self then
if not MenuStamp or self.MenuStamp~=MenuStamp then
if(not MenuTag)or(MenuTag and self.MenuTag and MenuTag==self.MenuTag)then
if self.MenuPath~=nil then
self:F({Group=self.GroupID,Text=self.MenuText,Path=self.MenuPath})
missionCommands.removeItemForGroup(self.GroupID,self.MenuPath)
end
MENU_INDEX:ClearGroupMenu(self.Group,Path)
self:ClearParentMenu(self.MenuText)
return nil
end
end
else
BASE:E({"Cannot Remove MENU_GROUP_COMMAND_DELAYED",Path=Path,ParentMenu=self.ParentMenu,MenuText=self.MenuText,Group=self.Group})
end
return self
end
end
ZONE_BASE={
ClassName="ZONE_BASE",
ZoneName="",
ZoneProbability=1,
DrawID=nil,
Color={},
ZoneID=nil,
Properties={},
Surface=nil,
Checktime=5,
}
function ZONE_BASE:New(ZoneName)
local self=BASE:Inherit(self,FSM:New())
self.ZoneName=ZoneName
return self
end
function ZONE_BASE:GetName()
return self.ZoneName
end
function ZONE_BASE:SetName(ZoneName)
self.ZoneName=ZoneName
end
function ZONE_BASE:IsVec2InZone(Vec2)
return false
end
function ZONE_BASE:IsVec3InZone(Vec3)
if not Vec3 then return false end
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
function ZONE_BASE:IsCoordinateInZone(Coordinate)
if not Coordinate then return false end
local InZone=self:IsVec2InZone(Coordinate:GetVec2())
return InZone
end
function ZONE_BASE:IsPointVec2InZone(Coordinate)
local InZone=self:IsVec2InZone(Coordinate:GetVec2())
return InZone
end
function ZONE_BASE:IsPointVec3InZone(PointVec3)
local InZone=self:IsPointVec2InZone(PointVec3)
return InZone
end
function ZONE_BASE:GetVec2()
return nil
end
function ZONE_BASE:GetPointVec2()
local Vec2=self:GetVec2()
local PointVec2=COORDINATE:NewFromVec2(Vec2)
return PointVec2
end
function ZONE_BASE:GetVec3(Height)
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}
return Vec3
end
function ZONE_BASE:GetPointVec3(Height)
local Vec3=self:GetVec3(Height)
local PointVec3=COORDINATE:NewFromVec3(Vec3)
return PointVec3
end
function ZONE_BASE:GetCoordinate(Height)
local Vec3=self:GetVec3(Height)
if self.Coordinate then
self.Coordinate.x=Vec3.x
self.Coordinate.y=Vec3.y
self.Coordinate.z=Vec3.z
else
self.Coordinate=COORDINATE:NewFromVec3(Vec3)
end
return self.Coordinate
end
function ZONE_BASE:Get2DDistance(Coordinate)
local a=self:GetVec2()
local b={}
if Coordinate.z then
b.x=Coordinate.x
b.y=Coordinate.z
else
b.x=Coordinate.x
b.y=Coordinate.y
end
local dist=UTILS.VecDist2D(a,b)
return dist
end
function ZONE_BASE:GetRandomVec2()
return nil
end
function ZONE_BASE:GetRandomPointVec2()
return nil
end
function ZONE_BASE:GetRandomPointVec3()
return nil
end
function ZONE_BASE:GetBoundingSquare()
return nil
end
function ZONE_BASE:GetSurfaceType()
local coord=self:GetCoordinate()
local surface=coord:GetSurfaceType()
return surface
end
function ZONE_BASE:BoundZone()
end
function ZONE_BASE:SetDrawCoalition(Coalition)
self.drawCoalition=Coalition or-1
return self
end
function ZONE_BASE:GetDrawCoalition()
return self.drawCoalition or-1
end
function ZONE_BASE:SetColor(RGBcolor,Alpha)
RGBcolor=RGBcolor or{1,0,0}
Alpha=Alpha or 0.15
self.Color={}
self.Color[1]=RGBcolor[1]
self.Color[2]=RGBcolor[2]
self.Color[3]=RGBcolor[3]
self.Color[4]=Alpha
return self
end
function ZONE_BASE:GetColor()
return self.Color or{1,0,0,0.15}
end
function ZONE_BASE:GetColorRGB()
local rgb={}
local Color=self:GetColor()
rgb[1]=Color[1]
rgb[2]=Color[2]
rgb[3]=Color[3]
return rgb
end
function ZONE_BASE:GetColorAlpha()
local Color=self:GetColor()
local alpha=Color[4]
return alpha
end
function ZONE_BASE:SetFillColor(RGBcolor,Alpha)
RGBcolor=RGBcolor or{1,0,0}
Alpha=Alpha or 0.15
self.FillColor={}
self.FillColor[1]=RGBcolor[1]
self.FillColor[2]=RGBcolor[2]
self.FillColor[3]=RGBcolor[3]
self.FillColor[4]=Alpha
return self
end
function ZONE_BASE:GetFillColor()
return self.FillColor or{1,0,0,0.15}
end
function ZONE_BASE:GetFillColorRGB()
local rgb={}
local FillColor=self:GetFillColor()
rgb[1]=FillColor[1]
rgb[2]=FillColor[2]
rgb[3]=FillColor[3]
return rgb
end
function ZONE_BASE:GetFillColorAlpha()
local FillColor=self:GetFillColor()
local alpha=FillColor[4]
return alpha
end
function ZONE_BASE:UndrawZone(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,ZONE_BASE.UndrawZone,self)
else
if self.DrawID then
if type(self.DrawID)~="table"then
UTILS.RemoveMark(self.DrawID)
else
for _,mark_id in pairs(self.DrawID)do
UTILS.RemoveMark(mark_id)
end
end
end
end
return self
end
function ZONE_BASE:GetDrawID()
return self.DrawID
end
function ZONE_BASE:SmokeZone(SmokeColor)
end
function ZONE_BASE:SetZoneProbability(ZoneProbability)
self.ZoneProbability=ZoneProbability or 1
return self
end
function ZONE_BASE:GetZoneProbability()
return self.ZoneProbability
end
function ZONE_BASE:GetZoneMaybe()
local Randomization=math.random()
if Randomization<=self.ZoneProbability then
return self
else
return nil
end
end
function ZONE_BASE:SetCheckTime(seconds)
self.Checktime=seconds or 5
return self
end
function ZONE_BASE:Trigger(Objects)
self:SetStartState("TriggerStopped")
self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning")
self:AddTransition("*","EnteredZone","*")
self:AddTransition("*","LeftZone","*")
self:AddTransition("*","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
self.objectset={Objects}
end
self:_TriggerCheck(true)
self:__TriggerRunCheck(self.Checktime)
return self
end
function ZONE_BASE:_TriggerCheck(fromstart)
local objectset=self.objectset or{}
if fromstart then
for _,_object in pairs(objectset)do
local obj=_object
if not obj.TriggerInZone then
obj.TriggerInZone={}
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
if not obj.TriggerInZone then
obj.TriggerInZone={}
end
if not obj.TriggerInZone[self.ZoneName]then
obj.TriggerInZone[self.ZoneName]=false
end
local inzone=self:IsCoordinateInZone(obj:GetCoordinate())
if inzone and 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)
if self:GetState()~="TriggerStopped"then
self:_TriggerCheck()
self:__TriggerRunCheck(self.Checktime)
end
return self
end
function ZONE_BASE:GetProperty(PropertyName)
return self.Properties[PropertyName]
end
function ZONE_BASE:GetAllProperties()
return self.Properties
end
ZONE_RADIUS={
ClassName="ZONE_RADIUS",
}
function ZONE_RADIUS:New(ZoneName,Vec2,Radius,DoNotRegisterZone)
local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
self.Radius=Radius
self.Vec2=Vec2
if not DoNotRegisterZone then
_EVENTDISPATCHER:CreateEventNewZone(self)
end
return self
end
function ZONE_RADIUS:UpdateFromVec2(Vec2,Radius)
self.Vec2=Vec2
if Radius then
self.Radius=Radius
end
return self
end
function ZONE_RADIUS:UpdateFromVec3(Vec3,Radius)
self.Vec2.x=Vec3.x
self.Vec2.y=Vec3.z
if Radius then
self.Radius=Radius
end
return self
end
function ZONE_RADIUS:MarkZone(Points)
local Point={}
local Vec2=self:GetVec2()
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,(360/Points)do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName())
end
end
function ZONE_RADIUS:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
local coordinate=self:GetCoordinate()
local Radius=self:GetRadius()
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillAlpha=FillAlpha or self:GetColorAlpha()
self.DrawID=coordinate:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
return self
end
function ZONE_RADIUS:BoundZone(Points,CountryID,UnBound)
local Point={}
local Vec2=self:GetVec2()
local countryID=CountryID or country.id.USA
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,(360/Points)do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
local CountryName=_DATABASE.COUNTRY_NAME[countryID]
local Tire={
["country"]=CountryName,
["category"]="Fortifications",
["canCargo"]=false,
["shape_name"]="H-tyre_B_WF",
["type"]="Black_Tyre_WF",
["y"]=Point.y,
["x"]=Point.x,
["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle),
["heading"]=0,
}
local Group=coalition.addStaticObject(countryID,Tire)
if UnBound and UnBound==true then
Group:destroy()
end
end
return self
end
function ZONE_RADIUS:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset)
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
AngleOffset=AngleOffset or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=(Angle+AngleOffset)*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
COORDINATE:New(Point.x,AddHeight,Point.y):Smoke(SmokeColor)
end
return self
end
function ZONE_RADIUS:FlareZone(FlareColor,Points,Azimuth,AddHeight)
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
COORDINATE:New(Point.x,AddHeight,Point.y):Flare(FlareColor,Azimuth)
end
return self
end
function ZONE_RADIUS:GetRadius()
return self.Radius
end
function ZONE_RADIUS:SetRadius(Radius)
self.Radius=Radius
return self.Radius
end
function ZONE_RADIUS:GetVec2()
return self.Vec2
end
function ZONE_RADIUS:SetVec2(Vec2)
self.Vec2=Vec2
return self.Vec2
end
function ZONE_RADIUS:GetVec3(Height)
Height=Height or 0
local Vec2=self:GetVec2()
local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y}
return Vec3
end
function ZONE_RADIUS:Scan(ObjectCategories,UnitCategories)
self.ScanData={}
self.ScanData.Coalitions={}
self.ScanData.Scenery={}
self.ScanData.SceneryTable={}
self.ScanData.Units={}
local ZoneCoord=self:GetCoordinate()
local ZoneRadius=self:GetRadius()
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=ZoneCoord:GetVec3(),
radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneObject)
if ZoneObject then
local ObjectCategory=Object.getCategory(ZoneObject)
if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then
local CoalitionDCSUnit=ZoneObject:getCoalition()
local Include=false
if not UnitCategories then
Include=true
else
local CategoryDCSUnit=ZoneObject:getDesc().category
for UnitCategoryID,UnitCategory in pairs(UnitCategories)do
if UnitCategory==CategoryDCSUnit then
Include=true
break
end
end
end
if Include then
local CoalitionDCSUnit=ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit]=true
self.ScanData.Units[ZoneObject]=ZoneObject
end
end
if ObjectCategory==Object.Category.SCENERY then
local SceneryType=ZoneObject:getTypeName()
local SceneryName=ZoneObject:getName()
self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{}
self.ScanData.Scenery[SceneryType][SceneryName]=SCENERY:Register(tostring(SceneryName),ZoneObject)
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
end
end
return true
end
world.searchObjects(ObjectCategories,SphereSearch,EvaluateZone)
end
function ZONE_RADIUS:RemoveJunk()
local radius=self.Radius
local vec3=self:GetVec3()
local volS={
id=world.VolumeType.SPHERE,
params={point=vec3,radius=radius}
}
local n=world.removeJunk(volS)
return n
end
function ZONE_RADIUS:GetScannedUnits()
return self.ScanData.Units
end
function ZONE_RADIUS:GetScannedSetUnit()
local SetUnit=SET_UNIT:New()
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
SetUnit:AddUnit(FoundUnit)
else
local FoundStatic=STATIC:FindByName(UnitObject:getName(),false)
if FoundStatic then
SetUnit:AddUnit(FoundStatic)
end
end
end
end
end
return SetUnit
end
function ZONE_RADIUS:GetScannedSetGroup()
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New()
self.ScanSetGroup.Set={}
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
local group=FoundUnit:GetGroup()
self.ScanSetGroup:AddGroup(group)
end
end
end
end
return self.ScanSetGroup
end
function ZONE_RADIUS:CountScannedCoalitions()
local Count=0
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
end
return Count
end
function ZONE_RADIUS:CheckScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
end
return nil
end
function ZONE_RADIUS:GetScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
else
local Count=0
local ReturnCoalition=nil
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
ReturnCoalition=CoalitionID
end
if Count~=1 then
ReturnCoalition=nil
end
return ReturnCoalition
end
end
function ZONE_RADIUS:GetScannedSceneryType(SceneryType)
return self.ScanData.Scenery[SceneryType]
end
function ZONE_RADIUS:GetScannedScenery()
return self.ScanData.Scenery
end
function ZONE_RADIUS:GetScannedSceneryObjects()
return self.ScanData.SceneryTable
end
function ZONE_RADIUS:GetScannedSetScenery()
local scenery=SET_SCENERY:New()
local objects=self:GetScannedSceneryObjects()
for _,_obj in pairs(objects)do
scenery:AddScenery(_obj)
end
return scenery
end
function ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==nil
end
function ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()>1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition)
return self:GetScannedCoalition(Coalition)==nil
end
function ZONE_RADIUS:IsNoneInZone()
return self:CountScannedCoalitions()==0
end
function ZONE_RADIUS:SearchZone(EvaluateFunction,ObjectCategories)
local SearchZoneResult=true
local ZoneCoord=self:GetCoordinate()
local ZoneRadius=self:GetRadius()
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=ZoneCoord:GetVec3(),
radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneDCSUnit)
local ZoneUnit=UNIT:Find(ZoneDCSUnit)
return EvaluateFunction(ZoneUnit)
end
world.searchObjects(Object.Category.UNIT,SphereSearch,EvaluateZone)
end
function ZONE_RADIUS:IsVec2InZone(Vec2)
if not Vec2 then return false end
local ZoneVec2=self:GetVec2()
if ZoneVec2 then
if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then
return true
end
end
return false
end
function ZONE_RADIUS:IsVec3InZone(Vec3)
if not Vec3 then return false end
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
function ZONE_RADIUS:GetRandomVec2(inner,outer,surfacetypes)
local Vec2=self:GetVec2()
local _inner=inner or 0
local _outer=outer or self:GetRadius()
if surfacetypes and type(surfacetypes)~="table"then
surfacetypes={surfacetypes}
end
local function _getpoint()
local point={}
local angle=math.random()*math.pi*2
point.x=Vec2.x+math.cos(angle)*math.random(_inner,_outer)
point.y=Vec2.y+math.sin(angle)*math.random(_inner,_outer)
return point
end
local function _checkSurface(point)
local stype=land.getSurfaceType(point)
for _,sf in pairs(surfacetypes)do
if sf==stype then
return true
end
end
return false
end
local point=_getpoint()
if surfacetypes then
local N=1;local Nmax=100;local gotit=false
while gotit==false and N<=Nmax do
gotit=_checkSurface(point)
if gotit then
else
point=_getpoint()
N=N+1
end
end
end
return point
end
function ZONE_RADIUS:GetRandomPointVec2(inner,outer)
local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer))
return PointVec2
end
function ZONE_RADIUS:GetRandomVec3(inner,outer)
local Vec2=self:GetRandomVec2(inner,outer)
return{x=Vec2.x,y=self.y,z=Vec2.y}
end
function ZONE_RADIUS:GetRandomPointVec3(inner,outer)
local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer))
return PointVec3
end
function ZONE_RADIUS:GetRandomCoordinate(inner,outer,surfacetypes)
local vec2=self:GetRandomVec2(inner,outer,surfacetypes)
local Coordinate=COORDINATE:NewFromVec2(vec2)
return Coordinate
end
function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,markbuildings,markfinal)
local dist=distance or 100
local objects={}
if self.ScanData and self.ScanData.Scenery then
objects=self:GetScannedScenery()
else
self:Scan({Object.Category.SCENERY})
objects=self:GetScannedScenery()
end
local T0=timer.getTime()
local T1=timer.getTime()
local buildings={}
local buildingzones={}
if self.ScanData and self.ScanData.BuildingCoordinates then
buildings=self.ScanData.BuildingCoordinates
buildingzones=self.ScanData.BuildingZones
else
for _,_object in pairs(objects)do
for _,_scen in pairs(_object)do
local scenery=_scen
local description=scenery:GetDesc()
if description and description.attributes and description.attributes.Buildings then
if markbuildings then
MARKER:New(scenery:GetCoordinate(),"Building"):ToAll()
end
buildings[#buildings+1]=scenery:GetCoordinate()
local bradius=scenery:GetBoundingRadius()or dist
local bzone=ZONE_RADIUS:New("Building-"..math.random(1,100000),scenery:GetVec2(),bradius,false)
buildingzones[#buildingzones+1]=bzone
end
end
end
self.ScanData.BuildingCoordinates=buildings
self.ScanData.BuildingZones=buildingzones
end
local rcoord=nil
local found=true
local iterations=0
for i=1,1000 do
iterations=iterations+1
rcoord=self:GetRandomCoordinate(inner,outer)
found=true
for _,_coord in pairs(buildingzones)do
local zone=_coord
if zone:IsPointVec2InZone(rcoord)then
found=false
break
end
end
if found then
if markfinal then
MARKER:New(rcoord,"FREE"):ToAll()
end
break
end
end
if not found then
local rcoord=nil
local found=true
local iterations=0
for i=1,1000 do
iterations=iterations+1
rcoord=self:GetRandomCoordinate(inner,outer)
found=true
for _,_coord in pairs(buildings)do
local coord=_coord
if coord:Get3DDistance(rcoord)<dist then
found=false
end
end
if found then
if markfinal then
MARKER:New(rcoord,"FREE"):ToAll()
end
break
end
end
end
T1=timer.getTime()
if found then return rcoord else return nil end
end
ZONE={
ClassName="ZONE",
}
function ZONE:New(ZoneName)
local zone=_DATABASE:FindZone(ZoneName)
if zone then
return zone
end
local Zone=trigger.misc.getZone(ZoneName)
if not Zone then
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.Color={1,0,0,0.15}
self.Zone=Zone
return self
end
function ZONE:FindByName(ZoneName)
local ZoneFound=_DATABASE:FindZone(ZoneName)
return ZoneFound
end
ZONE_UNIT={
ClassName="ZONE_UNIT",
}
function ZONE_UNIT:New(ZoneName,ZoneUNIT,Radius,Offset)
if Offset then
if(Offset.dx or Offset.dy)and(Offset.rho or Offset.theta)then
error("Cannot use (dx, dy) with (rho, theta)")
end
end
local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneUNIT:GetVec2(),Radius,true))
if Offset then
self.dy=Offset.dy or 0.0
self.dx=Offset.dx or 0.0
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.ZoneUNIT=ZoneUNIT
self.LastVec2=ZoneUNIT:GetVec2()
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_UNIT:GetVec2()
local ZoneVec2=self.ZoneUNIT:GetVec2()
if ZoneVec2 then
local heading
if self.relative_to_unit then
heading=(self.ZoneUNIT:GetHeading()or 0.0)*math.pi/180.0
else
heading=0.0
end
if(self.dx or self.dy)then
ZoneVec2.x=ZoneVec2.x+self.dx*math.cos(-heading)+self.dy*math.sin(-heading)
ZoneVec2.y=ZoneVec2.y-self.dx*math.sin(-heading)+self.dy*math.cos(-heading)
end
if(self.rho or self.theta)then
ZoneVec2.x=ZoneVec2.x+self.rho*math.cos(self.theta+heading)
ZoneVec2.y=ZoneVec2.y+self.rho*math.sin(self.theta+heading)
end
self.LastVec2=ZoneVec2
return ZoneVec2
else
return self.LastVec2
end
return nil
end
function ZONE_UNIT:GetRandomVec2()
local RandomVec2={}
local Vec2=self:GetVec2()
if not Vec2 then
Vec2=self.LastVec2
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();
return RandomVec2
end
function ZONE_UNIT:GetVec3(Height)
Height=Height or 0
local Vec2=self:GetVec2()
local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y}
return Vec3
end
ZONE_GROUP={
ClassName="ZONE_GROUP",
}
function ZONE_GROUP:New(ZoneName,ZoneGROUP,Radius)
local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneGROUP:GetVec2(),Radius,true))
self._.ZoneGROUP=ZoneGROUP
self._.ZoneVec2Cache=self._.ZoneGROUP:GetVec2()
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_GROUP:GetVec2()
local ZoneVec2=nil
if self._.ZoneGROUP:IsAlive()then
ZoneVec2=self._.ZoneGROUP:GetVec2()
self._.ZoneVec2Cache=ZoneVec2
else
ZoneVec2=self._.ZoneVec2Cache
end
return ZoneVec2
end
function ZONE_GROUP:GetRandomVec2()
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();
return Point
end
function ZONE_GROUP:GetRandomPointVec2(inner,outer)
local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec2
end
_ZONE_TRIANGLE={
ClassName="ZONE_TRIANGLE",
Points={},
Coords={},
CenterVec2={x=0,y=0},
SurfaceArea=0,
DrawID={}
}
function _ZONE_TRIANGLE:New(p1,p2,p3)
local self=BASE:Inherit(self,ZONE_BASE:New())
self.Points={p1,p2,p3}
local center_x=(p1.x+p2.x+p3.x)/3
local center_y=(p1.y+p2.y+p3.y)/3
self.CenterVec2={x=center_x,y=center_y}
for _,pt in pairs({p1,p2,p3})do
table.add(self.Coords,COORDINATE:NewFromVec2(pt))
end
self.SurfaceArea=math.abs((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))*0.5
return self
end
function _ZONE_TRIANGLE:ContainsPoint(pt,points)
points=points or self.Points
local function sign(p1,p2,p3)
return(p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y)
end
local d1=sign(pt,self.Points[1],self.Points[2])
local d2=sign(pt,self.Points[2],self.Points[3])
local d3=sign(pt,self.Points[3],self.Points[1])
local has_neg=(d1<0)or(d2<0)or(d3<0)
local has_pos=(d1>0)or(d2>0)or(d3>0)
return not(has_neg and has_pos)
end
function _ZONE_TRIANGLE:GetRandomVec2(points)
points=points or self.Points
local pt={math.random(),math.random()}
table.sort(pt)
local s=pt[1]
local t=pt[2]-pt[1]
local u=1-pt[2]
return{x=s*points[1].x+t*points[2].x+u*points[3].x,
y=s*points[1].y+t*points[2].y+u*points[3].y}
end
function _ZONE_TRIANGLE:Draw(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
Coalition=Coalition or-1
Color=Color or{1,0,0}
Alpha=Alpha or 1
FillColor=FillColor or Color
if not FillColor then UTILS.DeepCopy(Color)end
FillAlpha=FillAlpha or Alpha
if not FillAlpha then FillAlpha=1 end
for i=1,#self.Coords do
local c1=self.Coords[i]
local c2=self.Coords[i%#self.Coords+1]
local id=c1:LineToAll(c2,Coalition,Color,Alpha,LineType,ReadOnly)
self.DrawID[#self.DrawID+1]=id
end
local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
self.DrawID[#self.DrawID+1]=newID
return self.DrawID
end
function _ZONE_TRIANGLE:Fill(Coalition,FillColor,FillAlpha,ReadOnly)
Coalition=Coalition or-1
FillColor=FillColor
FillAlpha=FillAlpha
local newID=self.Coords[1]:MarkupToAllFreeForm({self.Coords[2],self.Coords[3]},Coalition,nil,nil,FillColor,FillAlpha,0,nil)
self.DrawID[#self.DrawID+1]=newID
return self.DrawID
end
ZONE_POLYGON_BASE={
ClassName="ZONE_POLYGON_BASE",
_Triangles={},
SurfaceArea=0,
DrawID={},
FillTriangles={},
Borderlines={},
}
function ZONE_POLYGON_BASE:New(ZoneName,PointsArray)
local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
if PointsArray then
self._.Polygon={}
for i=1,#PointsArray do
self._.Polygon[i]={}
self._.Polygon[i].x=PointsArray[i].x
self._.Polygon[i].y=PointsArray[i].y
end
self._Triangles=self:_Triangulate()
self.SurfaceArea=self:_CalculateSurfaceArea()
end
return self
end
function ZONE_POLYGON_BASE:_Triangulate()
local points=self._.Polygon
local triangles={}
local function get_orientation(shape_points)
local sum=0
for i=1,#shape_points do
local j=i%#shape_points+1
sum=sum+(shape_points[j].x-shape_points[i].x)*(shape_points[j].y+shape_points[i].y)
end
return sum>=0 and"clockwise"or"counter-clockwise"
end
local function ensure_clockwise(shape_points)
local orientation=get_orientation(shape_points)
if orientation=="counter-clockwise"then
local reversed={}
for i=#shape_points,1,-1 do
table.insert(reversed,shape_points[i])
end
return reversed
end
return shape_points
end
local function is_clockwise(p1,p2,p3)
local cross_product=(p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x)
return cross_product<0
end
local function divide_recursively(shape_points)
if#shape_points==3 then
table.insert(triangles,_ZONE_TRIANGLE:New(shape_points[1],shape_points[2],shape_points[3]))
elseif#shape_points>3 then
for i,p1 in ipairs(shape_points)do
local p2=shape_points[(i%#shape_points)+1]
local p3=shape_points[(i+1)%#shape_points+1]
local triangle=_ZONE_TRIANGLE:New(p1,p2,p3)
local is_ear=true
if not is_clockwise(p1,p2,p3)then
is_ear=false
else
for _,point in ipairs(shape_points)do
if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
is_ear=false
break
end
end
end
if is_ear then
local is_valid_triangle=true
for _,point in ipairs(points)do
if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
is_valid_triangle=false
break
end
end
if is_valid_triangle then
table.insert(triangles,triangle)
local remaining_points={}
for j,point in ipairs(shape_points)do
if point~=p2 then
table.insert(remaining_points,point)
end
end
divide_recursively(remaining_points)
break
end
else
end
end
end
end
points=ensure_clockwise(points)
divide_recursively(points)
return triangles
end
function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array)
self._.Polygon={}
for i=1,#Vec2Array do
self._.Polygon[i]={}
self._.Polygon[i].x=Vec2Array[i].x
self._.Polygon[i].y=Vec2Array[i].y
end
self._Triangles=self:_Triangulate()
self.SurfaceArea=self:_CalculateSurfaceArea()
return self
end
function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array)
self._.Polygon={}
for i=1,#Vec3Array do
self._.Polygon[i]={}
self._.Polygon[i].x=Vec3Array[i].x
self._.Polygon[i].y=Vec3Array[i].z
end
self._Triangles=self:_Triangulate()
self.SurfaceArea=self:_CalculateSurfaceArea()
return self
end
function ZONE_POLYGON_BASE:_CalculateSurfaceArea()
local area=0
for _,triangle in pairs(self._Triangles)do
area=area+triangle.SurfaceArea
end
return area
end
function ZONE_POLYGON_BASE:GetVec2()
local Bounds=self:GetBoundingSquare()
return{x=(Bounds.x2+Bounds.x1)/2,y=(Bounds.y2+Bounds.y1)/2}
end
function ZONE_POLYGON_BASE:GetVertexVec2(Index)
return self._.Polygon[Index or 1]
end
function ZONE_POLYGON_BASE:GetVertexVec3(Index)
local vec2=self:GetVertexVec2(Index)
if vec2 then
local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y}
return vec3
end
return nil
end
function ZONE_POLYGON_BASE:GetVertexCoordinate(Index)
local vec2=self:GetVertexVec2(Index)
if vec2 then
local coord=COORDINATE:NewFromVec2(vec2)
return coord
end
return nil
end
function ZONE_POLYGON_BASE:GetVerticiesVec2()
return self._.Polygon
end
function ZONE_POLYGON_BASE:GetVerticiesVec3()
local coords={}
for i,vec2 in ipairs(self._.Polygon)do
local vec3={x=vec2.x,y=land.getHeight(vec2),z=vec2.y}
table.insert(coords,vec3)
end
return coords
end
function ZONE_POLYGON_BASE:GetVerticiesCoordinates()
local coords={}
for i,vec2 in ipairs(self._.Polygon)do
local coord=COORDINATE:NewFromVec2(vec2)
table.insert(coords,coord)
end
return coords
end
function ZONE_POLYGON_BASE:Flush()
return self
end
function ZONE_POLYGON_BASE:BoundZone(UnBound)
local i
local j
local Segments=10
i=1
j=#self._.Polygon
while i<=#self._.Polygon do
local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x
local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y
for Segment=0,Segments do
local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments)
local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments)
local Tire={
["country"]="USA",
["category"]="Fortifications",
["canCargo"]=false,
["shape_name"]="H-tyre_B_WF",
["type"]="Black_Tyre_WF",
["y"]=PointY,
["x"]=PointX,
["name"]=string.format("%s-Tire #%0d",self:GetName(),((i-1)*Segments)+Segment),
["heading"]=0,
}
local Group=coalition.addStaticObject(country.id.USA,Tire)
if UnBound and UnBound==true then
Group:destroy()
end
end
j=i
i=i+1
end
return self
end
function ZONE_POLYGON_BASE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,IncludeTriangles)
if self._.Polygon and#self._.Polygon>=3 then
Coalition=Coalition or self:GetDrawCoalition()
self:SetDrawCoalition(Coalition)
Color=Color or self:GetColorRGB()
Alpha=Alpha or self:GetColorAlpha()
FillColor=FillColor or self:GetFillColorRGB()
FillAlpha=FillAlpha or self:GetFillColorAlpha()
if FillColor then
self:ReFill(FillColor,FillAlpha)
end
if Color then
self:ReDrawBorderline(Color,Alpha,LineType)
end
end
if false then
local coords=self:GetVerticiesCoordinates()
local coord=coords[1]
table.remove(coords,1)
coord:MarkupToAllFreeForm(coords,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,"Drew Polygon")
if true then
return
end
end
return self
end
function ZONE_POLYGON_BASE:ReFill(Color,Alpha)
local color=Color or self:GetFillColorRGB()or{1,0,0}
local alpha=Alpha or self:GetFillColorAlpha()or 1
local coalition=self:GetDrawCoalition()or-1
if#self.FillTriangles>0 then
for _,triangle in pairs(self._Triangles)do
triangle:UndrawZone()
end
for _,_value in pairs(self.FillTriangles)do
table.remove_by_value(self.DrawID,_value)
end
self.FillTriangles=nil
self.FillTriangles={}
end
for _,triangle in pairs(self._Triangles)do
local draw_ids=triangle:Fill(coalition,color,alpha,nil)
self.FillTriangles=draw_ids
table.combine(self.DrawID,draw_ids)
end
return self
end
function ZONE_POLYGON_BASE:ReDrawBorderline(Color,Alpha,LineType)
local color=Color or self:GetFillColorRGB()or{1,0,0}
local alpha=Alpha or self:GetFillColorAlpha()or 1
local coalition=self:GetDrawCoalition()or-1
local linetype=LineType or 1
if#self.Borderlines>0 then
for _,MarkID in pairs(self.Borderlines)do
trigger.action.removeMark(MarkID)
end
for _,_value in pairs(self.Borderlines)do
table.remove_by_value(self.DrawID,_value)
end
self.Borderlines=nil
self.Borderlines={}
end
local coords=self:GetVerticiesCoordinates()
for i=1,#coords do
local c1=coords[i]
local c2=coords[i%#coords+1]
local newID=c1:LineToAll(c2,coalition,color,alpha,linetype,nil)
self.DrawID[#self.DrawID+1]=newID
self.Borderlines[#self.Borderlines+1]=newID
end
return self
end
function ZONE_POLYGON_BASE:GetSurfaceArea()
return self.SurfaceArea
end
function ZONE_POLYGON_BASE:GetRadius()
local center=self:GetVec2()
local radius=0
for _,_vec2 in pairs(self._.Polygon)do
local vec2=_vec2
local r=UTILS.VecDist2D(center,vec2)
if r>radius then
radius=r
end
end
return radius
end
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName,DoNotRegisterZone)
local center=self:GetVec2()
local radius=self:GetRadius()
local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName,center,radius,DoNotRegisterZone)
return zone
end
function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName,DoNotRegisterZone)
local vec1,vec3=self:GetBoundingVec2()
local vec2={x=vec1.x,y=vec3.y}
local vec4={x=vec3.x,y=vec1.y}
local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName,{vec1,vec2,vec3,vec4})
return zone
end
function ZONE_POLYGON_BASE:RemoveJunk(Height)
Height=Height or 1000
local vec2SW,vec2NE=self:GetBoundingVec2()
local vec3SW={x=vec2SW.x,y=-Height,z=vec2SW.y}
local vec3NE={x=vec2NE.x,y=Height,z=vec2NE.y}
local volume={
id=world.VolumeType.BOX,
params={
min=vec3SW,
max=vec3SW
}
}
local n=world.removeJunk(volume)
return n
end
function ZONE_POLYGON_BASE:SmokeZone(SmokeColor,Segments)
Segments=Segments or 10
local i=1
local j=#self._.Polygon
while i<=#self._.Polygon do
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)
COORDINATE:New(PointX,0,PointY):Smoke(SmokeColor)
end
j=i
i=i+1
end
return self
end
function ZONE_POLYGON_BASE:FlareZone(FlareColor,Segments,Azimuth,AddHeight)
Segments=Segments or 10
AddHeight=AddHeight or 0
local i=1
local j=#self._.Polygon
while i<=#self._.Polygon do
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)
COORDINATE:New(PointX,AddHeight,PointY):Flare(FlareColor,Azimuth)
end
j=i
i=i+1
end
return self
end
function ZONE_POLYGON_BASE:IsVec2InZone(Vec2)
if not Vec2 then return false end
local Next
local Prev
local InPolygon=false
Next=1
Prev=#self._.Polygon
while Next<=#self._.Polygon do
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
Prev=Next
Next=Next+1
end
return InPolygon
end
function ZONE_POLYGON_BASE:IsVec3InZone(Vec3)
if not Vec3 then return false end
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
function ZONE_POLYGON_BASE:GetRandomVec2()
local weights={}
for _,triangle in pairs(self._Triangles)do
weights[triangle]=triangle.SurfaceArea/self.SurfaceArea
end
local random_weight=math.random()
local accumulated_weight=0
for triangle,weight in pairs(weights)do
accumulated_weight=accumulated_weight+weight
if accumulated_weight>=random_weight then
return triangle:GetRandomVec2()
end
end
end
function ZONE_POLYGON_BASE:GetRandomPointVec2()
local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec2
end
function ZONE_POLYGON_BASE:GetRandomPointVec3()
local PointVec3=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec3
end
function ZONE_POLYGON_BASE:GetRandomCoordinate()
local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2())
return Coordinate
end
function ZONE_POLYGON_BASE:GetBoundingSquare()
local x1=self._.Polygon[1].x
local y1=self._.Polygon[1].y
local x2=self._.Polygon[1].x
local y2=self._.Polygon[1].y
for i=2,#self._.Polygon do
x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1
x2=(x2<self._.Polygon[i].x)and self._.Polygon[i].x or x2
y1=(y1>self._.Polygon[i].y)and self._.Polygon[i].y or y1
y2=(y2<self._.Polygon[i].y)and self._.Polygon[i].y or y2
end
return{x1=x1,y1=y1,x2=x2,y2=y2}
end
function ZONE_POLYGON_BASE:GetBoundingVec2()
local x1=self._.Polygon[1].x
local y1=self._.Polygon[1].y
local x2=self._.Polygon[1].x
local y2=self._.Polygon[1].y
for i=2,#self._.Polygon do
x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1
x2=(x2<self._.Polygon[i].x)and self._.Polygon[i].x or x2
y1=(y1>self._.Polygon[i].y)and self._.Polygon[i].y or y1
y2=(y2<self._.Polygon[i].y)and self._.Polygon[i].y or y2
end
local vec1={x=x1,y=y1}
local vec2={x=x2,y=y2}
return vec1,vec2
end
function ZONE_POLYGON_BASE:Boundary(Coalition,Color,Radius,Alpha,Segments,Closed)
Coalition=Coalition or-1
Color=Color or{1,1,1}
Radius=Radius or 1000
Alpha=Alpha or 1
Segments=Segments or 10
Closed=Closed or false
local Limit
local i=1
local j=#self._.Polygon
if(Closed)then
Limit=#self._.Polygon+1
else
Limit=#self._.Polygon
end
while i<=#self._.Polygon do
if j~=Limit then
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)
end
end
j=i
i=i+1
end
return self
end
do
ZONE_POLYGON={
ClassName="ZONE_POLYGON",
}
function ZONE_POLYGON:New(ZoneName,ZoneGroup)
local GroupPoints=ZoneGroup:GetTaskRoute()
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,GroupPoints))
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_POLYGON:NewFromPointsArray(ZoneName,PointsArray)
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,PointsArray))
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
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))
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_POLYGON:NewFromDrawing(DrawingName)
local points={}
for _,layer in pairs(env.mission.drawings.layers)do
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
local half_height=object["height"]/2
local center={x=object["mapX"],y=object["mapY"]}
local p1=UTILS.RotatePointAroundPivot({x=center.x-half_height,y=center.y+half_width},center,angle)
local p2=UTILS.RotatePointAroundPivot({x=center.x+half_height,y=center.y+half_width},center,angle)
local p3=UTILS.RotatePointAroundPivot({x=center.x+half_height,y=center.y-half_width},center,angle)
local p4=UTILS.RotatePointAroundPivot({x=center.x-half_height,y=center.y-half_width},center,angle)
points={p1,p2,p3,p4}
else
end
end
end
end
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(DrawingName,points))
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_POLYGON:FindByName(ZoneName)
local ZoneFound=_DATABASE:FindZone(ZoneName)
return ZoneFound
end
function ZONE_POLYGON:Scan(ObjectCategories,UnitCategories)
self.ScanData={}
self.ScanData.Coalitions={}
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 CenterVec3=self:GetCoordinate():GetVec3()
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=CenterVec3,
radius=ZoneRadius,
}
}
local function EvaluateZone(ZoneObject)
if ZoneObject then
local ObjectCategory=Object.getCategory(ZoneObject)
if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then
local CoalitionDCSUnit=ZoneObject:getCoalition()
local Include=false
if not UnitCategories then
Include=true
else
local CategoryDCSUnit=ZoneObject:getDesc().category
for UnitCategoryID,UnitCategory in pairs(UnitCategories)do
if UnitCategory==CategoryDCSUnit then
Include=true
break
end
end
end
if Include then
local CoalitionDCSUnit=ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit]=true
self.ScanData.Units[ZoneObject]=ZoneObject
end
end
if ObjectCategory==Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint())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])
end
end
return true
end
local inzoneunits=SET_UNIT:New():FilterZones({self}):FilterOnce()
local inzonestatics=SET_STATIC:New():FilterZones({self}):FilterOnce()
inzoneunits:ForEach(
function(unit)
local Unit=unit
local DCS=Unit:GetDCSObject()
EvaluateZone(DCS)
end
)
inzonestatics:ForEach(
function(static)
local Static=static
local DCS=Static:GetDCSObject()
EvaluateZone(DCS)
end
)
local searchscenery=false
for _,_type in pairs(ObjectCategories)do
if _type==Object.Category.SCENERY then
searchscenery=true
end
end
if searchscenery then
world.searchObjects({Object.Category.SCENERY},SphereSearch,EvaluateZone)
end
end
function ZONE_POLYGON:GetScannedUnits()
return self.ScanData.Units
end
function ZONE_POLYGON:GetScannedSetUnit()
local SetUnit=SET_UNIT:New()
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
SetUnit:AddUnit(FoundUnit)
else
local FoundStatic=STATIC:FindByName(UnitObject:getName())
if FoundStatic then
SetUnit:AddUnit(FoundStatic)
end
end
end
end
end
return SetUnit
end
function ZONE_POLYGON:GetScannedSetGroup()
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New()
self.ScanSetGroup.Set={}
if self.ScanData then
for ObjectID,UnitObject in pairs(self.ScanData.Units)do
local UnitObject=UnitObject
if UnitObject:isExist()then
local FoundUnit=UNIT:FindByName(UnitObject:getName())
if FoundUnit then
local group=FoundUnit:GetGroup()
self.ScanSetGroup:AddGroup(group)
end
end
end
end
return self.ScanSetGroup
end
function ZONE_POLYGON:CountScannedCoalitions()
local Count=0
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
end
return Count
end
function ZONE_POLYGON:CheckScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
end
return nil
end
function ZONE_POLYGON:GetScannedCoalition(Coalition)
if Coalition then
return self.ScanData.Coalitions[Coalition]
else
local Count=0
local ReturnCoalition=nil
for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do
Count=Count+1
ReturnCoalition=CoalitionID
end
if Count~=1 then
ReturnCoalition=nil
end
return ReturnCoalition
end
end
function ZONE_POLYGON:GetScannedSceneryType(SceneryType)
return self.ScanData.Scenery[SceneryType]
end
function ZONE_POLYGON:GetScannedSceneryObjects()
return self.ScanData.SceneryTable
end
function ZONE_POLYGON:GetScannedScenery()
return self.ScanData.Scenery
end
function ZONE_POLYGON:GetScannedSetScenery()
local scenery=SET_SCENERY:New()
local objects=self:GetScannedSceneryObjects()
for _,_obj in pairs(objects)do
scenery:AddScenery(_obj)
end
return scenery
end
function ZONE_POLYGON:IsAllInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_POLYGON:IsAllInZoneOfOtherCoalition(Coalition)
return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==nil
end
function ZONE_POLYGON:IsSomeInZoneOfCoalition(Coalition)
return self:CountScannedCoalitions()>1 and self:GetScannedCoalition(Coalition)==true
end
function ZONE_POLYGON:IsNoneInZoneOfCoalition(Coalition)
return self:GetScannedCoalition(Coalition)==nil
end
function ZONE_POLYGON:IsNoneInZone()
return self:CountScannedCoalitions()==0
end
end
do
ZONE_ELASTIC={
ClassName="ZONE_ELASTIC",
points={},
setGroups={}
}
function ZONE_ELASTIC:New(ZoneName,Points)
local self=BASE:Inherit(self,ZONE_POLYGON_BASE:New(ZoneName,Points))
_EVENTDISPATCHER:CreateEventNewZone(self)
if Points then
self.points=Points
end
return self
end
function ZONE_ELASTIC:AddVertex2D(Vec2)
table.insert(self.points,Vec2)
return self
end
function ZONE_ELASTIC: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
end
function ZONE_ELASTIC:AddSetGroup(GroupSet)
table.insert(self.setGroups,GroupSet)
return self
end
function ZONE_ELASTIC:Update(Delay,Draw)
local points=UTILS.DeepCopy(self.points or{})
if self.setGroups then
for _,_setGroup in pairs(self.setGroups)do
local setGroup=_setGroup
for _,_group in pairs(setGroup.Set)do
local group=_group
if group and group:IsAlive()then
table.insert(points,group:GetVec2())
end
end
end
end
self._.Polygon=self:_ConvexHull(points)
self._Triangles=self:_Triangulate()
self.SurfaceArea=self:_CalculateSurfaceArea()
if Draw~=false then
if self.DrawID or Draw==true then
self:UndrawZone()
self:DrawZone()
end
end
return self
end
function ZONE_ELASTIC:StartUpdate(Tstart,dT,Tstop,Draw)
self.updateID=self:ScheduleRepeat(Tstart,dT,0,Tstop,ZONE_ELASTIC.Update,self,0,Draw)
return self
end
function ZONE_ELASTIC:StopUpdate(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,ZONE_ELASTIC.StopUpdate,self)
else
if self.updateID then
self:ScheduleStop(self.updateID)
self.updateID=nil
end
end
return self
end
function ZONE_ELASTIC:_ConvexHull(pl)
if#pl==0 then
return{}
end
table.sort(pl,function(left,right)
return left.x<right.x
end)
local h={}
local function ccw(a,b,c)
return(b.x-a.x)*(c.y-a.y)>(b.y-a.y)*(c.x-a.x)
end
for i,pt in pairs(pl)do
while#h>=2 and not ccw(h[#h-1],h[#h],pt)do
table.remove(h,#h)
end
table.insert(h,pt)
end
local t=#h+1
for i=#pl,1,-1 do
local pt=pl[i]
while#h>=t and not ccw(h[#h-1],h[#h],pt)do
table.remove(h,#h)
end
table.insert(h,pt)
end
table.remove(h,#h)
return h
end
end
ZONE_OVAL={
ClassName="OVAL",
ZoneName="",
MajorAxis=nil,
MinorAxis=nil,
Angle=0,
DrawPoly=nil
}
function ZONE_OVAL:New(name,vec2,major_axis,minor_axis,angle)
self=BASE:Inherit(self,ZONE_BASE:New())
self.ZoneName=name
self.CenterVec2=vec2
self.MajorAxis=major_axis
self.MinorAxis=minor_axis
self.Angle=angle or 0
_DATABASE:AddZone(name,self)
return self
end
function ZONE_OVAL:NewFromDrawing(DrawingName)
self=BASE:Inherit(self,ZONE_BASE:New(DrawingName))
for _,layer in pairs(env.mission.drawings.layers)do
for _,object in pairs(layer["objects"])do
if string.find(object["name"],DrawingName,1,true)then
if object["polygonMode"]=="oval"then
self.CenterVec2={x=object["mapX"],y=object["mapY"]}
self.MajorAxis=object["r1"]
self.MinorAxis=object["r2"]
self.Angle=object["angle"]
end
end
end
end
_DATABASE:AddZone(DrawingName,self)
return self
end
function ZONE_OVAL:GetMajorAxis()
return self.MajorAxis
end
function ZONE_OVAL:GetMinorAxis()
return self.MinorAxis
end
function ZONE_OVAL:GetAngle()
return self.Angle
end
function ZONE_OVAL:GetVec2()
return self.CenterVec2
end
function ZONE_OVAL:IsVec2InZone(vec2)
local cos,sin=math.cos,math.sin
local dx=vec2.x-self.CenterVec2.x
local dy=vec2.y-self.CenterVec2.y
local rx=dx*cos(self.Angle)+dy*sin(self.Angle)
local ry=-dx*sin(self.Angle)+dy*cos(self.Angle)
return rx*rx/(self.MajorAxis*self.MajorAxis)+ry*ry/(self.MinorAxis*self.MinorAxis)<=1
end
function ZONE_OVAL:GetBoundingSquare()
local min_x=self.CenterVec2.x-self.MajorAxis
local min_y=self.CenterVec2.y-self.MinorAxis
local max_x=self.CenterVec2.x+self.MajorAxis
local max_y=self.CenterVec2.y+self.MinorAxis
return{
{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y}
}
end
function ZONE_OVAL:PointsOnEdge(num_points)
num_points=num_points or 40
local points={}
local dtheta=2*math.pi/num_points
for i=0,num_points-1 do
local theta=i*dtheta
local x=self.CenterVec2.x+self.MajorAxis*math.cos(theta)*math.cos(self.Angle)-self.MinorAxis*math.sin(theta)*math.sin(self.Angle)
local y=self.CenterVec2.y+self.MajorAxis*math.cos(theta)*math.sin(self.Angle)+self.MinorAxis*math.sin(theta)*math.cos(self.Angle)
table.insert(points,{x=x,y=y})
end
return points
end
function ZONE_OVAL:GetRandomVec2()
local theta=math.rad(self.Angle)
local random_point=math.sqrt(math.random())
local phi=math.random()*2*math.pi
local x_c=random_point*math.cos(phi)
local y_c=random_point*math.sin(phi)
local x_e=x_c*self.MajorAxis
local y_e=y_c*self.MinorAxis
local rx=(x_e*math.cos(theta)-y_e*math.sin(theta))+self.CenterVec2.x
local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y
return{x=rx,y=ry}
end
function ZONE_OVAL:GetRandomPointVec2()
return COORDINATE:NewFromVec2(self:GetRandomVec2())
end
function ZONE_OVAL:GetRandomPointVec3()
return COORDINATE:NewFromVec3(self:GetRandomVec2())
end
function ZONE_OVAL:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType)
Coalition=Coalition or self:GetDrawCoalition()
self:SetDrawCoalition(Coalition)
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
self:SetColor(Color,Alpha)
FillColor=FillColor or self:GetFillColorRGB()
if not FillColor then
UTILS.DeepCopy(Color)
end
FillAlpha=FillAlpha or self:GetFillColorAlpha()
if not FillAlpha then
FillAlpha=0.15
end
LineType=LineType or 1
self:SetFillColor(FillColor,FillAlpha)
self.DrawPoly=ZONE_POLYGON:NewFromPointsArray(self.ZoneName,self:PointsOnEdge(80))
self.DrawPoly:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType)
end
function ZONE_OVAL:UndrawZone()
if self.DrawPoly then
self.DrawPoly:UndrawZone()
end
end
do
ZONE_AIRBASE={
ClassName="ZONE_AIRBASE",
}
function ZONE_AIRBASE:New(AirbaseName,Radius)
Radius=Radius or 4000
local Airbase=AIRBASE:FindByName(AirbaseName)
local self=BASE:Inherit(self,ZONE_RADIUS:New(AirbaseName,Airbase:GetVec2(),Radius,true))
self._.ZoneAirbase=Airbase
self._.ZoneVec2Cache=self._.ZoneAirbase:GetVec2()
if Airbase:IsShip()then
self.isShip=true
self.isHelipad=false
self.isAirdrome=false
elseif Airbase:IsHelipad()then
self.isShip=false
self.isHelipad=true
self.isAirdrome=false
elseif Airbase:IsAirdrome()then
self.isShip=false
self.isHelipad=false
self.isAirdrome=true
end
_EVENTDISPATCHER:CreateEventNewZone(self)
return self
end
function ZONE_AIRBASE:GetAirbase()
return self._.ZoneAirbase
end
function ZONE_AIRBASE:GetVec2()
local ZoneVec2=nil
if self._.ZoneAirbase:IsAlive()then
ZoneVec2=self._.ZoneAirbase:GetVec2()
self._.ZoneVec2Cache=ZoneVec2
else
ZoneVec2=self._.ZoneVec2Cache
end
return ZoneVec2
end
function ZONE_AIRBASE:GetRandomPointVec2(inner,outer)
local PointVec2=COORDINATE:NewFromVec2(self:GetRandomVec2())
return PointVec2
end
end
ZONE_DETECTION={
ClassName="ZONE_DETECTION",
}
function ZONE_DETECTION:New(ZoneName,Detection,Radius)
local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName))
self:F({ZoneName,Detection,Radius})
self.Detection=Detection
self.Radius=Radius
return self
end
function ZONE_DETECTION:BoundZone(Points,CountryID,UnBound)
local Point={}
local Vec2=self:GetVec2()
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,(360/Points)do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
local CountryName=_DATABASE.COUNTRY_NAME[CountryID]
local Tire={
["country"]=CountryName,
["category"]="Fortifications",
["canCargo"]=false,
["shape_name"]="H-tyre_B_WF",
["type"]="Black_Tyre_WF",
["y"]=Point.y,
["x"]=Point.x,
["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle),
["heading"]=0,
}
local Group=coalition.addStaticObject(CountryID,Tire)
if UnBound and UnBound==true then
Group:destroy()
end
end
return self
end
function ZONE_DETECTION:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset)
self:F2(SmokeColor)
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
AngleOffset=AngleOffset or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=(Angle+AngleOffset)*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
COORDINATE:New(Point.x,AddHeight,Point.y):Smoke(SmokeColor)
end
return self
end
function ZONE_DETECTION:FlareZone(FlareColor,Points,Azimuth,AddHeight)
self:F2({FlareColor,Azimuth})
local Point={}
local Vec2=self:GetVec2()
AddHeight=AddHeight or 0
Points=Points and Points or 360
local Angle
local RadialBase=math.pi*2
for Angle=0,360,360/Points do
local Radial=Angle*RadialBase/360
Point.x=Vec2.x+math.cos(Radial)*self:GetRadius()
Point.y=Vec2.y+math.sin(Radial)*self:GetRadius()
COORDINATE:New(Point.x,AddHeight,Point.y):Flare(FlareColor,Azimuth)
end
return self
end
function ZONE_DETECTION:GetRadius()
self:F2(self.ZoneName)
self:T2({self.Radius})
return self.Radius
end
function ZONE_DETECTION:SetRadius(Radius)
self:F2(self.ZoneName)
self.Radius=Radius
self:T2({self.Radius})
return self.Radius
end
function ZONE_DETECTION:IsVec2InZone(Vec2)
self:F2(Vec2)
local Coordinates=self.Detection:GetDetectedItemCoordinates()
for CoordinateID,Coordinate in pairs(Coordinates)do
local ZoneVec2=Coordinate:GetVec2()
if ZoneVec2 then
if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then
return true
end
end
end
return false
end
function ZONE_DETECTION:IsVec3InZone(Vec3)
self:F2(Vec3)
local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z})
return InZone
end
DATABASE={
ClassName="DATABASE",
Templates={
Units={},
Groups={},
Statics={},
ClientsByName={},
ClientsByID={},
},
UNITS={},
UNITS_Index={},
STATICS={},
GROUPS={},
PLAYERS={},
PLAYERSJOINED={},
PLAYERUNITS={},
CLIENTS={},
CARGOS={},
AIRBASES={},
COUNTRY_ID={},
COUNTRY_NAME={},
NavPoints={},
PLAYERSETTINGS={},
ZONENAMES={},
HITS={},
DESTROYS={},
ZONES={},
ZONES_GOAL={},
WAREHOUSES={},
FLIGHTGROUPS={},
FLIGHTCONTROLS={},
OPSZONES={},
PATHLINES={},
STORAGES={},
STNS={},
SADL={},
DYNAMICCARGO={},
}
local _DATABASECoalition=
{
[1]="Red",
[2]="Blue",
[3]="Neutral",
}
local _DATABASECategory=
{
["plane"]=Unit.Category.AIRPLANE,
["helicopter"]=Unit.Category.HELICOPTER,
["vehicle"]=Unit.Category.GROUND_UNIT,
["ship"]=Unit.Category.SHIP,
["static"]=Unit.Category.STRUCTURE,
}
function DATABASE:New()
local self=BASE:Inherit(self,BASE:New())
self:SetEventPriority(1)
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventOnPlayerEnterUnit)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.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()
self:_RegisterStatics()
self.UNITS_Position=0
return self
end
function DATABASE:FindUnit(UnitName)
local UnitFound=self.UNITS[UnitName]
return UnitFound
end
function DATABASE:AddUnit(DCSUnitName,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]
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)
end
return self.STATICS[DCSStaticName]
end
function DATABASE:DeleteStatic(DCSStaticName)
self.STATICS[DCSStaticName]=nil
end
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)
end
return self.AIRBASES[AirbaseName]
end
function DATABASE:DeleteAirbase(AirbaseName)
self.AIRBASES[AirbaseName]=nil
end
function DATABASE:FindAirbase(AirbaseName)
local AirbaseFound=self.AIRBASES[AirbaseName]
return AirbaseFound
end
function DATABASE:AddStorage(AirbaseName)
if not self.STORAGES[AirbaseName]then
self.STORAGES[AirbaseName]=STORAGE:New(AirbaseName)
end
return self.STORAGES[AirbaseName]
end
function DATABASE:DeleteStorage(AirbaseName)
self.STORAGES[AirbaseName]=nil
end
function DATABASE:FindStorage(AirbaseName)
local storage=self.STORAGES[AirbaseName]
return storage
end
do
function DATABASE:FindZone(ZoneName)
local ZoneFound=self.ZONES[ZoneName]
return ZoneFound
end
function DATABASE:AddZone(ZoneName,Zone)
if not self.ZONES[ZoneName]then
self.ZONES[ZoneName]=Zone
end
end
function DATABASE:DeleteZone(ZoneName)
self.ZONES[ZoneName]=nil
end
function DATABASE:AddPathline(PathlineName,Pathline)
if not self.PATHLINES[PathlineName]then
self.PATHLINES[PathlineName]=Pathline
end
end
function DATABASE:FindPathline(PathlineName)
local pathline=self.PATHLINES[PathlineName]
return pathline
end
function DATABASE:DeletePathline(PathlineName)
self.PATHLINES[PathlineName]=nil
return self
end
function DATABASE:_RegisterZones()
for ZoneID,ZoneData in pairs(env.mission.triggers.zones)do
local ZoneName=ZoneData.name
local color=ZoneData.color or{1,0,0,0.15}
local Zone=nil
if ZoneData.type==0 then
self:I(string.format("Register ZONE: %s (Circular)",ZoneName))
Zone=ZONE:New(ZoneName)
else
self:I(string.format("Register ZONE: %s (Polygon, Quad)",ZoneName))
Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,ZoneData.verticies)
end
if Zone then
Zone.Color=color
Zone.ZoneID=ZoneData.zoneId
local ZoneProperties=ZoneData.properties or nil
Zone.Properties={}
if ZoneName and ZoneProperties then
for _,ZoneProp in ipairs(ZoneProperties)do
if ZoneProp.key then
Zone.Properties[ZoneProp.key]=ZoneProp.value
end
end
end
self.ZONENAMES[ZoneName]=ZoneName
self:AddZone(ZoneName,Zone)
end
end
for ZoneGroupName,ZoneGroup in pairs(self.GROUPS)do
if ZoneGroupName:match("#ZONE_POLYGON")then
local ZoneName1=ZoneGroupName:match("(.*)#ZONE_POLYGON")
local ZoneName2=ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
local ZoneName=ZoneName1..(ZoneName2 or"")
self:I(string.format("Register ZONE: %s (Polygon)",ZoneName))
local Zone_Polygon=ZONE_POLYGON:New(ZoneName,ZoneGroup)
Zone_Polygon:SetColor({1,0,0},0.15)
self.ZONENAMES[ZoneName]=ZoneName
self:AddZone(ZoneName,Zone_Polygon)
end
end
if env.mission.drawings and env.mission.drawings.layers then
for layerID,layerData in pairs(env.mission.drawings.layers or{})do
for objectID,objectData in pairs(layerData.objects or{})do
if objectData.polygonMode and(objectData.polygonMode=="free")and objectData.points and#objectData.points>=4 then
local ZoneName=objectData.name or"Unknown free Polygon Drawing"
local vec2={x=objectData.mapX,y=objectData.mapY}
local points=UTILS.DeepCopy(objectData.points)
for i,_point in pairs(points)do
local point=_point
points[i]=UTILS.Vec2Add(point,vec2)
end
table.remove(points,#points)
self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)",ZoneName,#points))
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points)
Zone:SetColor({1,0,0},0.15)
Zone:SetFillColor({1,0,0},0.15)
if objectData.colorString then
local color=string.gsub(objectData.colorString,"^0x","")
local r=tonumber(string.sub(color,1,2),16)/255
local g=tonumber(string.sub(color,3,4),16)/255
local b=tonumber(string.sub(color,5,6),16)/255
local a=tonumber(string.sub(color,7,8),16)/255
Zone:SetColor({r,g,b},a)
end
if objectData.fillColorString then
local color=string.gsub(objectData.colorString,"^0x","")
local r=tonumber(string.sub(color,1,2),16)/255
local g=tonumber(string.sub(color,3,4),16)/255
local b=tonumber(string.sub(color,5,6),16)/255
local a=tonumber(string.sub(color,7,8),16)/255
Zone:SetFillColor({r,g,b},a)
end
self.ZONENAMES[ZoneName]=ZoneName
self:AddZone(ZoneName,Zone)
elseif objectData.polygonMode and objectData.polygonMode=="rect"then
local ZoneName=objectData.name or"Unknown rect Polygon Drawing"
local vec2={x=objectData.mapX,y=objectData.mapY}
local w=objectData.width
local h=objectData.height
local points={}
points[1]={x=vec2.x-h/2,y=vec2.y+w/2}
points[2]={x=vec2.x+h/2,y=vec2.y+w/2}
points[3]={x=vec2.x+h/2,y=vec2.y-w/2}
points[4]={x=vec2.x-h/2,y=vec2.y-w/2}
self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)",ZoneName,#points))
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName,points)
Zone:SetColor({1,0,0},0.15)
if objectData.colorString then
local color=string.gsub(objectData.colorString,"^0x","")
local r=tonumber(string.sub(color,1,2),16)/255
local g=tonumber(string.sub(color,3,4),16)/255
local b=tonumber(string.sub(color,5,6),16)/255
local a=tonumber(string.sub(color,7,8),16)/255
Zone:SetColor({r,g,b},a)
end
if objectData.fillColorString then
local color=string.gsub(objectData.colorString,"^0x","")
local r=tonumber(string.sub(color,1,2),16)/255
local g=tonumber(string.sub(color,3,4),16)/255
local b=tonumber(string.sub(color,5,6),16)/255
local a=tonumber(string.sub(color,7,8),16)/255
Zone:SetFillColor({r,g,b},a)
end
self.ZONENAMES[ZoneName]=ZoneName
self:AddZone(ZoneName,Zone)
elseif objectData.lineMode and(objectData.lineMode=="segments"or objectData.lineMode=="segment"or objectData.lineMode=="free")and objectData.points and#objectData.points>=2 then
local Name=objectData.name or"Unknown Line Drawing"
local vec2={x=objectData.mapX,y=objectData.mapY}
local points=UTILS.DeepCopy(objectData.points)
for i,_point in pairs(points)do
local point=_point
points[i]=UTILS.Vec2Add(point,vec2)
end
self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)",Name,#points))
local Pathline=PATHLINE:NewFromVec2Array(Name,points)
self:AddPathline(Name,Pathline)
end
end
end
end
end
end
do
function DATABASE:FindZoneGoal(ZoneName)
local ZoneFound=self.ZONES_GOAL[ZoneName]
return ZoneFound
end
function DATABASE:AddZoneGoal(ZoneName,Zone)
if not self.ZONES_GOAL[ZoneName]then
self.ZONES_GOAL[ZoneName]=Zone
end
end
function DATABASE:DeleteZoneGoal(ZoneName)
self.ZONES_GOAL[ZoneName]=nil
end
end
do
function DATABASE:FindOpsZone(ZoneName)
local ZoneFound=self.OPSZONES[ZoneName]
return ZoneFound
end
function DATABASE:AddOpsZone(OpsZone)
if OpsZone then
local ZoneName=OpsZone:GetName()
if not self.OPSZONES[ZoneName]then
self.OPSZONES[ZoneName]=OpsZone
end
end
end
function DATABASE:DeleteOpsZone(ZoneName)
self.OPSZONES[ZoneName]=nil
end
end
do
function DATABASE:AddCargo(Cargo)
if not self.CARGOS[Cargo.Name]then
self.CARGOS[Cargo.Name]=Cargo
end
end
function DATABASE:DeleteCargo(CargoName)
self.CARGOS[CargoName]=nil
end
function DATABASE:FindCargo(CargoName)
local CargoFound=self.CARGOS[CargoName]
return CargoFound
end
function DATABASE:IsCargo(TemplateName)
TemplateName=env.getValueDictByKey(TemplateName)
local Cargo=TemplateName:match("#(CARGO)")
return Cargo and Cargo=="CARGO"
end
function DATABASE:_RegisterCargos()
local Groups=UTILS.DeepCopy(self.GROUPS)
for CargoGroupName,CargoGroup in pairs(Groups)do
if self:IsCargo(CargoGroupName)then
local CargoInfo=CargoGroupName:match("#CARGO(.*)")
local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)")
local CargoName1=CargoGroupName:match("(.*)#CARGO%(.*%)")
local CargoName2=CargoGroupName:match(".*#CARGO%(.*%)(.*)")
local CargoName=CargoName1..(CargoName2 or"")
local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?")
local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName
local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?"))
local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?"))
self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius)
end
end
for CargoStaticName,CargoStatic in pairs(self.STATICS)do
if self:IsCargo(CargoStaticName)then
local CargoInfo=CargoStaticName:match("#CARGO(.*)")
local CargoParam=CargoInfo and CargoInfo:match("%((.*)%)")
local CargoName=CargoStaticName:match("(.*)#CARGO")
local Type=CargoParam and CargoParam:match("T=([%a%d ]+),?")
local Category=CargoParam and CargoParam:match("C=([%a%d ]+),?")
local Name=CargoParam and CargoParam:match("N=([%a%d]+),?")or CargoName
local LoadRadius=CargoParam and tonumber(CargoParam:match("RR=([%a%d]+),?"))
local NearRadius=CargoParam and tonumber(CargoParam:match("NR=([%a%d]+),?"))
if Category=="SLING"then
self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
else
if Category=="CRATE"then
self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius)
end
end
end
end
end
end
function DATABASE:FindClient(ClientName)
local ClientFound=self.CLIENTS[ClientName]
return ClientFound
end
function DATABASE:AddClient(ClientName,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[DCSUnitName]
end
function DATABASE:FindGroup(GroupName)
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,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: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})
self.PLAYERS[PlayerName]=nil
self.PLAYERUNITS[PlayerName]=nil
end
end
function DATABASE:GetPlayers()
return self.PLAYERS
end
function DATABASE:GetPlayerUnits()
return self.PLAYERUNITS
end
function DATABASE:GetPlayersJoined()
return self.PLAYERSJOINED
end
function DATABASE:Spawn(SpawnTemplate)
self:F(SpawnTemplate.name)
self:T({SpawnTemplate.SpawnCountryID,SpawnTemplate.SpawnCategoryID})
local SpawnCoalitionID=SpawnTemplate.CoalitionID
local SpawnCountryID=SpawnTemplate.CountryID
local SpawnCategoryID=SpawnTemplate.CategoryID
SpawnTemplate.CoalitionID=nil
SpawnTemplate.CountryID=nil
SpawnTemplate.CategoryID=nil
self:_RegisterGroupTemplate(SpawnTemplate,SpawnCoalitionID,SpawnCategoryID,SpawnCountryID,SpawnTemplate.name)
self:T3(SpawnTemplate)
coalition.addGroup(SpawnCountryID,SpawnCategoryID,SpawnTemplate)
SpawnTemplate.CoalitionID=SpawnCoalitionID
SpawnTemplate.CountryID=SpawnCountryID
SpawnTemplate.CategoryID=SpawnCategoryID
local SpawnGroup=self:AddGroup(SpawnTemplate.name)
for UnitID,UnitData in pairs(SpawnTemplate.units)do
self:AddUnit(UnitData.name)
end
return SpawnGroup
end
function DATABASE:SetStatusGroup(GroupName,Status)
self:F2(Status)
self.Templates.Groups[GroupName].Status=Status
end
function DATABASE:GetStatusGroup(GroupName)
self:F2(GroupName)
if self.Templates.Groups[GroupName]then
return self.Templates.Groups[GroupName].Status
else
return""
end
end
function DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName)
local GroupTemplateName=GroupName or env.getValueDictByKey(GroupTemplate.name)
if not self.Templates.Groups[GroupTemplateName]then
self.Templates.Groups[GroupTemplateName]={}
self.Templates.Groups[GroupTemplateName].Status=nil
end
if GroupTemplate.route and GroupTemplate.route.spans then
GroupTemplate.route.spans=nil
end
GroupTemplate.CategoryID=CategoryID
GroupTemplate.CoalitionID=CoalitionSide
GroupTemplate.CountryID=CountryID
self.Templates.Groups[GroupTemplateName].GroupName=GroupTemplateName
self.Templates.Groups[GroupTemplateName].Template=GroupTemplate
self.Templates.Groups[GroupTemplateName].groupId=GroupTemplate.groupId
self.Templates.Groups[GroupTemplateName].UnitCount=#GroupTemplate.units
self.Templates.Groups[GroupTemplateName].Units=GroupTemplate.units
self.Templates.Groups[GroupTemplateName].CategoryID=CategoryID
self.Templates.Groups[GroupTemplateName].CoalitionID=CoalitionSide
self.Templates.Groups[GroupTemplateName].CountryID=CountryID
local UnitNames={}
for unit_num,UnitTemplate in pairs(GroupTemplate.units)do
UnitTemplate.name=env.getValueDictByKey(UnitTemplate.name)
self.Templates.Units[UnitTemplate.name]={}
self.Templates.Units[UnitTemplate.name].UnitName=UnitTemplate.name
self.Templates.Units[UnitTemplate.name].Template=UnitTemplate
self.Templates.Units[UnitTemplate.name].GroupName=GroupTemplateName
self.Templates.Units[UnitTemplate.name].GroupTemplate=GroupTemplate
self.Templates.Units[UnitTemplate.name].GroupId=GroupTemplate.groupId
self.Templates.Units[UnitTemplate.name].CategoryID=CategoryID
self.Templates.Units[UnitTemplate.name].CoalitionID=CoalitionSide
self.Templates.Units[UnitTemplate.name].CountryID=CountryID
if UnitTemplate.skill and(UnitTemplate.skill=="Client"or UnitTemplate.skill=="Player")then
self.Templates.ClientsByName[UnitTemplate.name]=UnitTemplate
self.Templates.ClientsByName[UnitTemplate.name].CategoryID=CategoryID
self.Templates.ClientsByName[UnitTemplate.name].CoalitionID=CoalitionSide
self.Templates.ClientsByName[UnitTemplate.name].CountryID=CountryID
self.Templates.ClientsByID[UnitTemplate.unitId]=UnitTemplate
end
if UnitTemplate.AddPropAircraft then
if UnitTemplate.AddPropAircraft.STN_L16 then
local stn=UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16)
if stn==nil or stn<1 then
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name)
else
self.STNS[stn]=UnitTemplate.name
self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for "..UnitTemplate.name)
end
end
if UnitTemplate.AddPropAircraft.SADL_TN then
local sadl=UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN)
if sadl==nil or sadl<1 then
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name)
else
self.SADL[sadl]=UnitTemplate.name
self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for "..UnitTemplate.name)
end
end
end
UnitNames[#UnitNames+1]=self.Templates.Units[UnitTemplate.name].UnitName
end
self:T({Group=self.Templates.Groups[GroupTemplateName].GroupName,
Coalition=self.Templates.Groups[GroupTemplateName].CoalitionID,
Category=self.Templates.Groups[GroupTemplateName].CategoryID,
Country=self.Templates.Groups[GroupTemplateName].CountryID,
Units=UnitNames
}
)
end
function DATABASE:GetNextSTN(octal,unitname)
local first=UTILS.OctalToDecimal(octal)or 0
if self.STNS[first]==unitname then return octal end
local nextoctal=77777
local found=false
if 32767-first<10 then
first=0
end
for i=first+1,32767 do
if self.STNS[i]==nil then
found=true
nextoctal=UTILS.DecimalToOctal(i)
self.STNS[i]=unitname
self:T("Register STN "..tostring(nextoctal).." for "..unitname)
break
end
end
if not found then
self:E(string.format("WARNING: No next free STN past %05d found!",octal))
local NewSTNS={}
for _id,_name in pairs(self.STNS)do
if self.UNITS[_name]~=nil then
NewSTNS[_id]=_name
end
end
self.STNS=nil
self.STNS=NewSTNS
end
return nextoctal
end
function DATABASE:GetNextSADL(octal,unitname)
local first=UTILS.OctalToDecimal(octal)or 0
if self.SADL[first]==unitname then return octal end
local nextoctal=7777
local found=false
if 4095-first<10 then
first=0
end
for i=first+1,4095 do
if self.STNS[i]==nil then
found=true
nextoctal=UTILS.DecimalToOctal(i)
self.SADL[i]=unitname
self:T("Register SADL "..tostring(nextoctal).." for "..unitname)
break
end
end
if not found then
self:E(string.format("WARNING: No next free SADL past %04d found!",octal))
local NewSTNS={}
for _id,_name in pairs(self.SADL)do
if self.UNITS[_name]~=nil then
NewSTNS[_id]=_name
end
end
self.SADL=nil
self.SADL=NewSTNS
end
return nextoctal
end
function DATABASE:GetGroupTemplate(GroupName)
local GroupTemplate=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)
local StaticTemplate=UTILS.DeepCopy(StaticTemplate)
local StaticTemplateGroupName=env.getValueDictByKey(StaticTemplate.name)
local StaticTemplateName=StaticTemplate.units[1].name
self.Templates.Statics[StaticTemplateName]=self.Templates.Statics[StaticTemplateName]or{}
StaticTemplate.CategoryID=CategoryID
StaticTemplate.CoalitionID=CoalitionID
StaticTemplate.CountryID=CountryID
self.Templates.Statics[StaticTemplateName].StaticName=StaticTemplateGroupName
self.Templates.Statics[StaticTemplateName].GroupTemplate=StaticTemplate
self.Templates.Statics[StaticTemplateName].UnitTemplate=StaticTemplate.units[1]
self.Templates.Statics[StaticTemplateName].CategoryID=CategoryID
self.Templates.Statics[StaticTemplateName].CoalitionID=CoalitionID
self.Templates.Statics[StaticTemplateName].CountryID=CountryID
self:T({Static=self.Templates.Statics[StaticTemplateName].StaticName,
Coalition=self.Templates.Statics[StaticTemplateName].CoalitionID,
Category=self.Templates.Statics[StaticTemplateName].CategoryID,
Country=self.Templates.Statics[StaticTemplateName].CountryID
}
)
self:AddStatic(StaticTemplateName)
return self
end
function DATABASE:_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
return StaticTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID
else
self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName))
return nil
end
end
function DATABASE:GetStaticUnitTemplate(StaticName)
if self.Templates.Statics[StaticName]then
local UnitTemplate=self.Templates.Statics[StaticName].UnitTemplate
return UnitTemplate,self.Templates.Statics[StaticName].CoalitionID,self.Templates.Statics[StaticName].CategoryID,self.Templates.Statics[StaticName].CountryID
else
self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName))
return nil
end
end
function DATABASE:GetGroupNameFromUnitName(UnitName)
if self.Templates.Units[UnitName]then
return self.Templates.Units[UnitName].GroupName
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
function DATABASE:GetGroupTemplateFromUnitName(UnitName)
if self.Templates.Units[UnitName]then
return self.Templates.Units[UnitName].GroupTemplate
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
function DATABASE:GetUnitTemplateFromUnitName(UnitName)
if self.Templates.Units[UnitName]then
return self.Templates.Units[UnitName]
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
function DATABASE:GetCoalitionFromClientTemplate(ClientName)
if self.Templates.ClientsByName[ClientName]then
return self.Templates.ClientsByName[ClientName].CoalitionID
end
self:E("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:E("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:E("WARNING: Template does not exist for client "..tostring(ClientName))
return nil
end
function DATABASE:GetCoalitionFromAirbase(AirbaseName)
return self.AIRBASES[AirbaseName]:GetCoalition()
end
function DATABASE:GetCategoryFromAirbase(AirbaseName)
return self.AIRBASES[AirbaseName]:GetAirbaseCategory()
end
function DATABASE:_RegisterPlayers()
local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE),AlivePlayersNeutral=coalition.getPlayers(coalition.side.NEUTRAL)}
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
for UnitId,UnitData in pairs(CoalitionData)do
self:T3({"UnitData:",UnitData})
if UnitData and UnitData:isExist()then
local UnitName=UnitData:getName()
local PlayerName=UnitData:getPlayerName()
if not self.PLAYERS[PlayerName]then
self:I({"Add player for unit:",UnitName,PlayerName})
self:AddPlayer(UnitName,PlayerName)
end
end
end
end
return self
end
function DATABASE:_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
for DCSGroupId,DCSGroup in pairs(CoalitionData)do
if DCSGroup:isExist()then
local DCSGroupName=DCSGroup:getName()
self:I(string.format("Register Group: %s",tostring(DCSGroupName)))
self:AddGroup(DCSGroupName)
for DCSUnitId,DCSUnit in pairs(DCSGroup:getUnits())do
local DCSUnitName=DCSUnit:getName()
self:I(string.format("Register Unit: %s",tostring(DCSUnitName)))
self:AddUnit(DCSUnitName)
end
else
self:E({"Group does not exist: ",DCSGroup})
end
end
end
return self
end
function DATABASE:_RegisterClients()
for ClientName,ClientTemplate in pairs(self.Templates.ClientsByName)do
self:I(string.format("Register Client: %s",tostring(ClientName)))
local client=self:AddClient(ClientName)
client.SpawnCoord=COORDINATE:New(ClientTemplate.x,ClientTemplate.alt,ClientTemplate.y)
end
return self
end
function DATABASE:_RegisterStatics()
local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED),GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE),GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)}
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
for DCSStaticId,DCSStatic in pairs(CoalitionData)do
if DCSStatic:isExist()then
local DCSStaticName=DCSStatic:getName()
self:I(string.format("Register Static: %s",tostring(DCSStaticName)))
self:AddStatic(DCSStaticName)
else
self:E({"Static does not exist: ",DCSStatic})
end
end
end
return self
end
function DATABASE:_RegisterAirbases()
for DCSAirbaseId,DCSAirbase in pairs(world.getAirbases())do
self:_RegisterAirbase(DCSAirbase)
end
return self
end
function DATABASE:_RegisterAirbase(airbase)
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)
local typename=airbase:GetTypeName()
local category=airbase.category
if category==Airbase.Category.SHIP and typename=="FARP_SINGLE_01"then
category=Airbase.Category.HELIPAD
end
local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [",AIRBASE.CategoryName[category],tostring(DCSAirbaseName),airbaseUID,#airbase.runways,airbase.NparkingTotal)
for _,terminalType in pairs(AIRBASE.TerminalType)do
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType]then
text=text..string.format("%d=%d ",terminalType,airbase.NparkingTerminal[terminalType])
end
end
text=text.."]"
self:I(text)
end
return self
end
function DATABASE:_EventOnBirth(Event)
self: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)
self:AddGroup(Event.IniDCSGroupName)
local DCSAirbase=Airbase.getByName(Event.IniDCSUnitName)
if DCSAirbase then
self:I(string.format("Adding airbase %s",tostring(Event.IniDCSUnitName)))
self:AddAirbase(Event.IniDCSUnitName)
end
end
end
if Event.IniObjectCategory==Object.Category.UNIT then
Event.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 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(IniUnit)
self:CreateEventPlayerEnterAircraft(IniUnit)
end
self:ScheduleOnce(1,SetPlayerSettings,self,PlayerName,Event.IniUnit)
end
end
end
end
function DATABASE:_EventOnDeadOrCrash(Event)
if Event.IniDCSUnit then
local name=Event.IniDCSUnitName
if Event.IniObjectCategory==3 then
if self.STATICS[Event.IniDCSUnitName]then
self:DeleteStatic(Event.IniDCSUnitName)
end
if self.UNITS[Event.IniDCSUnitName]then
self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName))
local DCSUnit=_DATABASE:FindUnit(Event.IniDCSUnitName)
self:T({DCSUnit})
if DCSUnit then
return
end
end
else
if Event.IniObjectCategory==1 then
if self.UNITS[Event.IniDCSUnitName]then
self:ScheduleOnce(1,self.DeleteUnit,self,Event.IniDCSUnitName)
end
local client=self.CLIENTS[name]
if client then
client:RemovePlayers()
end
end
end
local airbase=self.AIRBASES[Event.IniDCSUnitName]
if airbase and(airbase:IsHelipad()or airbase:IsShip())then
self:DeleteAirbase(Event.IniDCSUnitName)
end
end
self:AccountDestroys(Event)
end
function DATABASE:_EventOnPlayerEnterUnit(Event)
self:F2({Event})
if Event.IniDCSUnit then
if Event.IniObjectCategory==1 and Event.IniGroup and Event.IniGroup:IsGround()then
local IsPlayer=Event.IniDCSUnit:getPlayerName()
if IsPlayer then
self:I(string.format("Player '%s' joined GROUND unit '%s' of group '%s'",tostring(Event.IniPlayerName),tostring(Event.IniDCSUnitName),tostring(Event.IniDCSGroupName)))
local client=self.CLIENTS[Event.IniDCSUnitName]
if not client then
client=self:AddClient(Event.IniDCSUnitName)
end
client:AddPlayer(Event.IniPlayerName)
if not self.PLAYERS[Event.IniPlayerName]then
self:AddPlayer(Event.IniUnitName,Event.IniPlayerName)
end
local Settings=SETTINGS:Set(Event.IniPlayerName)
Settings:SetPlayerMenu(Event.IniUnit)
end
end
end
end
function DATABASE:_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)
local playername=nil
for _name,_unitname in pairs(self.PLAYERS)do
if _unitname==UnitName then
playername=_name
break
end
end
return playername
end
if Event.IniUnit then
if Event.IniObjectCategory==1 then
local PlayerName=Event.IniPlayerName or Event.IniUnit:GetPlayerName()or FindPlayerName(Event.IniUnitName)
if PlayerName then
self:I(string.format("Player '%s' left unit %s",tostring(PlayerName),tostring(Event.IniUnitName)))
local Settings=SETTINGS:Set(PlayerName)
Settings:RemovePlayerMenu(Event.IniUnit)
self:DeletePlayer(Event.IniUnit,PlayerName)
local client=self.CLIENTS[Event.IniDCSUnitName]
if client then
client:RemovePlayer(PlayerName)
end
end
end
end
end
function DATABASE:ForEach(IteratorFunction,FinalizeFunction,arg,Set)
self:F2(arg)
local function CoRoutine()
local Count=0
for ObjectID,Object in pairs(Set)do
self:T2(Object)
IteratorFunction(Object,unpack(arg))
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
self:T3({status,res})
if status==false then
error(res)
end
if res==false then
return true
end
if FinalizeFunction then
FinalizeFunction(unpack(arg))
end
return false
end
Schedule()
return self
end
function DATABASE:ForEachStatic(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.STATICS)
return self
end
function DATABASE:ForEachUnit(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.UNITS)
return self
end
function DATABASE:ForEachGroup(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.GROUPS)
return self
end
function DATABASE:ForEachPlayer(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERS)
return self
end
function DATABASE:ForEachPlayerJoined(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERSJOINED)
return self
end
function DATABASE:ForEachPlayerUnit(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.PLAYERUNITS)
return self
end
function DATABASE:ForEachClient(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.CLIENTS)
return self
end
function DATABASE:ForEachCargo(IteratorFunction,FinalizeFunction,...)
self:F2(arg)
self:ForEach(IteratorFunction,FinalizeFunction,arg,self.CARGOS)
return self
end
function DATABASE:OnEventNewCargo(EventData)
self:F2({EventData})
if EventData.Cargo then
self:AddCargo(EventData.Cargo)
end
end
function DATABASE:OnEventDeleteCargo(EventData)
self:F2({EventData})
if EventData.Cargo then
self:DeleteCargo(EventData.Cargo.Name)
end
end
function DATABASE:OnEventNewZone(EventData)
self:F2({EventData})
if EventData.Zone then
self:AddZone(EventData.Zone.ZoneName,EventData.Zone)
end
end
function DATABASE:OnEventDeleteZone(EventData)
self:F2({EventData})
if EventData.Zone then
self:DeleteZone(EventData.Zone.ZoneName)
end
end
function DATABASE:GetPlayerSettings(PlayerName)
self:F2({PlayerName})
return self.PLAYERSETTINGS[PlayerName]
end
function DATABASE:SetPlayerSettings(PlayerName,Settings)
self:F2({PlayerName,Settings})
self.PLAYERSETTINGS[PlayerName]=Settings
end
function DATABASE:AddOpsGroup(opsgroup)
self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup
end
function DATABASE:GetOpsGroup(groupname)
if type(groupname)=="string"then
else
groupname=groupname:GetName()
end
return self.FLIGHTGROUPS[groupname]
end
function DATABASE:FindOpsGroup(groupname)
if type(groupname)=="string"then
else
groupname=groupname:GetName()
end
return self.FLIGHTGROUPS[groupname]
end
function DATABASE:FindOpsGroupFromUnit(unitname)
local unit=nil
local groupname
if type(unitname)=="string"then
unit=UNIT:FindByName(unitname)
else
unit=unitname
end
if unit then
groupname=unit:GetGroup():GetName()
end
if groupname then
return self.FLIGHTGROUPS[groupname]
else
return nil
end
end
function DATABASE:AddFlightControl(flightcontrol)
self:F2({flightcontrol})
self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol
end
function DATABASE:GetFlightControl(airbasename)
return self.FLIGHTCONTROLS[airbasename]
end
function DATABASE:_RegisterTemplates()
self:F2()
self.Navpoints={}
self.UNITS={}
for CoalitionName,coa_data in pairs(env.mission.coalition)do
self:T({CoalitionName=CoalitionName})
if(CoalitionName=='red'or CoalitionName=='blue'or CoalitionName=='neutrals')and type(coa_data)=='table'then
local CoalitionSide=coalition.side[string.upper(CoalitionName)]
if CoalitionName=="red"then
CoalitionSide=coalition.side.RED
elseif CoalitionName=="blue"then
CoalitionSide=coalition.side.BLUE
else
CoalitionSide=coalition.side.NEUTRAL
end
self.Navpoints[CoalitionName]={}
if coa_data.nav_points then
for nav_ind,nav_data in pairs(coa_data.nav_points)do
if type(nav_data)=='table'then
self.Navpoints[CoalitionName][nav_ind]=UTILS.DeepCopy(nav_data)
self.Navpoints[CoalitionName][nav_ind]['name']=nav_data.callsignStr
self.Navpoints[CoalitionName][nav_ind]['point']={}
self.Navpoints[CoalitionName][nav_ind]['point']['x']=nav_data.x
self.Navpoints[CoalitionName][nav_ind]['point']['y']=0
self.Navpoints[CoalitionName][nav_ind]['point']['z']=nav_data.y
end
end
end
if coa_data.country then
for cntry_id,cntry_data in pairs(coa_data.country)do
local CountryName=string.upper(cntry_data.name)
local CountryID=cntry_data.id
self.COUNTRY_ID[CountryName]=CountryID
self.COUNTRY_NAME[CountryID]=CountryName
if type(cntry_data)=='table'then
for obj_type_name,obj_type_data in pairs(cntry_data)do
if obj_type_name=="helicopter"or obj_type_name=="ship"or obj_type_name=="plane"or obj_type_name=="vehicle"or obj_type_name=="static"then
local CategoryName=obj_type_name
if((type(obj_type_data)=='table')and obj_type_data.group and(type(obj_type_data.group)=='table')and(#obj_type_data.group>0))then
for group_num,Template in pairs(obj_type_data.group)do
if obj_type_name~="static"and Template and Template.units and type(Template.units)=='table'then
self:_RegisterGroupTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID)
else
self:_RegisterStaticTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID)
end
end
end
end
end
end
end
end
end
end
return self
end
function DATABASE:AccountHits(Event)
self:F({Event})
if Event.IniPlayerName~=nil then
self:T("Hitting Something")
if Event.TgtCategory then
self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{}
local Hit=self.HITS[Event.TgtUnitName]
Hit.Players=Hit.Players or{}
Hit.Players[Event.IniPlayerName]=true
end
end
if Event.WeaponPlayerName~=nil then
self:T("Hitting Scenery")
if Event.TgtCategory then
if Event.WeaponCoalition then
self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{}
local Hit=self.HITS[Event.TgtUnitName]
Hit.Players=Hit.Players or{}
Hit.Players[Event.WeaponPlayerName]=true
else
end
end
end
end
function DATABASE:AccountDestroys(Event)
self:F({Event})
local TargetUnit=nil
local TargetGroup=nil
local TargetUnitName=""
local TargetGroupName=""
local TargetPlayerName=""
local TargetCoalition=nil
local TargetCategory=nil
local TargetType=nil
local TargetUnitCoalition=nil
local TargetUnitCategory=nil
local TargetUnitType=nil
if Event.IniDCSUnit then
TargetUnit=Event.IniUnit
TargetUnitName=Event.IniDCSUnitName
TargetGroup=Event.IniDCSGroup
TargetGroupName=Event.IniDCSGroupName
TargetPlayerName=Event.IniPlayerName
TargetCoalition=Event.IniCoalition
TargetCategory=Event.IniCategory
TargetType=Event.IniTypeName
TargetUnitType=TargetType
self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType})
end
local Destroyed=false
if self.HITS[Event.IniUnitName]then
self.DESTROYS[Event.IniUnitName]=self.DESTROYS[Event.IniUnitName]or{}
self.DESTROYS[Event.IniUnitName]=true
end
end
do
SET_BASE={
ClassName="SET_BASE",
Filter={},
Set={},
List={},
Index={},
Database=nil,
CallScheduler=nil,
}
function SET_BASE:New(Database)
local self=BASE:Inherit(self,FSM:New())
self.Database=Database
self:SetStartState("Started")
self:AddTransition("*","Added","*")
self:AddTransition("*","Removed","*")
self.YieldInterval=10
self.TimeInterval=0.001
self.Set={}
self.Index={}
self.CallScheduler=SCHEDULER:New(self)
self:SetEventPriority(2)
return self
end
function SET_BASE:FilterFunction(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
if not self.Filter.Functions then self.Filter.Functions={}end
table.insert(self.Filter.Functions,condition)
return self
end
function SET_BASE:_EvalFilterFunctions(Object)
for _,_condition in pairs(self.Filter.Functions or{})do
local condition=_condition
if condition.func(Object,unpack(condition.arg))==false then
return false
end
end
return true
end
function SET_BASE:Clear(TriggerEvent)
for Name,Object in pairs(self.Set)do
self:Remove(Name,not TriggerEvent)
end
return self
end
function SET_BASE:_Find(ObjectName)
local ObjectFound=self.Set[ObjectName]
return ObjectFound
end
function SET_BASE:GetSet()
return self.Set or{}
end
function SET_BASE:GetSetNames()
local Names={}
for Name,Object in pairs(self.Set)do
table.insert(Names,Name)
end
return Names
end
function SET_BASE:GetSetObjects()
local Objects={}
for Name,Object in pairs(self.Set)do
table.insert(Objects,Object)
end
return Objects
end
function SET_BASE:Remove(ObjectName,NoTriggerEvent)
local TriggerEvent=true
if NoTriggerEvent then
TriggerEvent=false
else
TriggerEvent=true
end
local Object=self.Set[ObjectName]
if Object then
for Index,Key in ipairs(self.Index)do
if Key==ObjectName then
table.remove(self.Index,Index)
self.Set[ObjectName]=nil
break
end
end
if TriggerEvent then
self:Removed(ObjectName,Object)
end
end
end
function SET_BASE:Add(ObjectName,Object)
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
self.Set[ObjectName]=Object
table.insert(self.Index,ObjectName)
self:Added(ObjectName,Object)
return self
end
function SET_BASE:AddObject(Object)
self:Add(Object.ObjectName,Object)
end
function SET_BASE:SortByName()
local function sort(a,b)
return a<b
end
table.sort(self.Index)
end
function SET_BASE:AddSet(SetToAdd)
if not SetToAdd then return self end
for _,ObjectB in pairs(SetToAdd.Set)do
self:AddObject(ObjectB)
end
return self
end
function SET_BASE:GetSetUnion(SetB)
local union=SET_BASE:New()
for _,ObjectA in pairs(self.Set)do
union:AddObject(ObjectA)
end
for _,ObjectB in pairs(SetB.Set)do
union:AddObject(ObjectB)
end
return union
end
function SET_BASE:GetSetIntersection(SetB)
local intersection=SET_BASE:New()
local union=self:GetSetUnion(SetB)
for _,Object in pairs(union.Set)do
if self:IsIncludeObject(Object)and SetB:IsIncludeObject(Object)then
intersection:AddObject(Object)
end
end
return intersection
end
function SET_BASE:GetSetComplement(SetB)
local complement=self:GetSetUnion(SetB)
local intersection=self:GetSetIntersection(SetB)
for _,Object in pairs(intersection.Set)do
complement:Remove(Object.ObjectName,true)
end
return complement
end
function SET_BASE:CompareSets(SetA,SetB)
for _,ObjectB in pairs(SetB.Set)do
if SetA:IsIncludeObject(ObjectB)then
SetA:Add(ObjectB)
end
end
return SetA
end
function SET_BASE:Get(ObjectName)
local Object=self.Set[ObjectName]
return Object
end
function SET_BASE:GetFirst()
local ObjectName=self.Index[1]
local FirstObject=self.Set[ObjectName]
return FirstObject
end
function SET_BASE:GetLast()
local tablemax=table.maxn(self.Index)
local ObjectName=self.Index[tablemax]
local LastObject=self.Set[ObjectName]
return LastObject
end
function SET_BASE:GetRandom()
local tablemax=0
for _,_ind in pairs(self.Index)do
tablemax=tablemax+1
end
local RandomItem=self.Set[self.Index[math.random(1,tablemax)]]
return RandomItem
end
function SET_BASE:GetRandomSurely()
local tablemax=0
local sorted={}
for _,_obj in pairs(self.Set)do
tablemax=tablemax+1
sorted[tablemax]=_obj
end
local RandomItem=sorted[math.random(1,tablemax)]
return RandomItem
end
function SET_BASE:Count()
return self.Index and table.maxn(self.Index)or 0
end
function SET_BASE:SetDatabase(BaseSet)
local OtherFilter=UTILS.DeepCopy(BaseSet.Filter)
self.Filter=OtherFilter
self.Database=BaseSet:GetSet()
return self
end
function SET_BASE:SetSomeIteratorLimit(Limit)
self.SomeIteratorLimit=Limit or 1
return self
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
self:Add(ObjectName,Object)
else
self:Remove(ObjectName,true)
end
end
return self
end
function SET_BASE:FilterClear()
for key,value in pairs(self.Filter)do
self.Filter[key]={}
end
return self
end
function SET_BASE:_FilterStart()
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
return self
end
function SET_BASE:FilterDeads()
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
return self
end
function SET_BASE:FilterCrashes()
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
return self
end
function SET_BASE:FilterStop()
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
return self
end
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=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate())
else
local Distance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate())
if Distance<ClosestDistance then
NearestObject=ObjectData
ClosestDistance=Distance
end
end
end
return NearestObject
end
function SET_BASE:_EventOnBirth(Event)
if Event.IniDCSUnit then
local ObjectName,Object=self:AddInDatabase(Event)
if Object and self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
end
function SET_BASE:_EventOnDeadOrCrash(Event)
if Event.IniDCSUnit then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
self:Remove(ObjectName)
end
end
end
function SET_BASE:ForEach(IteratorFunction,arg,Set,Function,FunctionArguments)
Set=Set or self:GetSet()
arg=arg or{}
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData
if Function then
if Function(unpack(FunctionArguments or{}),Object)==true then
IteratorFunction(Object,unpack(arg))
end
else
IteratorFunction(Object,unpack(arg))
end
Count=Count+1
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
function SET_BASE:ForSome(IteratorFunction,arg,Set,Function,FunctionArguments)
Set=Set or self:GetSet()
arg=arg or{}
local Limit=self:GetSomeIteratorLimit()
local function CoRoutine()
local Count=0
for ObjectID,ObjectData in pairs(Set)do
local Object=ObjectData
if Function then
if Function(unpack(FunctionArguments),Object)==true then
IteratorFunction(Object,unpack(arg))
end
else
IteratorFunction(Object,unpack(arg))
end
Count=Count+1
if Count>=Limit then
break
end
end
return true
end
local co=CoRoutine
local function Schedule()
local status,res=co()
if status==false then
error(res)
end
if res==false then
return true
end
return false
end
Schedule()
return self
end
function SET_BASE:IsIncludeObject(Object)
return true
end
function SET_BASE:IsInSet(Object)
local outcome=false
local name=Object:GetName()
self:ForEach(
function(object)
if object:GetName()==name then
outcome=true
end
end
)
return outcome
end
function SET_BASE:IsNotInSet(Object)
return not self:IsInSet(Object)
end
function SET_BASE:GetObjectNames()
local ObjectNames=""
for ObjectName,Object in pairs(self.Set)do
ObjectNames=ObjectNames..ObjectName..", "
end
return ObjectNames
end
function SET_BASE:Flush(MasterObject)
local ObjectNames=""
for ObjectName,Object in pairs(self.Set)do
ObjectNames=ObjectNames..ObjectName..", "
end
return ObjectNames
end
end
do
SET_GROUP={
ClassName="SET_GROUP",
Filter={
Coalitions=nil,
Categories=nil,
Countries=nil,
GroupPrefixes=nil,
Zones=nil,
Functions=nil,
Alive=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Group.Category.AIRPLANE,
helicopter=Group.Category.HELICOPTER,
ground=Group.Category.GROUND,
ship=Group.Category.SHIP,
structure=Group.Category.STRUCTURE,
},
},
}
function SET_GROUP:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS))
self:FilterActive(false)
return self
end
function SET_GROUP:GetAliveSet()
local AliveSet=SET_GROUP:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject then
if GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
end
return AliveSet.Set or{}
end
function SET_GROUP:GetUnitTypeNames()
local MT={}
local UnitTypes={}
local ReportUnitTypes=REPORT:New()
for GroupID,GroupData in pairs(self:GetSet())do
local Units=GroupData:GetUnits()
for UnitID,UnitData in pairs(Units)do
if UnitData:IsAlive()then
local UnitType=UnitData:GetTypeName()
if not UnitTypes[UnitType]then
UnitTypes[UnitType]=1
else
UnitTypes[UnitType]=UnitTypes[UnitType]+1
end
end
end
end
for UnitTypeID,UnitType in pairs(UnitTypes)do
ReportUnitTypes:Add(UnitType.." of "..UnitTypeID)
end
return ReportUnitTypes
end
function SET_GROUP:AddGroup(group,DontSetCargoBayLimit)
self:Add(group:GetName(),group)
if not DontSetCargoBayLimit then
for UnitID,UnitData in pairs(group:GetUnits()or{})do
if UnitData and UnitData:IsAlive()then
UnitData:SetCargoBayWeightLimit()
end
end
end
return self
end
function SET_GROUP:AddGroupsByName(AddGroupNames)
local AddGroupNamesArray=(type(AddGroupNames)=="table")and AddGroupNames or{AddGroupNames}
for AddGroupID,AddGroupName in pairs(AddGroupNamesArray)do
self:Add(AddGroupName,GROUP:FindByName(AddGroupName))
end
return self
end
function SET_GROUP:RemoveGroupsByName(RemoveGroupNames)
local RemoveGroupNamesArray=(type(RemoveGroupNames)=="table")and RemoveGroupNames or{RemoveGroupNames}
for RemoveGroupID,RemoveGroupName in pairs(RemoveGroupNamesArray)do
self:Remove(RemoveGroupName)
end
return self
end
function SET_GROUP:FindGroup(GroupName)
local GroupFound=self.Set[GroupName]
return GroupFound
end
function SET_GROUP:FindNearestGroupFromPointVec2(Coordinate)
local NearestGroup=nil
local ClosestDistance=nil
local Set=self:GetAliveSet()
for ObjectID,ObjectData in pairs(Set)do
if NearestGroup==nil then
NearestGroup=ObjectData
ClosestDistance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate())
else
local Distance=Coordinate:DistanceFromPointVec2(ObjectData:GetCoordinate())
if Distance<ClosestDistance then
NearestGroup=ObjectData
ClosestDistance=Distance
end
end
end
return NearestGroup
end
function SET_GROUP:FilterZones(Zones,Clear)
if Clear or not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_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={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_GROUP:FilterCategoryGround()
self:FilterCategories("ground")
return self
end
function SET_GROUP:FilterCategoryAirplane()
self:FilterCategories("plane")
return self
end
function SET_GROUP:FilterCategoryHelicopter()
self:FilterCategories("helicopter")
return self
end
function SET_GROUP:FilterCategoryShip()
self:FilterCategories("ship")
return self
end
function SET_GROUP:FilterCategoryStructure()
self:FilterCategories("structure")
return self
end
function SET_GROUP:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_GROUP:FilterPrefixes(Prefixes)
if not self.Filter.GroupPrefixes then
self.Filter.GroupPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.GroupPrefixes[Prefix]=Prefix
end
return self
end
function SET_GROUP:_ContinousZoneFilter()
local Database=_DATABASE.GROUPS
for ObjectName,Object in pairs(Database)do
if self:IsIncludeObject(Object)and self:IsNotInSet(Object)then
self:Add(ObjectName,Object)
elseif(not self:IsIncludeObject(Object))and self:IsInSet(Object)then
self:Remove(ObjectName)
end
end
return self
end
function SET_GROUP:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_GROUP:FilterAlive()
self.Filter.Alive=true
return self
end
function SET_GROUP:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
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.PlayerLeaveUnit,self._EventOnDeadOrCrash)
if self.Filter.Zones then
self.ZoneTimer=TIMER:New(self._ContinousZoneFilter,self)
local timing=self.ZoneTimerInterval or 30
self.ZoneTimer:Start(timing,timing)
end
end
return self
end
function SET_GROUP:FilterZoneTimer(Seconds)
self.ZoneTimerInterval=Seconds or 30
return self
end
function SET_GROUP:FilterStop()
if _DATABASE then
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.RemoveUnit)
self:UnHandleEvent(EVENTS.UnitLost)
if self.Filter.Zones and self.ZoneTimer and self.ZoneTimer:IsRunning()then
self.ZoneTimer:Stop()
end
end
return self
end
function SET_GROUP:_EventOnDeadOrCrash(Event)
if Event.IniDCSUnit then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
local size=1
if Event.IniDCSGroup then
size=Event.IniDCSGroup:getSize()
elseif Event.IniDCSGroupName then
local grp=Group.getByName(Event.IniDCSGroupName)
if grp then
size=grp:getSize()
end
elseif Object:IsAlive()then
size=Object:CountAliveUnits()
end
if size==1 then
self:Remove(ObjectName)
end
end
end
end
function SET_GROUP:AddInDatabase(Event)
if Event.IniObjectCategory==Object.Category.UNIT then
if not self.Database[Event.IniDCSGroupName]then
self.Database[Event.IniDCSGroupName]=GROUP:Register(Event.IniDCSGroupName)
end
end
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_GROUP:FindInDatabase(Event)
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_GROUP:ForEachGroup(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_GROUP:ForSomeGroup(IteratorFunction,...)
self:ForSome(IteratorFunction,arg,self:GetSet())
return self
end
function SET_GROUP:ForEachGroupAlive(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetAliveSet())
return self
end
function SET_GROUP:ForSomeGroupAlive(IteratorFunction,...)
self:ForSome(IteratorFunction,arg,self:GetAliveSet())
return self
end
function SET_GROUP:Activate(Delay)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
local group=GroupData
if group and group:IsAlive()==false then
group:Activate(Delay)
end
end
return self
end
function SET_GROUP:ForEachGroupCompletelyInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsCompletelyInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:ForEachGroupPartlyInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsPartlyInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:ForEachGroupNotInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:AllCompletelyInZone(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if not GroupData:IsCompletelyInZone(Zone)then
return false
end
end
return true
end
function SET_GROUP:ForEachGroupAnyInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,GroupObject)
if GroupObject:IsAnyInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_GROUP:AnyCompletelyInZone(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsCompletelyInZone(Zone)then
return true
end
end
return false
end
function SET_GROUP:AnyInZone(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsPartlyInZone(Zone)or GroupData:IsCompletelyInZone(Zone)then
return true
end
end
return false
end
function SET_GROUP:AnyPartlyInZone(Zone)
local IsPartlyInZone=false
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsCompletelyInZone(Zone)then
return false
elseif GroupData:IsPartlyInZone(Zone)then
IsPartlyInZone=true
end
end
if IsPartlyInZone then
return true
else
return false
end
end
function SET_GROUP:NoneInZone(Zone)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if not GroupData:IsNotInZone(Zone)then
return false
end
end
return true
end
function SET_GROUP:CountInZone(Zone)
local Count=0
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
if GroupData:IsCompletelyInZone(Zone)then
Count=Count+1
end
end
return Count
end
function SET_GROUP:CountUnitInZone(Zone)
local Count=0
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
Count=Count+GroupData:CountInZone(Zone)
end
return Count
end
function SET_GROUP: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
for _,_unit in pairs(GroupData:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
CountU=CountU+1
end
end
end
end
return CountG,CountU
end
function SET_GROUP:IsIncludeObject(MGroup)
local MGroupInclude=true
if self.Filter.Alive==true then
local MGroupAlive=false
if MGroup and MGroup:IsAlive()then
MGroupAlive=true
end
MGroupInclude=MGroupInclude and MGroupAlive
end
if self.Filter.Active~=nil then
local MGroupActive=false
if self.Filter.Active==false or(self.Filter.Active==true and MGroup:IsActive()==true)then
MGroupActive=true
end
MGroupInclude=MGroupInclude and MGroupActive
end
if self.Filter.Coalitions and MGroupInclude then
local MGroupCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition()then
MGroupCoalition=true
end
end
MGroupInclude=MGroupInclude and MGroupCoalition
end
if self.Filter.Categories and MGroupInclude then
local MGroupCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory()then
MGroupCategory=true
end
end
MGroupInclude=MGroupInclude and MGroupCategory
end
if self.Filter.Countries and MGroupInclude then
local MGroupCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
if country.id[CountryName]==MGroup:GetCountry()then
MGroupCountry=true
end
end
MGroupInclude=MGroupInclude and MGroupCountry
end
if self.Filter.GroupPrefixes and MGroupInclude then
local MGroupPrefix=false
for GroupPrefixId,GroupPrefix in pairs(self.Filter.GroupPrefixes)do
if string.find(MGroup:GetName(),string.gsub(GroupPrefix,"-","%%-"),1)then
MGroupPrefix=true
end
end
MGroupInclude=MGroupInclude and MGroupPrefix
end
if self.Filter.Zones and MGroupInclude then
local MGroupZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
if MGroup:IsInZone(Zone)then
MGroupZone=true
end
end
MGroupInclude=MGroupInclude and MGroupZone
end
if self.Filter.Functions and MGroupInclude then
local MGroupFunc=false
MGroupFunc=self:_EvalFilterFunctions(MGroup)
MGroupInclude=MGroupInclude and MGroupFunc
end
return MGroupInclude
end
function SET_GROUP:GetClosestGroup(Coordinate,Coalitions)
local Set=self:GetSet()
local dmin=math.huge
local gmin=nil
for GroupID,GroupData in pairs(Set)do
local group=GroupData
if group and group:IsAlive()and(Coalitions==nil or UTILS.IsAnyInTable(Coalitions,group:GetCoalition()))then
local coord=group:GetCoordinate()
local d
if coord~=nil then
d=UTILS.VecDist3D(Coordinate,coord)
if d<dmin then
dmin=d
gmin=group
end
end
end
end
return gmin,dmin
end
function SET_GROUP:SetCargoBayWeightLimit()
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
for UnitName,UnitData in pairs(GroupData:GetUnits())do
UnitData:SetCargoBayWeightLimit()
end
end
end
end
do
SET_UNIT={
ClassName="SET_UNIT",
Units={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
UnitPrefixes=nil,
Zones=nil,
Functions=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_UNIT,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_UNIT:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.UNITS))
self:FilterActive(false)
return self
end
function SET_UNIT:AddUnit(Unit)
self:Add(Unit:GetName(),Unit)
if Unit:IsInstanceOf("UNIT")then
Unit:SetCargoBayWeightLimit()
end
return self
end
function SET_UNIT:AddUnitsByName(AddUnitNames)
local AddUnitNamesArray=(type(AddUnitNames)=="table")and AddUnitNames or{AddUnitNames}
for AddUnitID,AddUnitName in pairs(AddUnitNamesArray)do
self:Add(AddUnitName,UNIT:FindByName(AddUnitName))
end
return self
end
function SET_UNIT:RemoveUnitsByName(RemoveUnitNames)
local RemoveUnitNamesArray=(type(RemoveUnitNames)=="table")and RemoveUnitNames or{RemoveUnitNames}
for RemoveUnitID,RemoveUnitName in pairs(RemoveUnitNamesArray)do
self:Remove(RemoveUnitName)
end
return self
end
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={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_UNIT:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_UNIT:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_UNIT:FilterPrefixes(Prefixes)
if not self.Filter.UnitPrefixes then
self.Filter.UnitPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.UnitPrefixes[Prefix]=Prefix
end
return self
end
function SET_UNIT:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_UNIT:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_UNIT:FilterAlive()
self:FilterFunction(
function(unit)
if unit and unit:IsExist()and unit:IsAlive()then
return true
else
return false
end
end
)
return self
end
function SET_UNIT:FilterGroupPrefixes(Prefixes)
if type(Prefixes)=="string"then
Prefixes={Prefixes}
end
self:FilterFunction(
function(unit,prefixes)
local outcome=false
if unit then
local grp=unit:GetGroup()
local gname=grp~=nil and grp:GetName()or"none"
for _,_fix in pairs(prefixes or{})do
if string.find(gname,_fix)then
outcome=true
break
end
end
else
return false
end
return outcome
end,Prefixes
)
return self
end
function SET_UNIT:FilterHasRadar(RadarTypes)
self.Filter.RadarTypes=self.Filter.RadarTypes or{}
if type(RadarTypes)~="table"then
RadarTypes={RadarTypes}
end
for RadarTypeID,RadarType in pairs(RadarTypes)do
self.Filter.RadarTypes[RadarType]=RadarType
end
return self
end
function SET_UNIT:FilterHasSEAD()
self.Filter.SEAD=true
return self
end
function SET_UNIT:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_UNIT:GetAliveSet()
local AliveSet=SET_UNIT:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject and GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
return AliveSet.Set or{},AliveSet
end
function SET_UNIT:_ContinousZoneFilter()
local Database=_DATABASE.UNITS
for ObjectName,Object in pairs(Database)do
if self:IsIncludeObject(Object)and self:IsNotInSet(Object)then
self:Add(ObjectName,Object)
elseif(not self:IsIncludeObject(Object))and self:IsInSet(Object)then
self:Remove(ObjectName)
end
end
return self
end
function SET_UNIT:FilterZoneTimer(Seconds)
self.ZoneTimerInterval=Seconds or 30
return self
end
function SET_UNIT:FilterStop()
if _DATABASE then
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.RemoveUnit)
if self.Filter.Zones and self.ZoneTimer and self.ZoneTimer:IsRunning()then
self.ZoneTimer:Stop()
end
end
return self
end
function SET_UNIT:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.UnitLost,self._EventOnDeadOrCrash)
if self.Filter.Zones then
self.ZoneTimer=TIMER:New(self._ContinousZoneFilter,self)
local timing=self.ZoneTimerInterval or 30
self.ZoneTimer:Start(timing,timing)
end
end
return self
end
function SET_UNIT:AddInDatabase(Event)
if Event.IniObjectCategory==Object.Category.UNIT then
if not self.Database[Event.IniDCSUnitName]then
self.Database[Event.IniDCSUnitName]=UNIT:Register(Event.IniDCSUnitName)
end
end
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_UNIT:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName]
end
do
function SET_UNIT:IsPartiallyInZone(ZoneTest)
local IsPartiallyInZone=false
local function EvaluateZone(ZoneUnit)
local ZoneUnitName=ZoneUnit:GetName()
if self:FindUnit(ZoneUnitName)then
IsPartiallyInZone=true
return false
end
return true
end
ZoneTest:SearchZone(EvaluateZone)
return IsPartiallyInZone
end
function SET_UNIT:IsNotInZone(Zone)
local IsNotInZone=true
local function EvaluateZone(ZoneUnit)
local ZoneUnitName=ZoneUnit:GetName()
if self:FindUnit(ZoneUnitName)then
IsNotInZone=false
return false
end
return true
end
Zone:SearchZone(EvaluateZone)
return IsNotInZone
end
end
function SET_UNIT:ForEachUnit(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_UNIT:GetSetPerThreatLevel(FromThreatLevel,ToThreatLevel)
local ThreatLevelSet={}
if self:Count()~=0 then
for UnitName,UnitObject in pairs(self.Set)do
local Unit=UnitObject
local ThreatLevel=Unit:GetThreatLevel()
ThreatLevelSet[ThreatLevel]=ThreatLevelSet[ThreatLevel]or{}
ThreatLevelSet[ThreatLevel].Set=ThreatLevelSet[ThreatLevel].Set or{}
ThreatLevelSet[ThreatLevel].Set[UnitName]=UnitObject
end
local OrderedPerThreatLevelSet={}
local ThreatLevelIncrement=FromThreatLevel<=ToThreatLevel and 1 or-1
for ThreatLevel=FromThreatLevel,ToThreatLevel,ThreatLevelIncrement do
local ThreatLevelItem=ThreatLevelSet[ThreatLevel]
if ThreatLevelItem then
for UnitName,UnitObject in pairs(ThreatLevelItem.Set)do
table.insert(OrderedPerThreatLevelSet,UnitObject)
end
end
end
return OrderedPerThreatLevelSet
end
end
function SET_UNIT:ForEachUnitPerThreatLevel(FromThreatLevel,ToThreatLevel,IteratorFunction,...)
local ThreatLevelSet={}
if self:Count()~=0 then
for UnitName,UnitObject in pairs(self.Set)do
local Unit=UnitObject
local ThreatLevel=Unit:GetThreatLevel()
ThreatLevelSet[ThreatLevel]=ThreatLevelSet[ThreatLevel]or{}
ThreatLevelSet[ThreatLevel].Set=ThreatLevelSet[ThreatLevel].Set or{}
ThreatLevelSet[ThreatLevel].Set[UnitName]=UnitObject
end
local ThreatLevelIncrement=FromThreatLevel<=ToThreatLevel and 1 or-1
for ThreatLevel=FromThreatLevel,ToThreatLevel,ThreatLevelIncrement do
local ThreatLevelItem=ThreatLevelSet[ThreatLevel]
if ThreatLevelItem then
self:ForEach(IteratorFunction,arg,ThreatLevelItem.Set)
end
end
end
return self
end
function SET_UNIT:ForEachUnitCompletelyInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,UnitObject)
if UnitObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_UNIT:ForEachUnitNotInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,UnitObject)
if UnitObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_UNIT:GetUnitTypes()
local MT={}
local UnitTypes={}
for UnitID,UnitData in pairs(self:GetSet())do
local TextUnit=UnitData
if TextUnit:IsAlive()then
local UnitType=TextUnit:GetTypeName()
if not UnitTypes[UnitType]then
UnitTypes[UnitType]=1
else
UnitTypes[UnitType]=UnitTypes[UnitType]+1
end
end
end
for UnitTypeID,UnitType in pairs(UnitTypes)do
MT[#MT+1]=UnitType.." of "..UnitTypeID
end
return UnitTypes
end
function SET_UNIT:GetUnitTypesText()
local MT={}
local UnitTypes=self:GetUnitTypes()
for UnitTypeID,UnitType in pairs(UnitTypes)do
MT[#MT+1]=UnitType.." of "..UnitTypeID
end
return table.concat(MT,", ")
end
function SET_UNIT:GetUnitThreatLevels()
local UnitThreatLevels={}
for UnitID,UnitData in pairs(self:GetSet())do
local ThreatUnit=UnitData
if ThreatUnit:IsAlive()then
local UnitThreatLevel,UnitThreatLevelText=ThreatUnit:GetThreatLevel()
local ThreatUnitName=ThreatUnit:GetName()
UnitThreatLevels[UnitThreatLevel]=UnitThreatLevels[UnitThreatLevel]or{}
UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText=UnitThreatLevelText
UnitThreatLevels[UnitThreatLevel].Units=UnitThreatLevels[UnitThreatLevel].Units or{}
UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName]=ThreatUnit
end
end
return UnitThreatLevels
end
function SET_UNIT:CalculateThreatLevelA2G()
local MaxThreatLevelA2G=0
local MaxThreatText=""
for UnitName,UnitData in pairs(self:GetSet())do
local ThreatUnit=UnitData
local ThreatLevelA2G,ThreatText=ThreatUnit:GetThreatLevel()
if ThreatLevelA2G>MaxThreatLevelA2G then
MaxThreatLevelA2G=ThreatLevelA2G
MaxThreatText=ThreatText
end
end
return MaxThreatLevelA2G,MaxThreatText
end
function SET_UNIT:GetCoordinate()
local function GetSetVec3(units)
local x=0
local y=0
local z=0
local n=0
for _,unit in pairs(units)do
local vec3=nil
if unit and unit:IsAlive()then
vec3=unit:GetVec3()
end
if vec3 then
x=x+vec3.x
y=y+vec3.y
z=z+vec3.z
n=n+1
end
end
if n>0 then
local Vec3={x=x/n,y=y/n,z=z/n}
return Vec3
end
return nil
end
local Coordinate=nil
local Vec3=GetSetVec3(self.Set)
if Vec3 then
Coordinate=COORDINATE:NewFromVec3(Vec3)
end
if Coordinate then
local heading=self:GetHeading()or 0
local velocity=self:GetVelocity()or 0
Coordinate:SetHeading(heading)
Coordinate:SetVelocity(velocity)
end
return Coordinate
end
function SET_UNIT:GetVelocity()
local Coordinate=self:GetFirst():GetCoordinate()
local MaxVelocity=0
for UnitName,UnitData in pairs(self:GetSet())do
local Unit=UnitData
local Coordinate=Unit:GetCoordinate()
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
MaxVelocity=(MaxVelocity<Velocity)and Velocity or MaxVelocity
end
end
return MaxVelocity
end
function SET_UNIT:GetHeading()
local HeadingSet=nil
local MovingCount=0
for UnitName,UnitData in pairs(self:GetSet())do
local Unit=UnitData
local Coordinate=Unit:GetCoordinate()
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
local Heading=Coordinate:GetHeading()
if HeadingSet==nil then
HeadingSet=Heading
else
local HeadingDiff=(HeadingSet-Heading+180+360)%360-180
HeadingDiff=math.abs(HeadingDiff)
if HeadingDiff>5 then
HeadingSet=nil
break
end
end
end
end
return HeadingSet
end
function SET_UNIT:HasRadar(RadarType)
local RadarCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitSensorTest=UnitData
local HasSensors
if RadarType then
HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR,RadarType)
else
HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR)
end
if HasSensors then
RadarCount=RadarCount+1
end
end
return RadarCount
end
function SET_UNIT:HasSEAD()
local SEADCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitSEAD=UnitData
if UnitSEAD:IsAlive()then
local UnitSEADAttributes=UnitSEAD:GetDesc().attributes
local HasSEAD=UnitSEAD:HasSEAD()
if HasSEAD then
SEADCount=SEADCount+1
end
end
end
return SEADCount
end
function SET_UNIT:HasGroundUnits()
local GroundUnitCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitTest=UnitData
if UnitTest:IsGround()then
GroundUnitCount=GroundUnitCount+1
end
end
return GroundUnitCount
end
function SET_UNIT:HasAirUnits()
local AirUnitCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitTest=UnitData
if UnitTest:IsAir()then
AirUnitCount=AirUnitCount+1
end
end
return AirUnitCount
end
function SET_UNIT:HasFriendlyUnits(FriendlyCoalition)
local FriendlyUnitCount=0
for UnitID,UnitData in pairs(self:GetSet())do
local UnitTest=UnitData
if UnitTest:IsFriendly(FriendlyCoalition)then
FriendlyUnitCount=FriendlyUnitCount+1
end
end
return FriendlyUnitCount
end
function SET_UNIT:IsIncludeObject(MUnit)
local MUnitInclude=false
if MUnit:IsAlive()~=nil then
MUnitInclude=true
if self.Filter.Active~=nil then
local MUnitActive=false
if self.Filter.Active==false or(self.Filter.Active==true and MUnit:IsActive()==true)then
MUnitActive=true
end
MUnitInclude=MUnitInclude and MUnitActive
end
if self.Filter.Coalitions and MUnitInclude then
local MUnitCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MUnit:GetCoalition()then
MUnitCoalition=true
end
end
MUnitInclude=MUnitInclude and MUnitCoalition
end
if self.Filter.Categories and MUnitInclude then
local MUnitCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MUnit:GetDesc().category then
MUnitCategory=true
end
end
MUnitInclude=MUnitInclude and MUnitCategory
end
if self.Filter.Types and MUnitInclude then
local MUnitType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
if TypeName==MUnit:GetTypeName()then
MUnitType=true
end
end
MUnitInclude=MUnitInclude and MUnitType
end
if self.Filter.Countries and MUnitInclude then
local MUnitCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
if country.id[CountryName]==MUnit:GetCountry()then
MUnitCountry=true
end
end
MUnitInclude=MUnitInclude and MUnitCountry
end
if self.Filter.UnitPrefixes and MUnitInclude then
local MUnitPrefix=false
for UnitPrefixId,UnitPrefix in pairs(self.Filter.UnitPrefixes)do
if string.find(MUnit:GetName(),UnitPrefix,1)then
MUnitPrefix=true
end
end
MUnitInclude=MUnitInclude and MUnitPrefix
end
if self.Filter.RadarTypes and MUnitInclude then
local MUnitRadar=false
for RadarTypeID,RadarType in pairs(self.Filter.RadarTypes)do
if MUnit:HasSensors(Unit.SensorType.RADAR,RadarType)==true then
if MUnit:GetRadar()==true then
end
MUnitRadar=true
end
end
MUnitInclude=MUnitInclude and MUnitRadar
end
if self.Filter.SEAD and MUnitInclude then
local MUnitSEAD=false
if MUnit:HasSEAD()==true then
MUnitSEAD=true
end
MUnitInclude=MUnitInclude and MUnitSEAD
end
end
if self.Filter.Zones and MUnitInclude then
local MGroupZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
if MUnit:IsInZone(Zone)then
MGroupZone=true
end
end
MUnitInclude=MUnitInclude and MGroupZone
end
if self.Filter.Functions and MUnitInclude then
local MUnitFunc=self:_EvalFilterFunctions(MUnit)
MUnitInclude=MUnitInclude and MUnitFunc
end
return MUnitInclude
end
function SET_UNIT:GetTypeNames(Delimiter)
Delimiter=Delimiter or", "
local TypeReport=REPORT:New()
local Types={}
for UnitName,UnitData in pairs(self:GetSet())do
local Unit=UnitData
local UnitTypeName=Unit:GetTypeName()
if not Types[UnitTypeName]then
Types[UnitTypeName]=UnitTypeName
TypeReport:Add(UnitTypeName)
end
end
return TypeReport:Text(Delimiter)
end
function SET_UNIT:SetCargoBayWeightLimit()
local Set=self:GetSet()
for UnitID,UnitData in pairs(Set)do
UnitData:SetCargoBayWeightLimit()
end
end
end
do
SET_STATIC={
ClassName="SET_STATIC",
Statics={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
StaticPrefixes=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_STATIC,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_STATIC:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.STATICS))
return self
end
function SET_STATIC:AddStatic(AddStatic)
self:Add(AddStatic:GetName(),AddStatic)
return self
end
function SET_STATIC:AddStaticsByName(AddStaticNames)
local AddStaticNamesArray=(type(AddStaticNames)=="table")and AddStaticNames or{AddStaticNames}
for AddStaticID,AddStaticName in pairs(AddStaticNamesArray)do
self:Add(AddStaticName,STATIC:FindByName(AddStaticName))
end
return self
end
function SET_STATIC:RemoveStaticsByName(RemoveStaticNames)
local RemoveStaticNamesArray=(type(RemoveStaticNames)=="table")and RemoveStaticNames or{RemoveStaticNames}
for RemoveStaticID,RemoveStaticName in pairs(RemoveStaticNamesArray)do
self:Remove(RemoveStaticName)
end
return self
end
function SET_STATIC:FindStatic(StaticName)
local StaticFound=self.Set[StaticName]
return StaticFound
end
function SET_STATIC:FilterCoalitions(Coalitions)
if not self.Filter.Coalitions then
self.Filter.Coalitions={}
end
if type(Coalitions)~="table"then
Coalitions={Coalitions}
end
for CoalitionID,Coalition in pairs(Coalitions)do
self.Filter.Coalitions[Coalition]=Coalition
end
return self
end
function SET_STATIC:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_STATIC:FilterCategories(Categories)
if not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_STATIC:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_STATIC:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_STATIC:FilterPrefixes(Prefixes)
if not self.Filter.StaticPrefixes then
self.Filter.StaticPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.StaticPrefixes[Prefix]=Prefix
end
return self
end
function SET_STATIC:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.UnitLost,self._EventOnDeadOrCrash)
end
return self
end
function SET_STATIC:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_STATIC:AddInDatabase(Event)
if Event.IniObjectCategory==Object.Category.STATIC then
if not self.Database[Event.IniDCSUnitName]then
self.Database[Event.IniDCSUnitName]=STATIC:Register(Event.IniDCSUnitName)
end
end
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_STATIC:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName]
end
do
function SET_STATIC:IsPartiallyInZone(Zone)
local IsPartiallyInZone=false
local function EvaluateZone(ZoneStatic)
local ZoneStaticName=ZoneStatic:GetName()
if self:FindStatic(ZoneStaticName)then
IsPartiallyInZone=true
return false
end
return true
end
return IsPartiallyInZone
end
function SET_STATIC:IsNotInZone(Zone)
local IsNotInZone=true
local function EvaluateZone(ZoneStatic)
local ZoneStaticName=ZoneStatic:GetName()
if self:FindStatic(ZoneStaticName)then
IsNotInZone=false
return false
end
return true
end
Zone:Search(EvaluateZone)
return IsNotInZone
end
function SET_STATIC:ForEachStaticInZone(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
end
function SET_STATIC:ForEachStatic(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,StaticObject)
if StaticObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_STATIC:ForEachStaticNotInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,StaticObject)
if StaticObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_STATIC:GetStaticTypes()
local MT={}
local StaticTypes={}
for StaticID,StaticData in pairs(self:GetSet())do
local TextStatic=StaticData
if TextStatic:IsAlive()then
local StaticType=TextStatic:GetTypeName()
if not StaticTypes[StaticType]then
StaticTypes[StaticType]=1
else
StaticTypes[StaticType]=StaticTypes[StaticType]+1
end
end
end
for StaticTypeID,StaticType in pairs(StaticTypes)do
MT[#MT+1]=StaticType.." of "..StaticTypeID
end
return StaticTypes
end
function SET_STATIC:GetStaticTypesText()
local MT={}
local StaticTypes=self:GetStaticTypes()
for StaticTypeID,StaticType in pairs(StaticTypes)do
MT[#MT+1]=StaticType.." of "..StaticTypeID
end
return table.concat(MT,", ")
end
function SET_STATIC:GetCoordinate()
local Coordinate=self:GetFirst():GetCoordinate()
local x1=Coordinate.x
local x2=Coordinate.x
local y1=Coordinate.y
local y2=Coordinate.y
local z1=Coordinate.z
local z2=Coordinate.z
local MaxVelocity=0
local AvgHeading=nil
local MovingCount=0
for StaticName,StaticData in pairs(self:GetSet())do
local Static=StaticData
local Coordinate=Static:GetCoordinate()
x1=(Coordinate.x<x1)and Coordinate.x or x1
x2=(Coordinate.x>x2)and Coordinate.x or x2
y1=(Coordinate.y<y1)and Coordinate.y or y1
y2=(Coordinate.y>y2)and Coordinate.y or y2
z1=(Coordinate.y<z1)and Coordinate.z or z1
z2=(Coordinate.y>z2)and Coordinate.z or z2
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
MaxVelocity=(MaxVelocity<Velocity)and Velocity or MaxVelocity
local Heading=Coordinate:GetHeading()
AvgHeading=AvgHeading and(AvgHeading+Heading)or Heading
MovingCount=MovingCount+1
end
end
AvgHeading=AvgHeading and(AvgHeading/MovingCount)
Coordinate.x=(x2-x1)/2+x1
Coordinate.y=(y2-y1)/2+y1
Coordinate.z=(z2-z1)/2+z1
Coordinate:SetHeading(AvgHeading)
Coordinate:SetVelocity(MaxVelocity)
return Coordinate
end
function SET_STATIC:GetVelocity()
return 0
end
function SET_STATIC:GetHeading()
local HeadingSet=nil
local MovingCount=0
for StaticName,StaticData in pairs(self:GetSet())do
local Static=StaticData
local Coordinate=Static:GetCoordinate()
local Velocity=Coordinate:GetVelocity()
if Velocity~=0 then
local Heading=Coordinate:GetHeading()
if HeadingSet==nil then
HeadingSet=Heading
else
local HeadingDiff=(HeadingSet-Heading+180+360)%360-180
HeadingDiff=math.abs(HeadingDiff)
if HeadingDiff>5 then
HeadingSet=nil
break
end
end
end
end
return HeadingSet
end
function SET_STATIC:CalculateThreatLevelA2G()
local MaxThreatLevelA2G=0
local MaxThreatText=""
for StaticName,StaticData in pairs(self:GetSet())do
local ThreatStatic=StaticData
local ThreatLevelA2G,ThreatText=ThreatStatic:GetThreatLevel()
if ThreatLevelA2G>MaxThreatLevelA2G then
MaxThreatLevelA2G=ThreatLevelA2G
MaxThreatText=ThreatText
end
end
return MaxThreatLevelA2G,MaxThreatText
end
function SET_STATIC:IsIncludeObject(MStatic)
local MStaticInclude=true
if self.Filter.Coalitions then
local MStaticCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MStatic:GetCoalition()then
MStaticCoalition=true
end
end
MStaticInclude=MStaticInclude and MStaticCoalition
end
if self.Filter.Categories then
local MStaticCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MStatic:GetDesc().category then
MStaticCategory=true
end
end
MStaticInclude=MStaticInclude and MStaticCategory
end
if self.Filter.Types then
local MStaticType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
if TypeName==MStatic:GetTypeName()then
MStaticType=true
end
end
MStaticInclude=MStaticInclude and MStaticType
end
if self.Filter.Countries then
local MStaticCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
if country.id[CountryName]==MStatic:GetCountry()then
MStaticCountry=true
end
end
MStaticInclude=MStaticInclude and MStaticCountry
end
if self.Filter.StaticPrefixes then
local MStaticPrefix=false
for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do
if string.find(MStatic:GetName(),StaticPrefix,1)then
MStaticPrefix=true
end
end
MStaticInclude=MStaticInclude and MStaticPrefix
end
if self.Filter.Zones then
local MStaticZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
if MStatic and MStatic:IsInZone(Zone)then
MStaticZone=true
end
end
MStaticInclude=MStaticInclude and MStaticZone
end
if self.Filter.Functions and MStaticInclude then
local MClientFunc=self:_EvalFilterFunctions(MStatic)
MStaticInclude=MStaticInclude and MClientFunc
end
return MStaticInclude
end
function SET_STATIC:GetTypeNames(Delimiter)
Delimiter=Delimiter or", "
local TypeReport=REPORT:New()
local Types={}
for StaticName,StaticData in pairs(self:GetSet())do
local Static=StaticData
local StaticTypeName=Static:GetTypeName()
if not Types[StaticTypeName]then
Types[StaticTypeName]=StaticTypeName
TypeReport:Add(StaticTypeName)
end
end
return TypeReport:Text(Delimiter)
end
function SET_STATIC:GetClosestStatic(Coordinate,Coalitions)
local Set=self:GetSet()
local dmin=math.huge
local gmin=nil
for GroupID,GroupData in pairs(Set)do
local group=GroupData
if group and group:IsAlive()and(Coalitions==nil or UTILS.IsAnyInTable(Coalitions,group:GetCoalition()))then
local coord=group:GetCoord()
local d=UTILS.VecDist3D(Coordinate,coord)
if d<dmin then
dmin=d
gmin=group
end
end
end
return gmin,dmin
end
end
do
SET_CLIENT={
ClassName="SET_CLIENT",
Clients={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
ClientPrefixes=nil,
Zones=nil,
Playernames=nil,
Callsigns=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_UNIT,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_CLIENT:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.CLIENTS))
self:FilterActive(false)
return self
end
function SET_CLIENT:AddClientsByName(AddClientNames)
local AddClientNamesArray=(type(AddClientNames)=="table")and AddClientNames or{AddClientNames}
for AddClientID,AddClientName in pairs(AddClientNamesArray)do
self:Add(AddClientName,CLIENT:FindByName(AddClientName))
end
return self
end
function SET_CLIENT:RemoveClientsByName(RemoveClientNames)
local RemoveClientNamesArray=(type(RemoveClientNames)=="table")and RemoveClientNames or{RemoveClientNames}
for RemoveClientID,RemoveClientName in pairs(RemoveClientNamesArray)do
self:Remove(RemoveClientName.ClientName)
end
return self
end
function SET_CLIENT:FindClient(ClientName)
local ClientFound=self.Set[ClientName]
return ClientFound
end
function SET_CLIENT:FilterCallsigns(Callsigns)
if not self.Filter.Callsigns then
self.Filter.Callsigns={}
end
if type(Callsigns)~="table"then
Callsigns={Callsigns}
end
for callsignID,callsign in pairs(Callsigns)do
self.Filter.Callsigns[callsign]=callsign
end
return self
end
function SET_CLIENT:FilterPlayernames(Playernames)
if not self.Filter.Playernames then
self.Filter.Playernames={}
end
if type(Playernames)~="table"then
Playernames={Playernames}
end
for PlayernameID,playername in pairs(Playernames)do
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={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_CLIENT:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_CLIENT:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_CLIENT:FilterPrefixes(Prefixes)
if not self.Filter.ClientPrefixes then
self.Filter.ClientPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.ClientPrefixes[Prefix]=Prefix
end
return self
end
function SET_CLIENT:FilterGroupPrefixes(Prefixes)
if type(Prefixes)=="string"then
Prefixes={Prefixes}
end
self:FilterFunction(
function(unit,prefixes)
local outcome=false
if unit then
local grp=unit:GetGroup()
local gname=grp~=nil and grp:GetName()or"none"
for _,_fix in pairs(prefixes or{})do
if string.find(gname,_fix)then
outcome=true
break
end
end
else
return false
end
return outcome
end,Prefixes
)
return self
end
function SET_CLIENT:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_CLIENT:FilterAlive()
self:FilterFunction(
function(unit)
if unit and unit:IsExist()and unit:IsAlive()then
return true
else
return false
end
end
)
return self
end
function SET_CLIENT:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_CLIENT:_ContinousZoneFilter()
local Database=_DATABASE.CLIENTS
for ObjectName,Object in pairs(Database)do
if self:IsIncludeObject(Object)and self:IsNotInSet(Object)then
self:Add(ObjectName,Object)
elseif(not self:IsIncludeObject(Object))and self:IsInSet(Object)then
self:Remove(ObjectName)
end
end
return self
end
function SET_CLIENT:FilterZoneTimer(Seconds)
self.ZoneTimerInterval=Seconds or 30
return self
end
function SET_CLIENT:FilterStop()
if _DATABASE then
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
if self.Filter.Zones and self.ZoneTimer and self.ZoneTimer:IsRunning()then
self.ZoneTimer:Stop()
end
end
return self
end
function SET_CLIENT:FilterStart()
if _DATABASE then
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventPlayerLeaveUnit)
if self.Filter.Zones then
self.ZoneTimer=TIMER:New(self._ContinousZoneFilter,self)
local timing=self.ZoneTimerInterval or 30
self.ZoneTimer:Start(timing,timing)
end
self:_FilterStart()
end
return self
end
function SET_CLIENT:_EventPlayerEnterUnit(Event)
if Event.IniDCSUnit then
if Event.IniObjectCategory==Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround()then
local ObjectName,Object=self:AddInDatabase(Event)
if Object and self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
end
return self
end
function SET_CLIENT:_EventPlayerLeaveUnit(Event)
if Event.IniDCSUnit then
if Event.IniObjectCategory==Object.Category.UNIT and Event.IniGroup then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
self:Remove(ObjectName)
end
end
end
return self
end
function SET_CLIENT:HandleCASlots()
self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit)
self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit)
self:FilterFunction(function(client)if client and client:IsAlive()and client:IsGround()then return true else return false end end)
return self
end
function SET_CLIENT:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CLIENT:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CLIENT:ForEachClient(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_CLIENT:ForEachClientInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_CLIENT:ForEachClientNotInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_CLIENT:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_CLIENT:GetAliveSet()
local AliveSet=SET_CLIENT:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject and GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
return AliveSet.Set or{}
end
function SET_CLIENT:IsIncludeObject(MClient)
local MClientInclude=true
if MClient then
local MClientName=MClient.UnitName
if self.Filter.Active~=nil then
local MClientActive=false
if self.Filter.Active==false or(self.Filter.Active==true and MClient:IsActive()==true and MClient:IsAlive()==true)then
MClientActive=true
end
MClientInclude=MClientInclude and MClientActive
end
if self.Filter.Coalitions and MClientInclude then
local MClientCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName)
if ClientCoalitionID==nil and MClient:IsAlive()~=nil then
ClientCoalitionID=MClient:GetCoalition()
end
if self.FilterMeta.Coalitions[CoalitionName]and ClientCoalitionID and self.FilterMeta.Coalitions[CoalitionName]==ClientCoalitionID then
MClientCoalition=true
end
end
MClientInclude=MClientInclude and MClientCoalition
end
if self.Filter.Categories and MClientInclude then
local MClientCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName)
local UnitCategory=0
if ClientCategoryID==nil and MClient:IsExist()then
ClientCategoryID,UnitCategory=MClient:GetCategory()
if self.FilterMeta.Categories[CategoryName]and UnitCategory and self.FilterMeta.Categories[CategoryName]==UnitCategory then
MClientCategory=true
end
else
if self.FilterMeta.Categories[CategoryName]and ClientCategoryID and self.FilterMeta.Categories[CategoryName]==ClientCategoryID then
MClientCategory=true
end
end
end
MClientInclude=MClientInclude and MClientCategory
end
if self.Filter.Types and MClientInclude then
local MClientType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
if TypeName==MClient:GetTypeName()then
MClientType=true
end
end
MClientInclude=MClientInclude and MClientType
end
if self.Filter.Countries and MClientInclude then
local MClientCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
local ClientCountryID=_DATABASE:GetCountryFromClientTemplate(MClientName)
if ClientCountryID==nil and MClient:IsAlive()~=nil then
ClientCountryID=MClient:GetCountry()
end
if country.id[CountryName]and ClientCountryID and country.id[CountryName]==ClientCountryID then
MClientCountry=true
end
end
MClientInclude=MClientInclude and MClientCountry
end
if self.Filter.ClientPrefixes and MClientInclude then
local MClientPrefix=false
for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do
if string.find(MClient.UnitName,ClientPrefix,1)then
MClientPrefix=true
end
end
MClientInclude=MClientInclude and MClientPrefix
end
if self.Filter.Zones and MClientInclude then
local MClientZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
local unit=MClient:GetClientGroupUnit()
if unit and unit:IsInZone(Zone)then
MClientZone=true
end
end
MClientInclude=MClientInclude and MClientZone
end
if self.Filter.Playernames and MClientInclude then
local MClientPlayername=false
local playername=MClient:GetPlayerName()or"Unknown"
for _,_Playername in pairs(self.Filter.Playernames)do
if playername and string.find(playername,_Playername)then
MClientPlayername=true
end
end
MClientInclude=MClientInclude and MClientPlayername
end
if self.Filter.Callsigns and MClientInclude then
local MClientCallsigns=false
local callsign=MClient:GetCallsign()
for _,_Callsign in pairs(self.Filter.Callsigns)do
if callsign and string.find(callsign,_Callsign,1,true)then
MClientCallsigns=true
end
end
MClientInclude=MClientInclude and MClientCallsigns
end
if self.Filter.Functions and MClientInclude then
local MClientFunc=self:_EvalFilterFunctions(MClient)
MClientInclude=MClientInclude and MClientFunc
end
end
return MClientInclude
end
end
do
SET_PLAYER={
ClassName="SET_PLAYER",
Clients={},
Filter={
Coalitions=nil,
Categories=nil,
Types=nil,
Countries=nil,
ClientPrefixes=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Unit.Category.AIRPLANE,
helicopter=Unit.Category.HELICOPTER,
ground=Unit.Category.GROUND_UNIT,
ship=Unit.Category.SHIP,
structure=Unit.Category.STRUCTURE,
},
},
}
function SET_PLAYER:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.PLAYERS))
return self
end
function SET_PLAYER:AddClientsByName(AddClientNames)
local AddClientNamesArray=(type(AddClientNames)=="table")and AddClientNames or{AddClientNames}
for AddClientID,AddClientName in pairs(AddClientNamesArray)do
self:Add(AddClientName,CLIENT:FindByName(AddClientName))
end
return self
end
function SET_PLAYER:RemoveClientsByName(RemoveClientNames)
local RemoveClientNamesArray=(type(RemoveClientNames)=="table")and RemoveClientNames or{RemoveClientNames}
for RemoveClientID,RemoveClientName in pairs(RemoveClientNamesArray)do
self:Remove(RemoveClientName.ClientName)
end
return self
end
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={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_PLAYER:FilterCategories(Categories)
if not self.Filter.Categories then
self.Filter.Categories={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_PLAYER:FilterTypes(Types)
if not self.Filter.Types then
self.Filter.Types={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_PLAYER:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_PLAYER:FilterPrefixes(Prefixes)
if not self.Filter.ClientPrefixes then
self.Filter.ClientPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.ClientPrefixes[Prefix]=Prefix
end
return self
end
function SET_PLAYER:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventOnDeadOrCrash)
end
return self
end
function SET_PLAYER:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_PLAYER:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_PLAYER:ForEachPlayer(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_PLAYER:ForEachPlayerInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_PLAYER:ForEachPlayerNotInZone(ZoneObject,IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet(),
function(ZoneObject,ClientObject)
if ClientObject:IsNotInZone(ZoneObject)then
return true
else
return false
end
end,{ZoneObject})
return self
end
function SET_PLAYER:IsIncludeObject(MClient)
local MClientInclude=true
if MClient then
local MClientName=MClient.UnitName
if self.Filter.Coalitions and MClientInclude then
local MClientCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName)
if ClientCoalitionID==nil and MClient:IsAlive()~=nil then
ClientCoalitionID=MClient:GetCoalition()
end
if self.FilterMeta.Coalitions[CoalitionName]and ClientCoalitionID and self.FilterMeta.Coalitions[CoalitionName]==ClientCoalitionID then
MClientCoalition=true
end
end
MClientInclude=MClientInclude and MClientCoalition
end
if self.Filter.Categories and MClientInclude then
local MClientCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName)
local UnitCategory=0
if ClientCategoryID==nil and MClient:IsExist()then
ClientCategoryID,UnitCategory=MClient:GetCategory()
if self.FilterMeta.Categories[CategoryName]and UnitCategory and self.FilterMeta.Categories[CategoryName]==UnitCategory then
MClientCategory=true
end
else
if self.FilterMeta.Categories[CategoryName]and ClientCategoryID and self.FilterMeta.Categories[CategoryName]==ClientCategoryID then
MClientCategory=true
end
end
end
MClientInclude=MClientInclude and MClientCategory
end
if self.Filter.Types then
local MClientType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
if TypeName==MClient:GetTypeName()then
MClientType=true
end
end
MClientInclude=MClientInclude and MClientType
end
if self.Filter.Countries then
local MClientCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
local ClientCountryID=_DATABASE:GetCountryFromClientTemplate(MClientName)
if country.id[CountryName]and country.id[CountryName]==ClientCountryID then
MClientCountry=true
end
end
MClientInclude=MClientInclude and MClientCountry
end
if self.Filter.ClientPrefixes then
local MClientPrefix=false
for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do
if string.find(MClient.UnitName,ClientPrefix,1)then
MClientPrefix=true
end
end
MClientInclude=MClientInclude and MClientPrefix
end
end
if self.Filter.Zones then
local MClientZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
local unit=MClient:GetClientGroupUnit()
if unit and unit:IsInZone(Zone)then
MClientZone=true
end
end
MClientInclude=MClientInclude and MClientZone
end
if self.Filter.Functions and MClientInclude then
local MClientFunc=self:_EvalFilterFunctions(MClient)
MClientInclude=MClientInclude and MClientFunc
end
return MClientInclude
end
end
do
SET_AIRBASE={
ClassName="SET_AIRBASE",
Airbases={},
Filter={
Coalitions=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
airdrome=Airbase.Category.AIRDROME,
helipad=Airbase.Category.HELIPAD,
ship=Airbase.Category.SHIP,
},
},
}
function SET_AIRBASE:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.AIRBASES))
return self
end
function SET_AIRBASE:AddAirbase(airbase)
self:Add(airbase:GetName(),airbase)
return self
end
function SET_AIRBASE:AddAirbasesByName(AddAirbaseNames)
local AddAirbaseNamesArray=(type(AddAirbaseNames)=="table")and AddAirbaseNames or{AddAirbaseNames}
for AddAirbaseID,AddAirbaseName in pairs(AddAirbaseNamesArray)do
self:Add(AddAirbaseName,AIRBASE:FindByName(AddAirbaseName))
end
return self
end
function SET_AIRBASE:RemoveAirbasesByName(RemoveAirbaseNames)
local RemoveAirbaseNamesArray=(type(RemoveAirbaseNames)=="table")and RemoveAirbaseNames or{RemoveAirbaseNames}
for RemoveAirbaseID,RemoveAirbaseName in pairs(RemoveAirbaseNamesArray)do
self:Remove(RemoveAirbaseName)
end
return self
end
function SET_AIRBASE:FindAirbase(AirbaseName)
local AirbaseFound=self.Set[AirbaseName]
return AirbaseFound
end
function SET_AIRBASE:FindAirbaseInRange(Coordinate,Range)
local AirbaseFound=nil
for AirbaseName,AirbaseObject in pairs(self.Set)do
local AirbaseCoordinate=AirbaseObject:GetCoordinate()
local Distance=Coordinate:Get2DDistance(AirbaseCoordinate)
if Distance<=Range then
AirbaseFound=AirbaseObject
break
end
end
return AirbaseFound
end
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={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_AIRBASE:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_AIRBASE:FilterStart()
if _DATABASE then
self:HandleEvent(EVENTS.BaseCaptured)
self:HandleEvent(EVENTS.Dead)
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveAirbasesByName(ObjectName)
end
end
end
return self
end
function SET_AIRBASE:OnEventBaseCaptured(EventData)
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveAirbasesByName(ObjectName)
end
end
end
function SET_AIRBASE:OnEventDead(EventData)
local airbaseName,airbase=self:FindInDatabase(EventData)
if airbase and(airbase:IsShip()or airbase:IsHelipad())then
self:RemoveAirbasesByName(airbaseName)
end
end
function SET_AIRBASE:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_AIRBASE:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_AIRBASE:ForEachAirbase(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_AIRBASE:FindNearestAirbaseFromPointVec2(Coordinate)
local NearestAirbase=self:FindNearestObjectFromPointVec2(Coordinate)
return NearestAirbase
end
function SET_AIRBASE:IsIncludeObject(MAirbase)
local MAirbaseInclude=true
if MAirbase then
local MAirbaseName=MAirbase:GetName()
if self.Filter.Coalitions then
local MAirbaseCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local AirbaseCoalitionID=_DATABASE:GetCoalitionFromAirbase(MAirbaseName)
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==AirbaseCoalitionID then
MAirbaseCoalition=true
end
end
MAirbaseInclude=MAirbaseInclude and MAirbaseCoalition
end
if self.Filter.Categories and MAirbaseInclude then
local MAirbaseCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
local AirbaseCategoryID=_DATABASE:GetCategoryFromAirbase(MAirbaseName)
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==AirbaseCategoryID then
MAirbaseCategory=true
end
end
MAirbaseInclude=MAirbaseInclude and MAirbaseCategory
end
if self.Filter.Zones and MAirbaseInclude then
local MAirbaseZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
local coord=MAirbase:GetCoordinate()
if coord and Zone:IsCoordinateInZone(coord)then
MAirbaseZone=true
end
end
MAirbaseInclude=MAirbaseInclude and MAirbaseZone
end
end
if self.Filter.Functions and MAirbaseInclude then
local MClientFunc=self:_EvalFilterFunctions(MAirbase)
MAirbaseInclude=MAirbaseInclude and MClientFunc
end
return MAirbaseInclude
end
end
do
SET_CARGO={
ClassName="SET_CARGO",
Cargos={},
Filter={
Coalitions=nil,
Types=nil,
Countries=nil,
ClientPrefixes=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
},
}
function SET_CARGO:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.CARGOS))
return self
end
function SET_CARGO:AddCargo(Cargo)
self:Add(Cargo:GetName(),Cargo)
return self
end
function SET_CARGO:AddCargosByName(AddCargoNames)
local AddCargoNamesArray=(type(AddCargoNames)=="table")and AddCargoNames or{AddCargoNames}
for AddCargoID,AddCargoName in pairs(AddCargoNamesArray)do
self:Add(AddCargoName,CARGO:FindByName(AddCargoName))
end
return self
end
function SET_CARGO:RemoveCargosByName(RemoveCargoNames)
local RemoveCargoNamesArray=(type(RemoveCargoNames)=="table")and RemoveCargoNames or{RemoveCargoNames}
for RemoveCargoID,RemoveCargoName in pairs(RemoveCargoNamesArray)do
self:Remove(RemoveCargoName.CargoName)
end
return self
end
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={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_CARGO:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_CARGO:FilterPrefixes(Prefixes)
if not self.Filter.CargoPrefixes then
self.Filter.CargoPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.CargoPrefixes[Prefix]=Prefix
end
return self
end
function SET_CARGO:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.NewCargo)
self:HandleEvent(EVENTS.DeleteCargo)
end
return self
end
function SET_CARGO:FilterStop()
self:UnHandleEvent(EVENTS.NewCargo)
self:UnHandleEvent(EVENTS.DeleteCargo)
return self
end
function SET_CARGO:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CARGO:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_CARGO:ForEachCargo(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_CARGO:FindNearestCargoFromPointVec2(Coordinate)
local NearestCargo=self:FindNearestObjectFromPointVec2(Coordinate)
return NearestCargo
end
function SET_CARGO:FirstCargoWithState(State)
local FirstCargo=nil
for CargoName,Cargo in pairs(self.Set)do
if Cargo:Is(State)then
FirstCargo=Cargo
break
end
end
return FirstCargo
end
function SET_CARGO:FirstCargoWithStateAndNotDeployed(State)
local FirstCargo=nil
for CargoName,Cargo in pairs(self.Set)do
if Cargo:Is(State)and not Cargo:IsDeployed()then
FirstCargo=Cargo
break
end
end
return FirstCargo
end
function SET_CARGO:FirstCargoUnLoaded()
local FirstCargo=self:FirstCargoWithState("UnLoaded")
return FirstCargo
end
function SET_CARGO:FirstCargoUnLoadedAndNotDeployed()
local FirstCargo=self:FirstCargoWithStateAndNotDeployed("UnLoaded")
return FirstCargo
end
function SET_CARGO:FirstCargoLoaded()
local FirstCargo=self:FirstCargoWithState("Loaded")
return FirstCargo
end
function SET_CARGO:FirstCargoDeployed()
local FirstCargo=self:FirstCargoWithState("Deployed")
return FirstCargo
end
function SET_CARGO:IsIncludeObject(MCargo)
local MCargoInclude=true
if MCargo then
local MCargoName=MCargo:GetName()
if self.Filter.Coalitions then
local MCargoCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
local CargoCoalitionID=MCargo:GetCoalition()
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==CargoCoalitionID then
MCargoCoalition=true
end
end
MCargoInclude=MCargoInclude and MCargoCoalition
end
if self.Filter.Types then
local MCargoType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
if TypeName==MCargo:GetType()then
MCargoType=true
end
end
MCargoInclude=MCargoInclude and MCargoType
end
if self.Filter.CargoPrefixes then
local MCargoPrefix=false
for CargoPrefixId,CargoPrefix in pairs(self.Filter.CargoPrefixes)do
if string.find(MCargo.Name,CargoPrefix,1)then
MCargoPrefix=true
end
end
MCargoInclude=MCargoInclude and MCargoPrefix
end
end
if self.Filter.Functions and MCargoInclude then
local MClientFunc=self:_EvalFilterFunctions(MCargo)
MCargoInclude=MCargoInclude and MClientFunc
end
return MCargoInclude
end
function SET_CARGO:OnEventNewCargo(EventData)
if EventData.Cargo then
if EventData.Cargo and self:IsIncludeObject(EventData.Cargo)then
self:Add(EventData.Cargo.Name,EventData.Cargo)
end
end
end
function SET_CARGO:OnEventDeleteCargo(EventData)
if EventData.Cargo then
local Cargo=_DATABASE:FindCargo(EventData.Cargo.Name)
if Cargo and Cargo.Name then
if Cargo.NoDestroy then
else
self:Remove(Cargo.Name)
end
end
end
end
end
do
SET_ZONE={
ClassName="SET_ZONE",
Zones={},
Filter={
Prefixes=nil,
},
FilterMeta={
},
Checktime=5,
}
function SET_ZONE:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.ZONES))
return self
end
function SET_ZONE:AddZonesByName(AddZoneNames)
local AddZoneNamesArray=(type(AddZoneNames)=="table")and AddZoneNames or{AddZoneNames}
for AddAirbaseID,AddZoneName in pairs(AddZoneNamesArray)do
self:Add(AddZoneName,ZONE:FindByName(AddZoneName))
end
return self
end
function SET_ZONE:AddZone(Zone)
self:Add(Zone:GetName(),Zone)
return self
end
function SET_ZONE:RemoveZonesByName(RemoveZoneNames)
local RemoveZoneNamesArray=(type(RemoveZoneNames)=="table")and RemoveZoneNames or{RemoveZoneNames}
for RemoveZoneID,RemoveZoneName in pairs(RemoveZoneNamesArray)do
self:Remove(RemoveZoneName)
end
return self
end
function SET_ZONE:FindZone(ZoneName)
local ZoneFound=self.Set[ZoneName]
return ZoneFound
end
function SET_ZONE:GetRandomZone(margin)
local margin=margin or 100
if self:Count()~=0 then
local Index=self.Index
local ZoneFound=nil
local counter=0
while(not ZoneFound)or(counter<margin)do
local ZoneRandom=math.random(1,#Index)
ZoneFound=self.Set[Index[ZoneRandom]]:GetZoneMaybe()
counter=counter+1
end
return ZoneFound
end
return nil
end
function SET_ZONE:SetZoneProbability(ZoneName,ZoneProbability)
local Zone=self:FindZone(ZoneName)
Zone:SetZoneProbability(ZoneProbability)
end
function SET_ZONE:FilterPrefixes(Prefixes)
if not self.Filter.Prefixes then
self.Filter.Prefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.Prefixes[Prefix]=Prefix
end
return self
end
function SET_ZONE:FilterStart()
if _DATABASE then
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveZonesByName(ObjectName)
end
end
end
self:HandleEvent(EVENTS.NewZone)
self:HandleEvent(EVENTS.DeleteZone)
return self
end
function SET_ZONE:FilterStop()
self:UnHandleEvent(EVENTS.NewZone)
self:UnHandleEvent(EVENTS.DeleteZone)
return self
end
function SET_ZONE:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE:ForEachZone(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_ZONE:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
for _,_zone in pairs(self.Set)do
local zone=_zone
zone:DrawZone(Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly)
end
return self
end
function SET_ZONE:GetAverageCoordinate()
local x,y,z=0,0,0
local count=0
for _,_zone in pairs(self.Set)do
local zone=_zone
local vec3=zone:GetVec3()
x=x+vec3.x
y=y+vec3.y
z=z+vec3.z
count=count+1
end
if count>1 then
x=x/count
y=y/count
z=z/count
end
local coord=COORDINATE:New(x,y,z)
return coord
end
function SET_ZONE:IsIncludeObject(MZone)
local MZoneInclude=true
if MZone then
local MZoneName=MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix=false
for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do
if string.find(MZoneName,ZonePrefix,1)then
MZonePrefix=true
end
end
MZoneInclude=MZoneInclude and MZonePrefix
end
end
if self.Filter.Functions and MZoneInclude then
local MClientFunc=self:_EvalFilterFunctions(MZone)
MZoneInclude=MZoneInclude and MClientFunc
end
return MZoneInclude
end
function SET_ZONE:OnEventNewZone(EventData)
if EventData.Zone then
if EventData.Zone and self:IsIncludeObject(EventData.Zone)then
self:Add(EventData.Zone.ZoneName,EventData.Zone)
end
end
end
function SET_ZONE:OnEventDeleteZone(EventData)
if EventData.Zone then
local Zone=_DATABASE:FindZone(EventData.Zone.ZoneName)
if Zone and Zone.ZoneName then
if Zone.NoDestroy then
else
self:Remove(Zone.ZoneName)
end
end
end
end
function SET_ZONE:IsCoordinateInZone(Coordinate)
for _,Zone in pairs(self:GetSet())do
local Zone=Zone
if Zone:IsCoordinateInZone(Coordinate)then
return Zone
end
end
return nil
end
function SET_ZONE:GetClosestZone(Coordinate)
local dmin=math.huge
local zmin=nil
for _,Zone in pairs(self:GetSet())do
local Zone=Zone
local d=Zone:Get2DDistance(Coordinate)
if d<dmin then
dmin=d
zmin=Zone
end
end
return zmin,dmin
end
function SET_ZONE:SetCheckTime(seconds)
self.Checktime=seconds or 5
return self
end
function SET_ZONE:Trigger(Objects)
self:AddTransition("*","TriggerStart","TriggerRunning")
self:AddTransition("*","EnteredZone","*")
self:AddTransition("*","LeftZone","*")
self:AddTransition("*","TriggerRunCheck","*")
self:AddTransition("*","TriggerStop","TriggerStopped")
self:TriggerStart()
self.checkobjects=Objects
if UTILS.IsInstanceOf(Objects,"SET_BASE")then
self.objectset=Objects.Set
else
self.objectset={Objects}
end
self:_TriggerCheck(true)
self:__TriggerRunCheck(self.Checktime)
return self
end
function SET_ZONE:_TriggerCheck(fromstart)
if fromstart then
for _,_object in pairs(self.objectset)do
local obj=_object
if obj and obj:IsAlive()then
for _,_zone in pairs(self.Set)do
if not obj.TriggerInZone then obj.TriggerInZone={}end
if _zone:IsCoordinateInZone(obj:GetCoordinate())then
obj.TriggerInZone[_zone.ZoneName]=true
else
obj.TriggerInZone[_zone.ZoneName]=false
end
end
end
end
else
for _,_object in pairs(self.objectset)do
local obj=_object
if obj and obj:IsAlive()then
for _,_zone in pairs(self.Set)do
if not obj.TriggerInZone then
obj.TriggerInZone={}
end
if not obj.TriggerInZone[_zone.ZoneName]then
obj.TriggerInZone[_zone.ZoneName]=false
end
local inzone=_zone:IsCoordinateInZone(obj:GetCoordinate())
if inzone and not obj.TriggerInZone[_zone.ZoneName]then
self:__EnteredZone(0.5,obj,_zone)
obj.TriggerInZone[_zone.ZoneName]=true
elseif(not inzone)and obj.TriggerInZone[_zone.ZoneName]then
self:__LeftZone(0.5,obj,_zone)
obj.TriggerInZone[_zone.ZoneName]=false
else
end
end
end
end
end
return self
end
function SET_ZONE:onafterTriggerRunCheck(From,Event,To)
if self:GetState()~="TriggerStopped"then
self:_TriggerCheck()
self:__TriggerRunCheck(self.Checktime)
end
return self
end
end
do
SET_ZONE_GOAL={
ClassName="SET_ZONE_GOAL",
Zones={},
Filter={
Prefixes=nil,
},
FilterMeta={
},
}
function SET_ZONE_GOAL:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.ZONES_GOAL))
return self
end
function SET_ZONE_GOAL:AddZone(Zone)
self:Add(Zone:GetName(),Zone)
return self
end
function SET_ZONE_GOAL:RemoveZonesByName(RemoveZoneNames)
local RemoveZoneNamesArray=(type(RemoveZoneNames)=="table")and RemoveZoneNames or{RemoveZoneNames}
for RemoveZoneID,RemoveZoneName in pairs(RemoveZoneNamesArray)do
self:Remove(RemoveZoneName)
end
return self
end
function SET_ZONE_GOAL:FindZone(ZoneName)
local ZoneFound=self.Set[ZoneName]
return ZoneFound
end
function SET_ZONE_GOAL:GetRandomZone()
if self:Count()~=0 then
local Index=self.Index
local ZoneFound=nil
while not ZoneFound do
local ZoneRandom=math.random(1,#Index)
ZoneFound=self.Set[Index[ZoneRandom]]:GetZoneMaybe()
end
return ZoneFound
end
return nil
end
function SET_ZONE_GOAL:SetZoneProbability(ZoneName,ZoneProbability)
local Zone=self:FindZone(ZoneName)
Zone:SetZoneProbability(ZoneProbability)
end
function SET_ZONE_GOAL:FilterPrefixes(Prefixes)
if not self.Filter.Prefixes then
self.Filter.Prefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.Prefixes[Prefix]=Prefix
end
return self
end
function SET_ZONE_GOAL:FilterStart()
if _DATABASE then
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveZonesByName(ObjectName)
end
end
end
self:HandleEvent(EVENTS.NewZoneGoal)
self:HandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_ZONE_GOAL:FilterStop()
self:UnHandleEvent(EVENTS.NewZoneGoal)
self:UnHandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_ZONE_GOAL:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE_GOAL:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_ZONE_GOAL:ForEachZone(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_ZONE_GOAL:IsIncludeObject(MZone)
local MZoneInclude=true
if MZone then
local MZoneName=MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix=false
for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do
if string.find(MZoneName,ZonePrefix,1)then
MZonePrefix=true
end
end
MZoneInclude=MZoneInclude and MZonePrefix
end
end
if self.Filter.Functions and MZoneInclude then
local MClientFunc=self:_EvalFilterFunctions(MZone)
MZoneInclude=MZoneInclude and MClientFunc
end
return MZoneInclude
end
function SET_ZONE_GOAL:OnEventNewZoneGoal(EventData)
if EventData.ZoneGoal then
if EventData.ZoneGoal and self:IsIncludeObject(EventData.ZoneGoal)then
self:Add(EventData.ZoneGoal.ZoneName,EventData.ZoneGoal)
end
end
end
function SET_ZONE_GOAL:OnEventDeleteZoneGoal(EventData)
if EventData.ZoneGoal then
local Zone=_DATABASE:FindZone(EventData.ZoneGoal.ZoneName)
if Zone and Zone.ZoneName then
if Zone.NoDestroy then
else
self:Remove(Zone.ZoneName)
end
end
end
end
function SET_ZONE_GOAL:IsCoordinateInZone(Coordinate)
for _,Zone in pairs(self:GetSet())do
local Zone=Zone
if Zone:IsCoordinateInZone(Coordinate)then
return Zone
end
end
return nil
end
end
do
SET_OPSZONE={
ClassName="SET_OPSZONE",
Zones={},
Filter={
Prefixes=nil,
Coalitions=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
},
}
function SET_OPSZONE:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.OPSZONES))
return self
end
function SET_OPSZONE:AddZone(Zone)
self:Add(Zone:GetName(),Zone)
return self
end
function SET_OPSZONE:RemoveZonesByName(RemoveZoneNames)
local RemoveZoneNamesArray=(type(RemoveZoneNames)=="table")and RemoveZoneNames or{RemoveZoneNames}
for RemoveZoneID,RemoveZoneName in pairs(RemoveZoneNamesArray)do
self:Remove(RemoveZoneName)
end
return self
end
function SET_OPSZONE:FindZone(ZoneName)
local ZoneFound=self.Set[ZoneName]
return ZoneFound
end
function SET_OPSZONE:GetRandomZone()
if self:Count()~=0 then
local Index=self.Index
local ZoneFound=nil
while not ZoneFound do
local ZoneRandom=math.random(1,#Index)
ZoneFound=self.Set[Index[ZoneRandom]]:GetZoneMaybe()
end
return ZoneFound
end
return nil
end
function SET_OPSZONE:SetZoneProbability(ZoneName,Probability)
local Zone=self:FindZone(ZoneName)
Zone:SetZoneProbability(Probability)
return self
end
function SET_OPSZONE:FilterPrefixes(Prefixes)
if not self.Filter.Prefixes then
self.Filter.Prefixes={}
end
Prefixes=UTILS.EnsureTable(Prefixes,false)
for PrefixID,Prefix in pairs(Prefixes)do
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)
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
return self
end
function SET_OPSZONE:FilterClear()
local parent=self:GetParent(self,SET_OPSZONE)
parent:FilterClear()
return self
end
function SET_OPSZONE:FilterStart()
if _DATABASE then
for ObjectName,Object in pairs(self.Database)do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:RemoveZonesByName(ObjectName)
end
end
end
self:HandleEvent(EVENTS.NewZoneGoal)
self:HandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_OPSZONE:FilterStop()
self:UnHandleEvent(EVENTS.NewZoneGoal)
self:UnHandleEvent(EVENTS.DeleteZoneGoal)
return self
end
function SET_OPSZONE:AddInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_OPSZONE:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName]
end
function SET_OPSZONE:ForEachZone(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_OPSZONE:IsIncludeObject(MZone)
local MZoneInclude=true
if MZone then
local MZoneName=MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix=false
for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do
if string.find(MZoneName,ZonePrefix,1)then
MZonePrefix=true
break
end
end
MZoneInclude=MZoneInclude and MZonePrefix
end
if self.Filter.Coalitions then
local MGroupCoalition=false
local coalition=MZone:GetOwner()
for _,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==coalition then
MGroupCoalition=true
break
end
end
MZoneInclude=MZoneInclude and MGroupCoalition
end
end
if self.Filter.Functions and MZoneInclude then
local MClientFunc=self:_EvalFilterFunctions(MZone)
MZoneInclude=MZoneInclude and MClientFunc
end
return MZoneInclude
end
function SET_OPSZONE:OnEventNewZoneGoal(EventData)
if EventData.ZoneGoal then
if EventData.ZoneGoal and self:IsIncludeObject(EventData.ZoneGoal)then
self:Add(EventData.ZoneGoal.ZoneName,EventData.ZoneGoal)
end
end
end
function SET_OPSZONE:OnEventDeleteZoneGoal(EventData)
if EventData.ZoneGoal then
local Zone=_DATABASE:FindZone(EventData.ZoneGoal.ZoneName)
if Zone and Zone.ZoneName then
if Zone.NoDestroy then
else
self:Remove(Zone.ZoneName)
end
end
end
end
function SET_OPSZONE:Start()
for _,_Zone in pairs(self:GetSet())do
local Zone=_Zone
if Zone:IsStopped()then
Zone:Start()
end
end
return self
end
function SET_OPSZONE:IsCoordinateInZone(Coordinate)
for _,_Zone in pairs(self:GetSet())do
local Zone=_Zone
if Zone:GetZone():IsCoordinateInZone(Coordinate)then
return Zone
end
end
return nil
end
function SET_OPSZONE:GetClosestZone(Coordinate,Coalitions)
Coalitions=UTILS.EnsureTable(Coalitions,true)
local dmin=math.huge
local zmin=nil
for _,_opszone in pairs(self:GetSet())do
local opszone=_opszone
local coal=opszone:GetOwner()
if opszone:IsStarted()and(Coalitions==nil or(Coalitions and UTILS.IsInTable(Coalitions,coal)))then
local d=opszone:GetZone():Get2DDistance(Coordinate)
if d<dmin then
dmin=d
zmin=opszone
end
end
end
return zmin,dmin
end
end
do
SET_OPSGROUP={
ClassName="SET_OPSGROUP",
Filter={
Coalitions=nil,
Categories=nil,
Countries=nil,
GroupPrefixes=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
},
Categories={
plane=Group.Category.AIRPLANE,
helicopter=Group.Category.HELICOPTER,
ground=Group.Category.GROUND,
ship=Group.Category.SHIP,
},
},
}
function SET_OPSGROUP:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS))
self:FilterActive(false)
return self
end
function SET_OPSGROUP:GetAliveSet()
local AliveSet=SET_OPSGROUP:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject and GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
return AliveSet.Set or{}
end
function SET_OPSGROUP:Add(ObjectName,Object)
if self.Set[ObjectName]then
self:Remove(ObjectName,true)
end
local object=nil
if Object:IsInstanceOf("GROUP")then
object=_DATABASE:FindOpsGroup(ObjectName)
if not object then
if Object:IsShip()then
object=NAVYGROUP:New(Object)
elseif Object:IsGround()then
object=ARMYGROUP:New(Object)
elseif Object:IsAir()then
object=FLIGHTGROUP:New(Object)
else
env.error("ERROR: Unknown category of group object!")
end
end
elseif Object:IsInstanceOf("OPSGROUP")then
object=Object
else
env.error("ERROR: Object must be a GROUP or OPSGROUP!")
end
self.Set[ObjectName]=object
table.insert(self.Index,ObjectName)
self:Added(ObjectName,object)
end
function SET_OPSGROUP:AddObject(Object)
self:Add(Object.groupname,Object)
end
function SET_OPSGROUP:AddGroup(group)
local groupname=group:GetName()
self:Add(groupname,group)
return self
end
function SET_OPSGROUP:AddGroupsByName(AddGroupNames)
local AddGroupNamesArray=(type(AddGroupNames)=="table")and AddGroupNames or{AddGroupNames}
for AddGroupID,AddGroupName in pairs(AddGroupNamesArray)do
self:Add(AddGroupName,GROUP:FindByName(AddGroupName))
end
return self
end
function SET_OPSGROUP:RemoveGroupsByName(RemoveGroupNames)
local RemoveGroupNamesArray=(type(RemoveGroupNames)=="table")and RemoveGroupNames or{RemoveGroupNames}
for RemoveGroupID,RemoveGroupName in pairs(RemoveGroupNamesArray)do
self:Remove(RemoveGroupName)
end
return self
end
function SET_OPSGROUP:FindGroup(GroupName)
local GroupFound=self.Set[GroupName]
return GroupFound
end
function SET_OPSGROUP:FindFlightGroup(GroupName)
local GroupFound=self:FindGroup(GroupName)
return GroupFound
end
function SET_OPSGROUP:FindArmyGroup(GroupName)
local GroupFound=self:FindGroup(GroupName)
return GroupFound
end
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={}
end
if type(Categories)~="table"then
Categories={Categories}
end
for CategoryID,Category in pairs(Categories)do
self.Filter.Categories[Category]=Category
end
return self
end
function SET_OPSGROUP:FilterCategoryGround()
self:FilterCategories("ground")
return self
end
function SET_OPSGROUP:FilterCategoryAirplane()
self:FilterCategories("plane")
return self
end
function SET_OPSGROUP:FilterCategoryAircraft()
self:FilterCategories({"plane","helicopter"})
return self
end
function SET_OPSGROUP:FilterCategoryHelicopter()
self:FilterCategories("helicopter")
return self
end
function SET_OPSGROUP:FilterCategoryShip()
self:FilterCategories("ship")
return self
end
function SET_OPSGROUP:FilterCountries(Countries,Clear)
if Clear or not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_OPSGROUP:FilterPrefixes(Prefixes,Clear)
if Clear or not self.Filter.GroupPrefixes then
self.Filter.GroupPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.GroupPrefixes[Prefix]=Prefix
end
return self
end
function SET_OPSGROUP:FilterActive(Active)
Active=Active or not(Active==false)
self.Filter.Active=Active
return self
end
function SET_OPSGROUP:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent(EVENTS.Birth,self._EventOnBirth)
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.UnitLost,self._EventOnDeadOrCrash)
end
return self
end
function SET_OPSGROUP:Activate(Delay)
local Set=self:GetSet()
for GroupID,GroupData in pairs(Set)do
local group=GroupData
if group and group:IsAlive()==false then
group:Activate(Delay)
end
end
return self
end
function SET_OPSGROUP:_EventOnBirth(Event)
if Event.IniDCSUnit and Event.IniDCSGroup then
local DCSgroup=Event.IniDCSGroup
local CountAliveActive=0
for index,data in pairs(DCSgroup:getUnits())do
if data:isExist()and data:isActive()then
CountAliveActive=CountAliveActive+1
end
end
if DCSgroup:getInitialSize()==DCSgroup:getSize()then
local groupname,group=self:AddInDatabase(Event)
if group and CountAliveActive==DCSgroup:getInitialSize()then
if group and self:IsIncludeObject(group)then
self:Add(groupname,group)
end
end
end
end
end
function SET_OPSGROUP:_EventOnDeadOrCrash(Event)
if Event.IniDCSGroup then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
if Event.IniDCSGroup:getSize()==1 then
self:Remove(ObjectName)
end
end
end
end
function SET_OPSGROUP:AddInDatabase(Event)
if Event.IniObjectCategory==Object.Category.UNIT then
if not self.Database[Event.IniDCSGroupName]then
self.Database[Event.IniDCSGroupName]=GROUP:Register(Event.IniDCSGroupName)
end
end
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_OPSGROUP:FindInDatabase(Event)
return Event.IniDCSGroupName,self.Database[Event.IniDCSGroupName]
end
function SET_OPSGROUP:ForEachGroup(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_OPSGROUP:IsIncludeObject(MGroup)
local MGroupInclude=true
if self.Filter.Active~=nil then
local MGroupActive=false
if self.Filter.Active==false or(self.Filter.Active==true and MGroup:IsActive()==true)then
MGroupActive=true
end
MGroupInclude=MGroupInclude and MGroupActive
end
if self.Filter.Coalitions and MGroupInclude then
local MGroupCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition()then
MGroupCoalition=true
end
end
MGroupInclude=MGroupInclude and MGroupCoalition
end
if self.Filter.Categories and MGroupInclude then
local MGroupCategory=false
for CategoryID,CategoryName in pairs(self.Filter.Categories)do
if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory()then
MGroupCategory=true
end
end
MGroupInclude=MGroupInclude and MGroupCategory
end
if self.Filter.Countries and MGroupInclude then
local MGroupCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
if country.id[CountryName]==MGroup:GetCountry()then
MGroupCountry=true
end
end
MGroupInclude=MGroupInclude and MGroupCountry
end
if self.Filter.GroupPrefixes and MGroupInclude then
local MGroupPrefix=false
for GroupPrefixId,GroupPrefix in pairs(self.Filter.GroupPrefixes)do
if string.find(MGroup:GetName(),GroupPrefix:gsub("-","%%-"),1)then
MGroupPrefix=true
end
end
MGroupInclude=MGroupInclude and MGroupPrefix
end
if self.Filter.Functions and MGroupInclude then
local MClientFunc=self:_EvalFilterFunctions(MGroup)
MGroupInclude=MGroupInclude and MClientFunc
end
return MGroupInclude
end
end
do
SET_SCENERY={
ClassName="SET_SCENERY",
Scenerys={},
Filter={
SceneryPrefixes=nil,
SceneryRoles=nil,
Zones=nil,
},
}
function SET_SCENERY:New(ZoneSet)
local zoneset={}
local self=BASE:Inherit(self,SET_BASE:New(zoneset))
local zonenames={}
if ZoneSet then
for _,_zone in pairs(ZoneSet.Set)do
table.insert(zonenames,_zone:GetName())
end
self:AddSceneryByName(zonenames)
end
return self
end
function SET_SCENERY:NewFromZone(Zone)
local zone=Zone
if type(Zone)=="string"then
zone=ZONE:FindByName(Zone)
end
zone:Scan({Object.Category.SCENERY})
return zone:GetScannedSetScenery()
end
function SET_SCENERY:AddScenery(AddScenery)
self:Add(AddScenery:GetName(),AddScenery)
return self
end
function SET_SCENERY:AddSceneryByName(AddSceneryNames)
local AddSceneryNamesArray=(type(AddSceneryNames)=="table")and AddSceneryNames or{AddSceneryNames}
for AddSceneryID,AddSceneryName in pairs(AddSceneryNamesArray)do
self:Add(AddSceneryName,SCENERY:FindByZoneName(AddSceneryName))
end
return self
end
function SET_SCENERY:RemoveSceneryByName(RemoveSceneryNames)
local RemoveSceneryNamesArray=(type(RemoveSceneryNames)=="table")and RemoveSceneryNames or{RemoveSceneryNames}
for RemoveSceneryID,RemoveSceneryName in pairs(RemoveSceneryNamesArray)do
self:Remove(RemoveSceneryName)
end
return self
end
function SET_SCENERY:FindScenery(SceneryName)
local SceneryFound=self.Set[SceneryName]
return SceneryFound
end
function SET_SCENERY:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_SCENERY:FilterPrefixes(Prefixes)
if not self.Filter.SceneryPrefixes then
self.Filter.SceneryPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.SceneryPrefixes[Prefix]=Prefix
end
return self
end
function SET_SCENERY:FilterRoles(Role)
if not self.Filter.SceneryRoles then
self.Filter.SceneryRoles={}
end
if type(Role)~="table"then
Role={Role}
end
for PrefixID,Prefix in pairs(Role)do
self.Filter.SceneryRoles[Prefix]=Prefix
end
return self
end
function SET_SCENERY:CountAlive()
local Set=self:GetSet()
local CountU=0
for UnitID,UnitData in pairs(Set)do
if UnitData and UnitData:IsAlive()then
CountU=CountU+1
end
end
return CountU
end
function SET_SCENERY:GetAliveSet()
local AliveSet=SET_SCENERY:New()
for GroupName,GroupObject in pairs(self.Set)do
local GroupObject=GroupObject
if GroupObject then
if GroupObject:IsAlive()then
AliveSet:Add(GroupName,GroupObject)
end
end
end
return AliveSet.Set or{},AliveSet
end
function SET_SCENERY:ForEachScenery(IteratorFunction,...)
self:ForEach(IteratorFunction,arg,self:GetSet())
return self
end
function SET_SCENERY:GetCoordinate()
local Coordinate=self:GetFirst():GetCoordinate()
local x1=Coordinate.x
local x2=Coordinate.x
local y1=Coordinate.y
local y2=Coordinate.y
local z1=Coordinate.z
local z2=Coordinate.z
for SceneryName,SceneryData in pairs(self:GetSet())do
local Scenery=SceneryData
local Coordinate=Scenery:GetCoordinate()
x1=(Coordinate.x<x1)and Coordinate.x or x1
x2=(Coordinate.x>x2)and Coordinate.x or x2
y1=(Coordinate.y<y1)and Coordinate.y or y1
y2=(Coordinate.y>y2)and Coordinate.y or y2
z1=(Coordinate.y<z1)and Coordinate.z or z1
z2=(Coordinate.y>z2)and Coordinate.z or z2
end
Coordinate.x=(x2-x1)/2+x1
Coordinate.y=(y2-y1)/2+y1
Coordinate.z=(z2-z1)/2+z1
return Coordinate
end
function SET_SCENERY:IsIncludeObject(MScenery)
local MSceneryInclude=true
if MScenery then
local MSceneryName=MScenery:GetName()
if self.Filter.Prefixes then
local MSceneryPrefix=false
for ZonePrefixId,ZonePrefix in pairs(self.Filter.Prefixes)do
if string.find(MSceneryName,ZonePrefix,1)then
MSceneryPrefix=true
end
end
MSceneryInclude=MSceneryInclude and MSceneryPrefix
end
if self.Filter.Zones then
local MSceneryZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
local coord=MScenery:GetCoordinate()
if coord and Zone:IsCoordinateInZone(coord)then
MSceneryZone=true
end
end
MSceneryInclude=MSceneryInclude and MSceneryZone
end
if self.Filter.SceneryRoles then
local MSceneryRole=false
local Role=MScenery:GetProperty("ROLE")or"none"
for ZoneRoleId,ZoneRole in pairs(self.Filter.SceneryRoles)do
if ZoneRole==Role then
MSceneryRole=true
end
end
MSceneryInclude=MSceneryInclude and MSceneryRole
end
end
if self.Filter.Functions and MSceneryInclude then
local MClientFunc=self:_EvalFilterFunctions(MScenery)
MSceneryInclude=MSceneryInclude and MClientFunc
end
return MSceneryInclude
end
function SET_SCENERY:FilterOnce()
for ObjectName,Object in pairs(self:GetSet())do
if self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
else
self:Remove(ObjectName,true)
end
end
return self
end
function SET_SCENERY:GetLife0()
local life0=0
self:ForEachScenery(
function(obj)
local Obj=obj
life0=life0+Obj:GetLife0()
end
)
return life0
end
function SET_SCENERY:GetLife()
local life=0
self:ForEachScenery(
function(obj)
local Obj=obj
life=life+Obj:GetLife()
end
)
return life
end
function SET_SCENERY:GetRelativeLife()
local life=self:GetLife()
local life0=self:GetLife0()
local rlife=math.floor((life/life0)*100)
return rlife
end
end
do
SET_DYNAMICCARGO={
ClassName="SET_DYNAMICCARGO",
Set={},
List={},
Index={},
Database=nil,
CallScheduler=nil,
Filter={
Coalitions=nil,
Types=nil,
Countries=nil,
StaticPrefixes=nil,
Zones=nil,
},
FilterMeta={
Coalitions={
red=coalition.side.RED,
blue=coalition.side.BLUE,
neutral=coalition.side.NEUTRAL,
}
},
ZoneTimerInterval=20,
ZoneTimer=nil,
}
function SET_DYNAMICCARGO:New()
local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.DYNAMICCARGO))
return self
end
function SET_DYNAMICCARGO:IsIncludeObject(DCargo)
local DCargoInclude=true
if self.Filter.Coalitions then
local DCargoCoalition=false
for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do
if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==DCargo:GetCoalition()then
DCargoCoalition=true
end
end
DCargoInclude=DCargoInclude and DCargoCoalition
end
if self.Filter.Types then
local DCargoType=false
for TypeID,TypeName in pairs(self.Filter.Types)do
if TypeName==DCargo:GetTypeName()then
DCargoType=true
end
end
DCargoInclude=DCargoInclude and DCargoType
end
if self.Filter.Countries then
local DCargoCountry=false
for CountryID,CountryName in pairs(self.Filter.Countries)do
if country.id[CountryName]==DCargo:GetCountry()then
DCargoCountry=true
end
end
DCargoInclude=DCargoInclude and DCargoCountry
end
if self.Filter.StaticPrefixes then
local DCargoPrefix=false
for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do
if string.find(DCargo:GetName(),StaticPrefix,1)then
DCargoPrefix=true
end
end
DCargoInclude=DCargoInclude and DCargoPrefix
end
if self.Filter.Zones then
local DCargoZone=false
for ZoneName,Zone in pairs(self.Filter.Zones)do
if DCargo and DCargo:IsInZone(Zone)then
DCargoZone=true
end
end
DCargoInclude=DCargoInclude and DCargoZone
end
if self.Filter.Functions and DCargoInclude then
local MClientFunc=self:_EvalFilterFunctions(DCargo)
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={}
end
if type(Types)~="table"then
Types={Types}
end
for TypeID,Type in pairs(Types)do
self.Filter.Types[Type]=Type
end
return self
end
function SET_DYNAMICCARGO:FilterCountries(Countries)
if not self.Filter.Countries then
self.Filter.Countries={}
end
if type(Countries)~="table"then
Countries={Countries}
end
for CountryID,Country in pairs(Countries)do
self.Filter.Countries[Country]=Country
end
return self
end
function SET_DYNAMICCARGO:FilterPrefixes(Prefixes)
if not self.Filter.StaticPrefixes then
self.Filter.StaticPrefixes={}
end
if type(Prefixes)~="table"then
Prefixes={Prefixes}
end
for PrefixID,Prefix in pairs(Prefixes)do
self.Filter.StaticPrefixes[Prefix]=Prefix
end
return self
end
function SET_DYNAMICCARGO:FilterNamePattern(Patterns)
return self:FilterPrefixes(Patterns)
end
function SET_DYNAMICCARGO:FilterIsLoaded()
self:FilterFunction(
function(cargo)
if cargo and cargo.CargoState and cargo.CargoState==DYNAMICCARGO.State.LOADED then
return true
else
return false
end
end
)
return self
end
function SET_DYNAMICCARGO:FilterIsUnloaded()
self:FilterFunction(
function(cargo)
if cargo and cargo.CargoState and cargo.CargoState==DYNAMICCARGO.State.UNLOADED then
return true
else
return false
end
end
)
return self
end
function SET_DYNAMICCARGO:FilterIsNew()
self:FilterFunction(
function(cargo)
if cargo and cargo.CargoState and cargo.CargoState==DYNAMICCARGO.State.NEW then
return true
else
return false
end
end
)
return self
end
function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName)
self:FilterFunction(
function(cargo)
if cargo and cargo.Owner and string.find(cargo.Owner,PlayerName,1,true)then
return true
else
return false
end
end
)
return self
end
function SET_DYNAMICCARGO:FilterZones(Zones)
if not self.Filter.Zones then
self.Filter.Zones={}
end
local zones={}
if Zones.ClassName and Zones.ClassName=="SET_ZONE"then
zones=Zones.Set
elseif type(Zones)~="table"or(type(Zones)=="table"and Zones.ClassName)then
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
return self
else
zones=Zones
end
for _,Zone in pairs(zones)do
local zonename=Zone:GetName()
self.Filter.Zones[zonename]=Zone
end
return self
end
function SET_DYNAMICCARGO:FilterStart()
if _DATABASE then
self:HandleEvent(EVENTS.NewDynamicCargo,self._EventHandlerDCAdd)
self:HandleEvent(EVENTS.DynamicCargoRemoved,self._EventHandlerDCRemove)
if self.Filter.Zones then
self.ZoneTimer=TIMER:New(self._ContinousZoneFilter,self)
local timing=self.ZoneTimerInterval or 30
self.ZoneTimer:Start(timing,timing)
end
self:_FilterStart()
end
return self
end
function SET_DYNAMICCARGO:FilterStop()
if _DATABASE then
self:UnHandleEvent(EVENTS.NewDynamicCargo)
self:UnHandleEvent(EVENTS.DynamicCargoRemoved)
if self.ZoneTimer and self.ZoneTimer:IsRunning()then
self.ZoneTimer:Stop()
end
end
return self
end
function SET_DYNAMICCARGO:_ContinousZoneFilter()
local Database=_DATABASE.DYNAMICCARGO
for ObjectName,Object in pairs(Database)do
if self:IsIncludeObject(Object)and self:IsNotInSet(Object)then
self:Add(ObjectName,Object)
elseif(not self:IsIncludeObject(Object))and self:IsInSet(Object)then
self:Remove(ObjectName)
end
end
return self
end
function SET_DYNAMICCARGO:_EventHandlerDCAdd(Event)
if Event.IniDynamicCargo and Event.IniDynamicCargoName then
if not _DATABASE.DYNAMICCARGO[Event.IniDynamicCargoName]then
_DATABASE:AddDynamicCargo(Event.IniDynamicCargoName)
end
local ObjectName,Object=self:FindInDatabase(Event)
if Object and self:IsIncludeObject(Object)then
self:Add(ObjectName,Object)
end
end
return self
end
function SET_DYNAMICCARGO:_EventHandlerDCRemove(Event)
if Event.IniDCSUnitName then
local ObjectName,Object=self:FindInDatabase(Event)
if ObjectName then
self:Remove(ObjectName)
end
end
return self
end
function SET_DYNAMICCARGO:FindInDatabase(Event)
return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName]
end
function SET_DYNAMICCARGO:FilterZoneTimer(Seconds)
self.ZoneTimerInterval=Seconds or 30
return self
end
function SET_DYNAMICCARGO:FilterDeads()
return self
end
function SET_DYNAMICCARGO:FilterCrashes()
return self
end
function SET_DYNAMICCARGO:GetOwnerNames()
local owners={}
self:ForEach(
function(cargo)
if cargo and cargo.Owner then
table.insert(owners,cargo.Owner,cargo.Owner)
end
end
)
return owners
end
function SET_DYNAMICCARGO:GetStorageObjects()
local owners={}
self:ForEach(
function(cargo)
if cargo and cargo.warehouse then
table.insert(owners,cargo.StaticName,cargo.warehouse)
end
end
)
return owners
end
function SET_DYNAMICCARGO:GetOwnerClientObjects()
local owners={}
self:ForEach(
function(cargo)
if cargo and cargo.Owner then
local client=CLIENT:FindByPlayerName(cargo.Owner)
if client then
table.insert(owners,cargo.Owner,client)
end
end
end
)
return owners
end
end
do
COORDINATE={
ClassName="COORDINATE",
}
COORDINATE.WaypointAltType={
BARO="BARO",
RADIO="RADIO",
}
COORDINATE.WaypointAction={
TurningPoint="Turning Point",
FlyoverPoint="Fly Over Point",
FromParkingArea="From Parking Area",
FromParkingAreaHot="From Parking Area Hot",
FromGroundAreaHot="From Ground Area Hot",
FromGroundArea="From Ground Area",
FromRunway="From Runway",
Landing="Landing",
LandingReFuAr="LandingReFuAr",
}
COORDINATE.WaypointType={
TakeOffParking="TakeOffParking",
TakeOffParkingHot="TakeOffParkingHot",
TakeOff="TakeOffParkingHot",
TakeOffGroundHot="TakeOffGroundHot",
TakeOffGround="TakeOffGround",
TurningPoint="Turning Point",
Land="Land",
LandingReFuAr="LandingReFuAr",
}
function COORDINATE:New(x,y,z)
local self=BASE:Inherit(self,BASE:New())
self.x=x
self.y=y
self.z=z
return self
end
function COORDINATE:NewFromCoordinate(Coordinate)
local self=BASE:Inherit(self,BASE:New())
self.x=Coordinate.x
self.y=Coordinate.y
self.z=Coordinate.z
return self
end
function COORDINATE:NewFromVec2(Vec2,LandHeightAdd)
local LandHeight=land.getHeight(Vec2)
LandHeightAdd=LandHeightAdd or 0
LandHeight=LandHeight+LandHeightAdd
local self=self:New(Vec2.x,LandHeight,Vec2.y)
return self
end
function COORDINATE:NewFromVec3(Vec3)
local self=self:New(Vec3.x,Vec3.y,Vec3.z)
self:F2(self)
return self
end
function COORDINATE:NewFromWaypoint(Waypoint)
local self=self:New(Waypoint.x,Waypoint.alt,Waypoint.y)
return self
end
function COORDINATE:GetCoordinate()
return self
end
function COORDINATE:GetVec3()
return{x=self.x,y=self.y,z=self.z}
end
function COORDINATE:GetVec2()
return{x=self.x,y=self.z}
end
function COORDINATE:UpdateFromVec3(Vec3)
self.x=Vec3.x
self.y=Vec3.y
self.z=Vec3.z
return self
end
function COORDINATE:UpdateFromCoordinate(Coordinate)
self.x=Coordinate.x
self.y=Coordinate.y
self.z=Coordinate.z
return self
end
function COORDINATE:UpdateFromVec2(Vec2)
self.x=Vec2.x
self.z=Vec2.y
return self
end
function COORDINATE:GetMagneticDeclination(Month,Year)
local decl=UTILS.GetMagneticDeclination()
if require then
local magvar=require('magvar')
if magvar then
local date,year,month,day=UTILS.GetDCSMissionDate()
magvar.init(Month or month,Year or year)
local lat,lon=self:GetLLDDM()
decl=magvar.get_mag_decl(lat,lon)
if decl then
decl=math.deg(decl)
end
end
else
self:T("The require package is not available. Using constant value for magnetic declination")
end
return decl
end
function COORDINATE:NewFromLLDD(latitude,longitude,altitude)
local vec3=coord.LLtoLO(latitude,longitude)
local _coord=self:NewFromVec3(vec3)
if altitude==nil then
_coord.y=self:GetLandHeight()
else
_coord.y=altitude
end
return _coord
end
function COORDINATE:IsAtCoordinate2D(Coordinate,Precision)
self:F({Coordinate=Coordinate:GetVec2()})
self:F({self=self:GetVec2()})
local x=Coordinate.x
local z=Coordinate.z
return x-Precision<=self.x and x+Precision>=self.x and z-Precision<=self.z and z+Precision>=self.z
end
function COORDINATE:ScanObjects(radius,scanunits,scanstatics,scanscenery)
self:F(string.format("Scanning in radius %.1f m.",radius or 100))
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=self:GetVec3(),
radius=radius,
}
}
radius=radius or 100
if scanunits==nil then
scanunits=true
end
if scanstatics==nil then
scanstatics=true
end
if scanscenery==nil then
scanscenery=false
end
local scanobjects={}
if scanunits then
table.insert(scanobjects,Object.Category.UNIT)
end
if scanstatics then
table.insert(scanobjects,Object.Category.STATIC)
end
if scanscenery then
table.insert(scanobjects,Object.Category.SCENERY)
end
local Units={}
local Statics={}
local Scenery={}
local gotstatics=false
local gotunits=false
local gotscenery=false
local function EvaluateZone(ZoneObject)
if ZoneObject then
local ObjectCategory=Object.getCategory(ZoneObject)
if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()then
table.insert(Units,UNIT:Find(ZoneObject))
gotunits=true
elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist()then
table.insert(Statics,ZoneObject)
gotstatics=true
elseif ObjectCategory==Object.Category.SCENERY then
table.insert(Scenery,ZoneObject)
gotscenery=true
end
end
return true
end
world.searchObjects(scanobjects,SphereSearch,EvaluateZone)
for _,unit in pairs(Units)do
self:T(string.format("Scan found unit %s",unit:GetName()))
end
for _,static in pairs(Statics)do
self:T(string.format("Scan found static %s",static:getName()))
_DATABASE:AddStatic(static:getName())
end
for _,scenery in pairs(Scenery)do
self:T(string.format("Scan found scenery %s typename=%s",scenery:getName(),scenery:getTypeName()))
end
return gotunits,gotstatics,gotscenery,Units,Statics,Scenery
end
function COORDINATE:ScanUnits(radius)
local _,_,_,units=self:ScanObjects(radius,true,false,false)
local set=SET_UNIT:New()
for _,unit in pairs(units)do
set:AddUnit(unit)
end
return set
end
function COORDINATE:ScanStatics(radius)
local _,_,_,_,statics=self:ScanObjects(radius,false,true,false)
local set=SET_STATIC:New()
for _,stat in pairs(statics)do
set:AddStatic(STATIC:Find(stat))
end
return set
end
function COORDINATE:FindClosestStatic(radius)
local units=self:ScanStatics(radius)
local umin=nil
local dmin=math.huge
for _,_unit in pairs(units.Set)do
local unit=_unit
local coordinate=unit:GetCoordinate()
local d=self:Get2DDistance(coordinate)
if d<dmin then
dmin=d
umin=unit
end
end
return umin
end
function COORDINATE:FindClosestUnit(radius)
local units=self:ScanUnits(radius)
local umin=nil
local dmin=math.huge
for _,_unit in pairs(units.Set)do
local unit=_unit
local coordinate=unit:GetCoordinate()
local d=self:Get2DDistance(coordinate)
if d<dmin then
dmin=d
umin=unit
end
end
return umin
end
function COORDINATE:ScanScenery(radius)
local _,_,_,_,_,scenerys=self:ScanObjects(radius,false,false,true)
local set={}
for _,_scenery in pairs(scenerys)do
local scenery=_scenery
local name=scenery:getName()
local s=SCENERY:Register(name,scenery)
table.insert(set,s)
end
return set
end
function COORDINATE:FindClosestScenery(radius)
local sceneries=self:ScanScenery(radius)
local umin=nil
local dmin=math.huge
for _,_scenery in pairs(sceneries)do
local scenery=_scenery
local coordinate=scenery:GetCoordinate()
local d=self:Get2DDistance(coordinate)
if d<dmin then
dmin=d
umin=scenery
end
end
return umin
end
function COORDINATE:DistanceFromPointVec2(PointVec2Reference)
self:F2(PointVec2Reference)
if not PointVec2Reference then return math.huge end
local Distance=((PointVec2Reference.x-self.x)^2+(PointVec2Reference.z-self.z)^2)^0.5
self:T2(Distance)
return Distance
end
function COORDINATE:Translate(Distance,Angle,Keepalt,Overwrite)
local alpha=math.rad((Angle or 0))
local x=Distance*math.cos(alpha)+self.x
local z=Distance*math.sin(alpha)+self.z
local y=Keepalt and self.y or land.getHeight({x=x,y=z})
if Overwrite then
self.x=x
self.y=y
self.z=z
return self
else
local coord=COORDINATE:New(x,y,z)
return coord
end
end
function COORDINATE:Rotate2D(Angle)
if not Angle then
return self
end
local phi=math.rad(Angle)
local X=self.z
local Y=self.x
local x=X*math.cos(phi)-Y*math.sin(phi)
local y=X*math.sin(phi)+Y*math.cos(phi)
local coord=COORDINATE:NewFromVec3({x=y,y=self.y,z=x})
return coord
end
function COORDINATE:GetRandomVec2InRadius(OuterRadius,InnerRadius)
self:F2({OuterRadius,InnerRadius})
local Theta=2*math.pi*math.random()
local Radials=math.random()+math.random()
if Radials>1 then
Radials=2-Radials
end
local RadialMultiplier
if InnerRadius and InnerRadius<=OuterRadius then
RadialMultiplier=(OuterRadius-InnerRadius)*Radials+InnerRadius
else
RadialMultiplier=OuterRadius*Radials
end
local RandomVec2
if OuterRadius>0 then
RandomVec2={x=math.cos(Theta)*RadialMultiplier+self.x,y=math.sin(Theta)*RadialMultiplier+self.z}
else
RandomVec2={x=self.x,y=self.z}
end
return RandomVec2
end
function COORDINATE:GetRandomCoordinateInRadius(OuterRadius,InnerRadius)
self:F2({OuterRadius,InnerRadius})
local coord=COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius))
return coord
end
function COORDINATE:GetRandomVec3InRadius(OuterRadius,InnerRadius)
local RandomVec2=self:GetRandomVec2InRadius(OuterRadius,InnerRadius)
local y=self.y+math.random(InnerRadius,OuterRadius)
local RandomVec3={x=RandomVec2.x,y=y,z=RandomVec2.y}
return RandomVec3
end
function COORDINATE:GetLandHeight()
local Vec2={x=self.x,y=self.z}
return land.getHeight(Vec2)
end
function COORDINATE:SetHeading(Heading)
self.Heading=Heading
end
function COORDINATE:GetHeading()
return self.Heading
end
function COORDINATE:SetVelocity(Velocity)
self.Velocity=Velocity
end
function COORDINATE:GetVelocity()
local Velocity=self.Velocity
return Velocity or 0
end
function COORDINATE:GetName()
local name=self:ToStringMGRS()
return name
end
function COORDINATE:GetMovingText(Settings)
return self:GetVelocityText(Settings)..", "..self:GetHeadingText(Settings)
end
function COORDINATE:GetDirectionVec3(TargetCoordinate)
if TargetCoordinate then
return{x=TargetCoordinate.x-self.x,y=TargetCoordinate.y-self.y,z=TargetCoordinate.z-self.z}
else
return{x=0,y=0,z=0}
end
end
function COORDINATE:GetNorthCorrectionRadians()
local TargetVec3=self:GetVec3()
local lat,lon=coord.LOtoLL(TargetVec3)
local north_posit=coord.LLtoLO(lat+1,lon)
return math.atan2(north_posit.z-TargetVec3.z,north_posit.x-TargetVec3.x)
end
function COORDINATE:GetAngleRadians(DirectionVec3)
local DirectionRadians=math.atan2(DirectionVec3.z,DirectionVec3.x)
if DirectionRadians<0 then
DirectionRadians=DirectionRadians+2*math.pi
end
return DirectionRadians
end
function COORDINATE:GetAngleDegrees(DirectionVec3)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Angle=UTILS.ToDegree(AngleRadians)
return Angle
end
function COORDINATE:GetIntermediateCoordinate(ToCoordinate,Fraction)
local f=Fraction or 0.5
local vec=UTILS.VecSubstract(ToCoordinate,self)
if f>1 then
local norm=UTILS.VecNorm(vec)
f=Fraction/norm
end
vec.x=f*vec.x
vec.y=f*vec.y
vec.z=f*vec.z
vec=UTILS.VecAdd(self,vec)
local coord=COORDINATE:New(vec.x,vec.y,vec.z)
return coord
end
function COORDINATE:Get2DDistance(TargetCoordinate)
if not TargetCoordinate then return 1000000 end
local a=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)
self:F2(height)
local y=height or self.y
local point={x=self.x,y=height or self.y,z=self.z}
local T,P=atmosphere.getTemperatureAndPressure(point)
return T-273.15
end
function COORDINATE:GetTemperatureText(height,Settings)
local DegreesCelcius=self:GetTemperature(height)
local Settings=Settings or _SETTINGS
if DegreesCelcius then
if Settings:IsMetric()then
return string.format(" %-2.2f °C",DegreesCelcius)
else
return string.format(" %-2.2f °F",UTILS.CelsiusToFahrenheit(DegreesCelcius))
end
else
return" no temperature"
end
return nil
end
function COORDINATE:GetPressure(height)
local point={x=self.x,y=height or self.y,z=self.z}
local T,P=atmosphere.getTemperatureAndPressure(point)
return P/100
end
function COORDINATE:GetPressureText(height,Settings)
local Pressure_hPa=self:GetPressure(height)
local Pressure_mmHg=Pressure_hPa*0.7500615613030
local Pressure_inHg=Pressure_hPa*0.0295299830714
local Settings=Settings or _SETTINGS
if Pressure_hPa then
if Settings:IsMetric()then
return string.format(" %4.1f hPa (%3.1f mmHg)",Pressure_hPa,Pressure_mmHg)
else
return string.format(" %4.1f hPa (%3.2f inHg)",Pressure_hPa,Pressure_inHg)
end
else
return" no pressure"
end
return nil
end
function COORDINATE:HeadingTo(ToCoordinate)
local dz=ToCoordinate.z-self.z
local dx=ToCoordinate.x-self.x
local heading=math.deg(math.atan2(dz,dx))
if heading<0 then
heading=360+heading
end
return heading
end
function COORDINATE:GetWindVec3(height,turbulence)
local landheight=self:GetLandHeight()+0.1
local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z}
local wind=nil
if turbulence then
wind=atmosphere.getWindWithTurbulence(point)
else
wind=atmosphere.getWind(point)
end
return wind
end
function COORDINATE:GetWind(height,turbulence)
local wind=self:GetWindVec3(height,turbulence)
local direction=UTILS.VecHdg(wind)
if direction>180 then
direction=direction-180
else
direction=direction+180
end
local strength=UTILS.VecNorm(wind)
return direction,strength
end
function COORDINATE:GetWindWithTurbulenceVec3(height)
local landheight=self:GetLandHeight()+0.1
local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z}
local vec3=atmosphere.getWindWithTurbulence(point)
return vec3
end
function COORDINATE: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
if Direction and Strength then
if Settings:IsMetric()then
return string.format(" %d ° at %3.2f mps",Direction,UTILS.MpsToKmph(Strength))
else
return string.format(" %d ° at %3.2f kps",Direction,UTILS.MpsToKnots(Strength))
end
else
return" no wind"
end
return nil
end
function COORDINATE:Get3DDistance(TargetCoordinate)
local TargetVec3={x=TargetCoordinate.x,y=TargetCoordinate.y,z=TargetCoordinate.z}
local SourceVec3=self:GetVec3()
local dist=UTILS.VecDist3D(TargetVec3,SourceVec3)
return dist
end
function COORDINATE:GetBearingText(AngleRadians,Precision,Settings,MagVar)
local Settings=Settings or _SETTINGS
local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision)
local s=string.format('%03d°',AngleDegrees)
if MagVar then
local variation=self:GetMagneticDeclination()or 0
local AngleMagnetic=AngleDegrees-variation
if AngleMagnetic<0 then AngleMagnetic=360-AngleMagnetic end
s=string.format('%03d°M|%03d°',AngleMagnetic,AngleDegrees)
end
return s
end
function COORDINATE:GetDistanceText(Distance,Settings,Language,Precision)
local Settings=Settings or _SETTINGS
local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN"
Language=string.lower(Language)
local Precision=Precision or 0
local DistanceText
if Settings:IsMetric()then
if Language=="en"then
DistanceText=" for "..UTILS.Round(Distance/1000,Precision).." km"
elseif Language=="ru"then
DistanceText=" за "..UTILS.Round(Distance/1000,Precision).." километров"
end
else
if Language=="en"then
DistanceText=" for "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." miles"
elseif Language=="ru"then
DistanceText=" за "..UTILS.Round(UTILS.MetersToNM(Distance),Precision).." миль"
end
end
return DistanceText
end
function COORDINATE:GetAltitudeText(Settings,Language)
local Altitude=self.y
local Settings=Settings or _SETTINGS
local Language=Language or Settings.Locale or _SETTINGS.Locale or"EN"
Language=string.lower(Language)
if Altitude~=0 then
if Settings:IsMetric()then
if Language=="en"then
return" at "..UTILS.Round(self.y,-3).." meters"
elseif Language=="ru"then
return" в "..UTILS.Round(self.y,-3).." метры"
end
else
if Language=="en"then
return" at "..UTILS.Round(UTILS.MetersToFeet(self.y),-3).." feet"
elseif Language=="ru"then
return" в "..UTILS.Round(self.y,-3).." ноги"
end
end
else
return""
end
end
function COORDINATE:GetVelocityText(Settings)
local Velocity=self:GetVelocity()
local Settings=Settings or _SETTINGS
if Velocity then
if Settings:IsMetric()then
return string.format(" moving at %d km/h",UTILS.MpsToKmph(Velocity))
else
return string.format(" moving at %d mi/h",UTILS.MpsToKmph(Velocity)/1.852)
end
else
return" stationary"
end
end
function COORDINATE:GetHeadingText(Settings)
local Heading=self:GetHeading()
if Heading then
return string.format(" bearing %3d°",Heading)
else
return" bearing unknown"
end
end
function COORDINATE:GetBRText(AngleRadians,Distance,Settings,Language,MagVar,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,Precision)
local BRText=BearingText..DistanceText
return BRText
end
function COORDINATE:GetBRAText(AngleRadians,Distance,Settings,Language,MagVar)
local Settings=Settings or _SETTINGS
local BearingText=self:GetBearingText(AngleRadians,0,Settings,MagVar)
local DistanceText=self:GetDistanceText(Distance,Settings,Language,0)
local AltitudeText=self:GetAltitudeText(Settings,Language)
local BRAText=BearingText..DistanceText..AltitudeText
return BRAText
end
function COORDINATE:SetAltitude(altitude,asl)
local alt=altitude
if asl then
alt=altitude
else
alt=self:GetLandHeight()+altitude
end
self.y=alt
return self
end
function COORDINATE:SetAtLandheight()
local alt=self:GetLandHeight()
self.y=alt
return self
end
function COORDINATE:WaypointAir(AltType,Type,Action,Speed,SpeedLocked,airbase,DCSTasks,description,timeReFuAr)
self:F2({AltType,Type,Action,Speed,SpeedLocked})
AltType=AltType or"RADIO"
if SpeedLocked==nil then
SpeedLocked=true
end
Speed=Speed or 500
local RoutePoint={}
RoutePoint.x=self.x
RoutePoint.y=self.z
RoutePoint.alt=self.y
RoutePoint.alt_type=AltType
RoutePoint.type=Type or nil
RoutePoint.action=Action or nil
RoutePoint.speed=Speed/3.6
RoutePoint.speed_locked=SpeedLocked
RoutePoint.ETA=0
RoutePoint.ETA_locked=false
RoutePoint.name=description
if airbase then
local AirbaseID=airbase:GetID()
local AirbaseCategory=airbase:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then
RoutePoint.linkUnit=AirbaseID
RoutePoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
RoutePoint.airdromeId=AirbaseID
else
self:E("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!")
end
end
if Type==COORDINATE.WaypointType.LandingReFuAr then
RoutePoint.timeReFuAr=timeReFuAr or 10
end
RoutePoint.task={}
RoutePoint.task.id="ComboTask"
RoutePoint.task.params={}
RoutePoint.task.params.tasks=DCSTasks or{}
self:T({RoutePoint=RoutePoint})
return RoutePoint
end
function COORDINATE:WaypointAirTurningPoint(AltType,Speed,DCSTasks,description)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description)
end
function COORDINATE:WaypointAirFlyOverPoint(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.FlyoverPoint,Speed)
end
function COORDINATE:WaypointAirTakeOffParkingHot(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParkingHot,COORDINATE.WaypointAction.FromParkingAreaHot,Speed)
end
function COORDINATE:WaypointAirTakeOffParking(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOffParking,COORDINATE.WaypointAction.FromParkingArea,Speed)
end
function COORDINATE:WaypointAirTakeOffRunway(AltType,Speed)
return self:WaypointAir(AltType,COORDINATE.WaypointType.TakeOff,COORDINATE.WaypointAction.FromRunway,Speed)
end
function COORDINATE:WaypointAirLanding(Speed,airbase,DCSTasks,description)
return self:WaypointAir(nil,COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,Speed,false,airbase,DCSTasks,description)
end
function COORDINATE:WaypointAirLandingReFu(Speed,airbase,timeReFuAr,DCSTasks,description)
return self:WaypointAir(nil,COORDINATE.WaypointType.LandingReFuAr,COORDINATE.WaypointAction.LandingReFuAr,Speed,false,airbase,DCSTasks,description,timeReFuAr or 10)
end
function COORDINATE:WaypointGround(Speed,Formation,DCSTasks)
self:F2({Speed,Formation,DCSTasks})
local RoutePoint={}
RoutePoint.x=self.x
RoutePoint.y=self.z
RoutePoint.alt=self:GetLandHeight()+1
RoutePoint.alt_type=COORDINATE.WaypointAltType.BARO
RoutePoint.type="Turning Point"
RoutePoint.action=Formation or"Off Road"
RoutePoint.formation_template=""
RoutePoint.ETA=0
RoutePoint.ETA_locked=false
RoutePoint.speed=(Speed or 20)/3.6
RoutePoint.speed_locked=true
RoutePoint.task={}
RoutePoint.task.id="ComboTask"
RoutePoint.task.params={}
RoutePoint.task.params.tasks=DCSTasks or{}
return RoutePoint
end
function COORDINATE:WaypointNaval(Speed,Depth,DCSTasks)
self:F2({Speed,Depth,DCSTasks})
local RoutePoint={}
RoutePoint.x=self.x
RoutePoint.y=self.z
RoutePoint.alt=Depth or self.y
RoutePoint.alt_type="BARO"
RoutePoint.type="Turning Point"
RoutePoint.action="Turning Point"
RoutePoint.formation_template=""
RoutePoint.ETA=0
RoutePoint.ETA_locked=false
RoutePoint.speed=(Speed or 20)/3.6
RoutePoint.speed_locked=true
RoutePoint.task={}
RoutePoint.task.id="ComboTask"
RoutePoint.task.params={}
RoutePoint.task.params.tasks=DCSTasks or{}
return RoutePoint
end
function COORDINATE:GetClosestAirbase(Category,Coalition)
local airbases=AIRBASE.GetAllAirbases(Coalition)
local closest=nil
local distmin=nil
for _,_airbase in pairs(airbases)do
local airbase=_airbase
if airbase then
local category=airbase:GetAirbaseCategory()
if Category and Category==category or Category==nil then
local dist=self:Get2DDistance(airbase:GetCoordinate())
if closest==nil then
distmin=dist
closest=airbase
else
if dist<distmin then
distmin=dist
closest=airbase
end
end
end
end
end
return closest,distmin
end
function COORDINATE:GetClosestAirbase2(Category,Coalition)
local closest,distmin=self:GetClosestAirbase(Category,Coalition)
return closest,distmin
end
function COORDINATE:GetClosestParkingSpot(airbase,terminaltype,free)
local airbases={}
if airbase then
table.insert(airbases,airbase)
else
airbases=AIRBASE.GetAllAirbases()
end
local _closest=nil
local _termID=nil
local _distmin=nil
local spot=nil
for _,_airbase in pairs(airbases)do
local mybase=_airbase
local parkingdata=mybase:GetParkingSpotsTable(terminaltype)
for _,_spot in pairs(parkingdata)do
if(free==true and _spot.Free==true)or(free==false and _spot.Free==false)or free==nil then
local _coord=_spot.Coordinate
local _dist=self:Get2DDistance(_coord)
if _distmin==nil then
_closest=_coord
_distmin=_dist
_termID=_spot.TerminalID
spot=_spot
else
if _dist<_distmin then
_distmin=_dist
_closest=_coord
_termID=_spot.TerminalID
spot=_spot
end
end
end
end
end
return _closest,_termID,_distmin,spot
end
function COORDINATE:GetClosestFreeParkingSpot(airbase,terminaltype)
return self:GetClosestParkingSpot(airbase,terminaltype,true)
end
function COORDINATE:GetClosestOccupiedParkingSpot(airbase,terminaltype)
return self:GetClosestParkingSpot(airbase,terminaltype,false)
end
function COORDINATE:GetClosestPointToRoad(Railroad)
local roadtype="roads"
if Railroad==true then
roadtype="railroads"
end
local x,y=land.getClosestPointOnRoads(roadtype,self.x,self.z)
local coord=nil
if x and y then
local vec2={x=x,y=y}
coord=COORDINATE:NewFromVec2(vec2)
end
return coord
end
function COORDINATE:GetPathOnRoad(ToCoord,IncludeEndpoints,Railroad,MarkPath,SmokePath)
local RoadType="roads"
if Railroad==true then
RoadType="railroads"
end
local path=land.findPathOnRoads(RoadType,self.x,self.z,ToCoord.x,ToCoord.z)
local Path={}
local Way=0
if IncludeEndpoints then
Path[1]=self
end
local GotPath=true
if path then
for _i,_vec2 in ipairs(path)do
local coord=COORDINATE:NewFromVec2(_vec2)
Path[#Path+1]=coord
end
else
self:E("Path is nil. No valid path on road could be found.")
GotPath=false
end
if IncludeEndpoints then
Path[#Path+1]=ToCoord
end
if MarkPath or SmokePath then
for i,c in pairs(Path)do
local coord=c
if MarkPath then
coord:MarkToAll(string.format("Path segment %d",i))
end
if SmokePath then
if i==1 or i==#Path then
coord:SmokeBlue()
else
coord:SmokeGreen()
end
end
end
end
if#Path>=2 then
for i=1,#Path-1 do
Way=Way+Path[i+1]:Get2DDistance(Path[i])
end
else
return nil,nil,false
end
return Path,Way,GotPath
end
function COORDINATE:GetSurfaceType()
local vec2=self:GetVec2()
local surface=land.getSurfaceType(vec2)
return surface
end
function COORDINATE:IsSurfaceTypeLand()
return self:GetSurfaceType()==land.SurfaceType.LAND
end
function COORDINATE:IsSurfaceTypeLand()
return self:GetSurfaceType()==land.SurfaceType.LAND
end
function COORDINATE:IsSurfaceTypeRoad()
return self:GetSurfaceType()==land.SurfaceType.ROAD
end
function COORDINATE:IsSurfaceTypeRunway()
return self:GetSurfaceType()==land.SurfaceType.RUNWAY
end
function COORDINATE:IsSurfaceTypeShallowWater()
return self:GetSurfaceType()==land.SurfaceType.SHALLOW_WATER
end
function COORDINATE:IsSurfaceTypeWater()
return self:GetSurfaceType()==land.SurfaceType.WATER
end
function COORDINATE:Explosion(ExplosionIntensity,Delay)
ExplosionIntensity=ExplosionIntensity or 100
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.Explosion,self,ExplosionIntensity)
else
trigger.action.explosion(self:GetVec3(),ExplosionIntensity)
end
return self
end
function COORDINATE:IlluminationBomb(Power,Delay)
Power=Power or 1000
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.IlluminationBomb,self,Power)
else
trigger.action.illuminationBomb(self:GetVec3(),Power)
end
return self
end
function COORDINATE:Smoke(SmokeColor,Duration,Delay,Name)
self:F2({SmokeColor,Name,Duration,Delay})
SmokeColor=SmokeColor or SMOKECOLOR.Green
if Delay and Delay>0 then
self:ScheduleOnce(Delay,COORDINATE.Smoke,self,SmokeColor,Duration,0,Name)
else
self.firename=Name or"Smoke-"..math.random(1,100000)
trigger.action.smoke(self:GetVec3(),SmokeColor,self.firename)
if Duration and Duration>0 then
self:ScheduleOnce(Duration,COORDINATE.StopSmoke,self,self.firename)
end
end
return self
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,Duration,Delay,Name)
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,Density,Duration,Delay,Name)
return self
end
function COORDINATE:BigSmokeAndFireMedium(Density,Duration,Delay,Name)
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,Density,Duration,Delay,Name)
return self
end
function COORDINATE:BigSmokeAndFireLarge(Density,Duration,Delay,Name)
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,Density,Duration,Delay,Name)
return self
end
function COORDINATE:BigSmokeAndFireHuge(Density,Duration,Delay,Name)
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,Density,Duration,Delay,Name)
return self
end
function COORDINATE:BigSmokeSmall(Density,Duration,Delay,Name)
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,Density,Duration,Delay,Name)
return self
end
function COORDINATE:BigSmokeMedium(Density,Duration,Delay,Name)
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,Density,Duration,Delay,Name)
return self
end
function COORDINATE:BigSmokeLarge(Density,Duration,Delay,Name)
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,Density,Duration,Delay,Name)
return self
end
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})
trigger.action.signalFlare(self:GetVec3(),FlareColor,Azimuth and Azimuth or 0)
end
function COORDINATE:FlareWhite(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.White,Azimuth)
end
function COORDINATE:FlareYellow(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.Yellow,Azimuth)
end
function COORDINATE:FlareGreen(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.Green,Azimuth)
end
function COORDINATE:FlareRed(Azimuth)
self:F2(Azimuth)
self:Flare(FLARECOLOR.Red,Azimuth)
end
do
function COORDINATE:MarkToAll(MarkText,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local text=Text or""
trigger.action.markToAll(MarkID,MarkText,self:GetVec3(),ReadOnly,text)
return MarkID
end
function COORDINATE:MarkToCoalition(MarkText,Coalition,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local text=Text or""
trigger.action.markToCoalition(MarkID,MarkText,self:GetVec3(),Coalition,ReadOnly,text)
return MarkID
end
function COORDINATE:MarkToCoalitionRed(MarkText,ReadOnly,Text)
return self:MarkToCoalition(MarkText,coalition.side.RED,ReadOnly,Text)
end
function COORDINATE:MarkToCoalitionBlue(MarkText,ReadOnly,Text)
return self:MarkToCoalition(MarkText,coalition.side.BLUE,ReadOnly,Text)
end
function COORDINATE:MarkToGroup(MarkText,MarkGroup,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local text=Text or""
trigger.action.markToGroup(MarkID,MarkText,self:GetVec3(),MarkGroup:GetID(),ReadOnly,text)
return MarkID
end
function COORDINATE:RemoveMark(MarkID)
trigger.action.removeMark(MarkID)
end
function COORDINATE:LineToAll(Endpoint,Coalition,Color,Alpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
trigger.action.lineToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:CircleToAll(Radius,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=self:GetVec3()
Radius=Radius or 1000
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.circleToAll(Coalition,MarkID,vec3,Radius,Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
end
function COORDINATE:RectToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.rectToAll(Coalition,MarkID,self:GetVec3(),vec3,Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:QuadToAll(Coord2,Coord3,Coord4,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local point1=self:GetVec3()
local point2=Coord2:GetVec3()
local point3=Coord3:GetVec3()
local point4=Coord4:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.quadToAll(Coalition,MarkID,point1,point2,point3,point4,Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:MarkupToAllFreeForm(Coordinates,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
local vecs={}
vecs[1]=self:GetVec3()
for i,coord in ipairs(Coordinates)do
vecs[i+1]=coord:GetVec3()
end
if#vecs<3 then
self:E("ERROR: A free form polygon needs at least three points!")
elseif#vecs==3 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==4 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==5 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==6 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==7 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==8 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==9 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==10 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==11 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==12 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==13 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],vecs[13],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==14 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],vecs[13],vecs[14],
Color,FillColor,LineType,ReadOnly,Text or"")
elseif#vecs==15 then
trigger.action.markupToAll(7,Coalition,MarkID,vecs[1],vecs[2],vecs[3],vecs[4],vecs[5],vecs[6],vecs[7],vecs[8],vecs[9],vecs[10],
vecs[11],vecs[12],vecs[13],vecs[14],vecs[15],
Color,FillColor,LineType,ReadOnly,Text or"")
else
local s=string.format("trigger.action.markupToAll(7, %d, %d,",Coalition,MarkID)
for _,vec in pairs(vecs)do
s=s..string.format("{x=%.1f, y=%.1f, z=%.1f},",vec.x,vec.y,vec.z)
end
s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",Color[1],Color[2],Color[3],Color[4])
s=s..string.format("{%.3f, %.3f, %.3f, %.3f},",FillColor[1],FillColor[2],FillColor[3],FillColor[4])
s=s..string.format("%d,",LineType or 1)
s=s..string.format("%s",tostring(ReadOnly))
if Text and type(Text)=="string"and string.len(Text)>0 then
s=s..string.format(", \"%s\"",tostring(Text))
end
s=s..")"
local success=UTILS.DoString(s)
if not success then
self:E("ERROR: Could not draw polygon")
env.info(s)
end
end
return MarkID
end
function COORDINATE:TextToAll(Text,Coalition,Color,Alpha,FillColor,FillAlpha,FontSize,ReadOnly)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.3
FontSize=FontSize or 14
trigger.action.textToAll(Coalition,MarkID,self:GetVec3(),Color,FillColor,FontSize,ReadOnly,Text or"Hello World")
return MarkID
end
function COORDINATE:ArrowToAll(Endpoint,Coalition,Color,Alpha,FillColor,FillAlpha,LineType,ReadOnly,Text)
local MarkID=UTILS.GetMarkID()
if ReadOnly==nil then
ReadOnly=false
end
local vec3=Endpoint:GetVec3()
Coalition=Coalition or-1
Color=Color or{1,0,0}
Color[4]=Alpha or 1.0
LineType=LineType or 1
FillColor=FillColor or UTILS.DeepCopy(Color)
FillColor[4]=FillAlpha or 0.15
trigger.action.arrowToAll(Coalition,MarkID,vec3,self:GetVec3(),Color,FillColor,LineType,ReadOnly,Text or"")
return MarkID
end
function COORDINATE:IsLOS(ToCoordinate,Offset)
Offset=Offset or 2
local FromVec3=self:GetVec3()
FromVec3.y=FromVec3.y+Offset
local ToVec3=ToCoordinate:GetVec3()
ToVec3.y=ToVec3.y+Offset
local IsLOS=land.isVisible(FromVec3,ToVec3)
return IsLOS
end
function COORDINATE:IsInRadius(Coordinate,Radius)
local InVec2=self:GetVec2()
local Vec2=Coordinate:GetVec2()
local InRadius=UTILS.IsInRadius(InVec2,Vec2,Radius)
return InRadius
end
function COORDINATE:IsInSphere(Coordinate,Radius)
local InVec3=self:GetVec3()
local Vec3=Coordinate:GetVec3()
local InSphere=UTILS.IsInSphere(InVec3,Vec3,Radius)
return InSphere
end
function COORDINATE:GetSunriseAtDate(Day,Month,Year,InSeconds)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
if InSeconds then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetSunriseAtDayOfYear(DayOfYear,InSeconds)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
if InSeconds then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetSunrise(InSeconds)
local DayOfYear=UTILS.GetMissionDayOfYear()
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
local date=UTILS.GetDCSMissionDate()
if InSeconds or type(sunrise)=="string"then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetMinutesToSunrise(OnlyToday)
local time=UTILS.SecondsOfToday()
local sunrise=nil
local delta=nil
if OnlyToday then
sunrise=self:GetSunrise(true)
delta=sunrise-time
else
local DayOfYear=UTILS.GetMissionDayOfYear()+1
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
delta=sunrise+UTILS.SecondsToMidnight()
end
return delta/60
end
function COORDINATE:IsDay(Clock)
if Clock then
local Time=UTILS.ClockToSeconds(Clock)
local clock=UTILS.Split(Clock,"+")[1]
local DayOfYear=UTILS.GetMissionDayOfYear(Time)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff)
local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
if sunrise=="N/R"then return false end
if sunrise=="N/S"then return true end
local time=UTILS.ClockToSeconds(clock)
if time>sunrise and time<=sunset then
return true
else
return false
end
else
local sunrise=self:GetSunrise(true)
local sunset=self:GetSunset(true)
local time=UTILS.SecondsOfToday()
if time>sunrise and time<=sunset then
return true
else
return false
end
end
end
function COORDINATE:IsNight(Clock)
return not self:IsDay(Clock)
end
function COORDINATE:GetSunsetAtDate(Day,Month,Year,InSeconds)
local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day)
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
if InSeconds then
return sunset
else
return UTILS.SecondsToClock(sunset,true)
end
end
function COORDINATE:GetSunset(InSeconds)
local DayOfYear=UTILS.GetMissionDayOfYear()
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
local date=UTILS.GetDCSMissionDate()
if InSeconds or type(sunrise)=="string"then
return sunrise
else
return UTILS.SecondsToClock(sunrise,true)
end
end
function COORDINATE:GetMinutesToSunset(OnlyToday)
local time=UTILS.SecondsOfToday()
local sunset=nil
local delta=nil
if OnlyToday then
sunset=self:GetSunset(true)
delta=sunset-time
else
local DayOfYear=UTILS.GetMissionDayOfYear()+1
local Latitude,Longitude=self:GetLLDDM()
local Tdiff=UTILS.GMTToLocalTimeDifference()
sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff)
delta=sunset+UTILS.SecondsToMidnight()
end
return delta/60
end
function COORDINATE:ToStringBR(FromCoordinate,Settings,MagVar,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,Precision)
end
function COORDINATE:ToStringBRA(FromCoordinate,Settings,MagVar)
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=FromCoordinate:Get2DDistance(self)
local Altitude=self:GetAltitudeText()
return"BRA, "..self:GetBRAText(AngleRadians,Distance,Settings,nil,MagVar)
end
function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML,Angels,Zeros)
local BRAANATO="Merged."
local currentCoord=FromCoordinate
local DirectionVec3=FromCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local bearing=UTILS.Round(UTILS.ToDegree(AngleRadians),0)
local 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)
local alt=UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)
local alttext=string.format("%d thousand",alt)
if Angels then
alttext=string.format("Angels %d",alt)
end
if alt<1 then
alttext="very low"
end
local track="Maneuver"
if self.Heading then
track=UTILS.BearingToCardinal(self.Heading)or"North"
end
if rangeNM>3 then
if SSML then
if Zeros then
bearing=string.format("%03d",bearing)
local AngleDegText=string.gsub(bearing,"%d","%1 ")
AngleDegText=string.gsub(AngleDegText," $","")
AngleDegText=string.gsub(AngleDegText,"0","zero")
if aspect==""then
BRAANATO=string.format("brah %s, %d miles, %s, Track %s",AngleDegText,rangeNM,alttext,track)
else
BRAANATO=string.format("brah %s, %d miles, %s, %s, Track %s",AngleDegText,rangeNM,alttext,aspect,track)
end
else
if aspect==""then
BRAANATO=string.format("brah <say-as interpret-as='characters'>%03d</say-as>, %d miles, %s, Track %s",bearing,rangeNM,alttext,track)
else
BRAANATO=string.format("brah <say-as interpret-as='characters'>%03d</say-as>, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track)
end
end
if Bogey and Spades then
BRAANATO=BRAANATO..", Bogey, Spades."
elseif Bogey then
BRAANATO=BRAANATO..", Bogey."
elseif Spades then
BRAANATO=BRAANATO..", Spades."
else
BRAANATO=BRAANATO.."."
end
else
if aspect==""then
BRAANATO=string.format("BRA %03d, %d miles, %s, Track %s",bearing,rangeNM,alttext,track)
else
BRAANATO=string.format("BRAA %03d, %d miles, %s, %s, Track %s",bearing,rangeNM,alttext,aspect,track)
end
if Bogey and Spades then
BRAANATO=BRAANATO..", Bogey, Spades."
elseif Bogey then
BRAANATO=BRAANATO..", Bogey."
elseif Spades then
BRAANATO=BRAANATO..", Spades."
else
BRAANATO=BRAANATO.."."
end
end
end
return BRAANATO
end
function COORDINATE.GetBullseyeCoordinate(Coalition)
return COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
end
function COORDINATE:ToStringBULLS(Coalition,Settings,MagVar)
local BullsCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(Coalition))
local DirectionVec3=BullsCoordinate:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(BullsCoordinate)
local Altitude=self:GetAltitudeText()
return"BULLS, "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar)
end
function COORDINATE:ToStringAspect(TargetCoordinate)
local Heading=self.Heading
local DirectionVec3=self:GetDirectionVec3(TargetCoordinate)
local Angle=self:GetAngleDegrees(DirectionVec3)
if Heading then
local Aspect=Angle-Heading
if Aspect>-135 and Aspect<=-45 then
return"Flanking"
end
if Aspect>-45 and Aspect<=45 then
return"Hot"
end
if Aspect>45 and Aspect<=135 then
return"Flanking"
end
if Aspect>135 or Aspect<=-135 then
return"Cold"
end
end
return""
end
function COORDINATE:GetLLDDM()
return coord.LOtoLL(self:GetVec3())
end
function COORDINATE:ToStringLL(Settings)
local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
local lat,lon=coord.LOtoLL(self:GetVec3())
return string.format('%f',lat)..' '..string.format('%f',lon)
end
function COORDINATE:ToStringLLDMS(Settings)
local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
local lat,lon=coord.LOtoLL(self:GetVec3())
return"LL DMS "..UTILS.tostringLL(lat,lon,LL_Accuracy,true)
end
function COORDINATE:ToStringLLDDM(Settings)
local LL_Accuracy=Settings and Settings.LL_Accuracy or _SETTINGS.LL_Accuracy
local lat,lon=coord.LOtoLL(self:GetVec3())
return"LL DDM "..UTILS.tostringLL(lat,lon,LL_Accuracy,false)
end
function COORDINATE:ToStringMGRS(Settings)
local MGRS_Accuracy=Settings and Settings.MGRS_Accuracy or _SETTINGS.MGRS_Accuracy
local lat,lon=coord.LOtoLL(self:GetVec3())
local MGRS=coord.LLtoMGRS(lat,lon)
return"MGRS "..UTILS.tostringMGRS(MGRS,MGRS_Accuracy)
end
function COORDINATE:NewFromMGRSString(MGRSString)
local myparts=UTILS.Split(MGRSString," ")
local northing=tostring(myparts[5])or""
local easting=tostring(myparts[4])or""
if string.len(easting)<5 then easting=easting..string.rep("0",5-string.len(easting))end
if string.len(northing)<5 then northing=northing..string.rep("0",5-string.len(northing))end
local MGRS={
UTMZone=myparts[2],
MGRSDigraph=myparts[3],
Easting=easting,
Northing=northing,
}
local lat,lon=coord.MGRStoLL(MGRS)
local point=coord.LLtoLO(lat,lon,0)
local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z})
return coord
end
function COORDINATE:NewFromMGRS(UTMZone,MGRSDigraph,Easting,Northing)
if string.len(Easting)<5 then Easting=tostring(Easting..string.rep("0",5-string.len(Easting)))end
if string.len(Northing)<5 then Northing=tostring(Northing..string.rep("0",5-string.len(Northing)))end
local MGRS={
UTMZone=UTMZone,
MGRSDigraph=MGRSDigraph,
Easting=tostring(Easting),
Northing=tostring(Northing),
}
local lat,lon=coord.MGRStoLL(MGRS)
local point=coord.LLtoLO(lat,lon,0)
local coord=COORDINATE:NewFromVec2({x=point.x,y=point.z})
return coord
end
function COORDINATE:ToStringFromRP(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar)
self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
local IsAir=Controllable and Controllable:IsAirPlane()or false
if IsAir then
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return"Targets are the last seen "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
else
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return"Target are located "..self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
end
return nil
end
function COORDINATE:ToStringFromRPShort(ReferenceCoord,ReferenceName,Controllable,Settings,MagVar)
self:F2({ReferenceCoord=ReferenceCoord,ReferenceName=ReferenceName})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
local IsAir=Controllable and Controllable:IsAirPlane()or false
if IsAir then
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
else
local DirectionVec3=ReferenceCoord:GetDirectionVec3(self)
local AngleRadians=self:GetAngleRadians(DirectionVec3)
local Distance=self:Get2DDistance(ReferenceCoord)
return self:GetBRText(AngleRadians,Distance,Settings,nil,MagVar).." from "..ReferenceName
end
return nil
end
function COORDINATE:ToStringA2G(Controllable,Settings,MagVar)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
if Settings:IsA2G_BR()then
if Controllable then
local Coordinate=Controllable:GetCoordinate()
return Controllable and self:ToStringBR(Coordinate,Settings,MagVar)or self:ToStringMGRS(Settings)
else
return self:ToStringMGRS(Settings)
end
end
if Settings:IsA2G_LL_DMS()then
return self:ToStringLLDMS(Settings)
end
if Settings:IsA2G_LL_DDM()then
return self:ToStringLLDDM(Settings)
end
if Settings:IsA2G_MGRS()then
return self:ToStringMGRS(Settings)
end
return nil
end
function COORDINATE:ToStringA2A(Controllable,Settings,MagVar)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
if Settings:IsA2A_BRAA()then
if Controllable then
local Coordinate=Controllable:GetCoordinate()
return self:ToStringBRA(Coordinate,Settings,MagVar)
else
return self:ToStringMGRS(Settings)
end
end
if Settings:IsA2A_BULLS()then
local Coalition=Controllable:GetCoalition()
return self:ToStringBULLS(Coalition,Settings,MagVar)
end
if Settings:IsA2A_LL_DMS()then
return self:ToStringLLDMS(Settings)
end
if Settings:IsA2A_LL_DDM()then
return self:ToStringLLDDM(Settings)
end
if Settings:IsA2A_MGRS()then
return self:ToStringMGRS(Settings)
end
return nil
end
function COORDINATE:ToString(Controllable,Settings)
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
local ModeA2A=nil
if ModeA2A==nil then
local IsAir=Controllable and(Controllable:IsAirPlane()or Controllable:IsHelicopter())or false
if IsAir then
ModeA2A=true
else
ModeA2A=false
end
end
if ModeA2A==true then
return self:ToStringA2A(Controllable,Settings)
else
return self:ToStringA2G(Controllable,Settings)
end
return nil
end
function COORDINATE:ToStringPressure(Controllable,Settings)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
return self:GetPressureText(nil,Settings)
end
function COORDINATE:ToStringWind(Controllable,Settings)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
return self:GetWindText(nil,Settings)
end
function COORDINATE:ToStringTemperature(Controllable,Settings)
self:F2({Controllable=Controllable and Controllable:GetName()})
local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS
return self:GetTemperatureText(nil,Settings)
end
function COORDINATE:IsInSteepArea(Radius,Minelevation)
local steep=false
local elev=Minelevation or 8
local bdelta=0
local h0=self:GetLandHeight()
local radius=Radius or 50
local diam=radius*2
for i=0,150,30 do
local polar=math.fmod(i+180,360)
local c1=self:Translate(radius,i,false,false)
local c2=self:Translate(radius,polar,false,false)
local h1=c1:GetLandHeight()
local h2=c2:GetLandHeight()
local d1=math.abs(h1-h2)
local d2=math.abs(h0-h1)
local d3=math.abs(h0-h2)
local dm=d1>d2 and d1 or d2
local dm1=dm>d3 and dm or d3
bdelta=dm1>bdelta and dm1 or bdelta
self:T(string.format("d1=%d, d2=%d, d3=%d, max delta=%d",d1,d2,d3,bdelta))
end
local steepness=bdelta/(radius/100)
if steepness>=elev then steep=true end
return steep,math.floor(steepness)
end
function COORDINATE:IsInFlatArea(Radius,Minelevation)
local steep,elev=self:IsInSteepArea(Radius,Minelevation)
local flat=not steep
return flat,elev
end
function COORDINATE:GetRandomPointVec3InRadius(OuterRadius,InnerRadius)
return COORDINATE:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius))
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",
}
function VELOCITY:New(VelocityMps)
local self=BASE:Inherit(self,BASE:New())
self:F({})
self.Velocity=VelocityMps
return self
end
function VELOCITY:Set(VelocityMps)
self.Velocity=VelocityMps
return self
end
function VELOCITY:Get()
return self.Velocity
end
function VELOCITY:SetKmph(VelocityKmph)
self.Velocity=UTILS.KmphToMps(VelocityKmph)
return self
end
function VELOCITY:GetKmph()
return UTILS.MpsToKmph(self.Velocity)
end
function VELOCITY:SetMiph(VelocityMiph)
self.Velocity=UTILS.MiphToMps(VelocityMiph)
return self
end
function VELOCITY:GetMiph()
return UTILS.MpsToMiph(self.Velocity)
end
function VELOCITY:GetText(Settings)
local Settings=Settings or _SETTINGS
if self.Velocity~=0 then
if Settings:IsMetric()then
return string.format("%d km/h",UTILS.MpsToKmph(self.Velocity))
else
return string.format("%d mi/h",UTILS.MpsToMiph(self.Velocity))
end
else
return"stationary"
end
end
function VELOCITY:ToString(VelocityGroup,Settings)
self:F({Group=VelocityGroup and VelocityGroup:GetName()})
local Settings=Settings or(VelocityGroup and _DATABASE:GetPlayerSettings(VelocityGroup:GetPlayerName()))or _SETTINGS
return self:GetText(Settings)
end
end
do
VELOCITY_POSITIONABLE={
ClassName="VELOCITY_POSITIONABLE",
}
function VELOCITY_POSITIONABLE:New(Positionable)
local self=BASE:Inherit(self,VELOCITY:New())
self:F({})
self.Positionable=Positionable
return self
end
function VELOCITY_POSITIONABLE:Get()
return self.Positionable:GetVelocityMPS()or 0
end
function VELOCITY_POSITIONABLE:GetKmph()
return UTILS.MpsToKmph(self.Positionable:GetVelocityMPS()or 0)
end
function VELOCITY_POSITIONABLE:GetMiph()
return UTILS.MpsToMiph(self.Positionable:GetVelocityMPS()or 0)
end
function VELOCITY_POSITIONABLE:ToString()
self:F({Group=self.Positionable and self.Positionable:GetName()})
local Settings=Settings or(self.Positionable and _DATABASE:GetPlayerSettings(self.Positionable:GetPlayerName()))or _SETTINGS
self.Velocity=self.Positionable:GetVelocityMPS()
return self:GetText(Settings)
end
end
MESSAGE={
ClassName="MESSAGE",
MessageCategory=0,
MessageID=0,
}
MESSAGE.Type={
Update="Update",
Information="Information",
Briefing="Briefing Report",
Overview="Overview Report",
Detailed="Detailed Report",
}
function MESSAGE:New(Text,Duration,Category,ClearScreen)
local self=BASE:Inherit(self,BASE:New())
self:F({Text,Duration,Category})
self.MessageType=nil
if Category and Category~=""then
if Category:sub(-1)~="\n"then
self.MessageCategory=Category..": "
else
self.MessageCategory=Category:sub(1,-2)..":\n"
end
else
self.MessageCategory=""
end
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
end
self.MessageDuration=Duration or 5
self.MessageTime=timer.getTime()
self.MessageText=Text:gsub("^\n","",1):gsub("\n$","",1)
self.MessageSent=false
self.MessageGroup=false
self.MessageCoalition=false
return self
end
function MESSAGE:NewType(MessageText,MessageType,ClearScreen)
local self=BASE:Inherit(self,BASE:New())
self:F({MessageText})
self.MessageType=MessageType
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
end
self.MessageTime=timer.getTime()
self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1)
return self
end
function MESSAGE:Clear()
self:F()
self.ClearScreen=true
return self
end
function MESSAGE:ToClient(Client,Settings)
self:F(Client)
self:ToUnit(Client,Settings)
return self
end
function MESSAGE:ToGroup(Group,Settings)
self:F(Group.GroupName)
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)
self.MessageCategory=""
end
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForGroup(Group:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToUnit(Unit,Settings)
self:F(Unit.IdentifiableName)
if Unit 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)
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
return self
end
function MESSAGE:ToCountry(Country,Settings)
self:F(Country)
if Country then
if self.MessageType then
local Settings=Settings or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForCountry(Country,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToCountryIf(Country,Condition,Settings)
self:F(Country)
if Country and Condition==true then
self:ToCountry(Country,Settings)
end
return self
end
function MESSAGE:ToBlue()
self:F()
self:ToCoalition(coalition.side.BLUE)
return self
end
function MESSAGE:ToRed()
self:F()
self:ToCoalition(coalition.side.RED)
return self
end
function MESSAGE:ToCoalition(CoalitionSide,Settings)
self:F(CoalitionSide)
if self.MessageType then
local Settings=Settings or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if CoalitionSide then
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outTextForCoalition(CoalitionSide,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
self.CoalitionSide=CoalitionSide
return self
end
function MESSAGE:ToCoalitionIf(CoalitionSide,Condition)
self:F(CoalitionSide)
if Condition and Condition==true then
self:ToCoalition(CoalitionSide)
end
return self
end
function MESSAGE:ToAll(Settings,Delay)
self:F()
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MESSAGE.ToAll,self,Settings,0)
else
if self.MessageType then
local Settings=Settings or _SETTINGS
self.MessageDuration=Settings:GetMessageTime(self.MessageType)
self.MessageCategory=""
end
if self.MessageDuration~=0 then
self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration)
trigger.action.outText(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen)
end
end
return self
end
function MESSAGE:ToAllIf(Condition)
if Condition and Condition==true then
self:ToAll()
end
return self
end
function MESSAGE:ToLog()
env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""))
return self
end
function MESSAGE:ToLogIf(Condition)
if Condition and Condition==true then
env.info(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""))
end
return self
end
_MESSAGESRS={}
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend)
_MESSAGESRS.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
_MESSAGESRS.frequency=Frequency or MSRS.frequencies or 243
_MESSAGESRS.modulation=Modulation or MSRS.modulations or radio.modulation.AM
_MESSAGESRS.MSRS=MSRS:New(_MESSAGESRS.PathToSRS,_MESSAGESRS.frequency,_MESSAGESRS.modulation)
_MESSAGESRS.coalition=Coalition or MSRS.coalition or coalition.side.NEUTRAL
_MESSAGESRS.MSRS:SetCoalition(_MESSAGESRS.coalition)
_MESSAGESRS.coordinate=Coordinate
if Coordinate then
_MESSAGESRS.MSRS:SetCoordinate(Coordinate)
end
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"
_MESSAGESRS.MSRS:SetGender(Gender)
if PathToCredentials then
_MESSAGESRS.MSRS:SetProviderOptionsGoogle(PathToCredentials)
_MESSAGESRS.MSRS:SetProvider(MSRS.Provider.GOOGLE)
end
_MESSAGESRS.label=Label or MSRS.Label or"MESSAGE"
_MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label)
_MESSAGESRS.port=Port or MSRS.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
_MESSAGESRS.voice=Voice or MSRS.voice
_MESSAGESRS.SRSQ=MSRSQUEUE:New(_MESSAGESRS.label)
end
function MESSAGE:ToSRS(frequency,modulation,gender,culture,voice,coalition,volume,coordinate)
local tgender=gender or _MESSAGESRS.Gender
if _MESSAGESRS.SRSQ then
if voice then
_MESSAGESRS.MSRS:SetVoice(voice or _MESSAGESRS.voice)
end
if coordinate then
_MESSAGESRS.MSRS:SetCoordinate(coordinate)
end
local category=string.gsub(self.MessageCategory,":","")
_MESSAGESRS.SRSQ:NewTransmission(self.MessageText,nil,_MESSAGESRS.MSRS,0.5,1,nil,nil,nil,frequency or _MESSAGESRS.frequency,modulation or _MESSAGESRS.modulation,gender or _MESSAGESRS.Gender,culture or _MESSAGESRS.Culture,nil,volume or _MESSAGESRS.volume,category,coordinate or _MESSAGESRS.coordinate)
end
return self
end
function MESSAGE:ToSRSBlue(frequency,modulation,gender,culture,voice,volume,coordinate)
self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.BLUE,volume,coordinate)
return self
end
function MESSAGE:ToSRSRed(frequency,modulation,gender,culture,voice,volume,coordinate)
self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.RED,volume,coordinate)
return self
end
function MESSAGE:ToSRSAll(frequency,modulation,gender,culture,voice,volume,coordinate)
self:ToSRS(frequency,modulation,gender,culture,voice,coalition.side.NEUTRAL,volume,coordinate)
return self
end
do
FSM={
ClassName="FSM",
}
function FSM:New()
self=BASE:Inherit(self,BASE:New())
self.options=options or{}
self.options.subs=self.options.subs or{}
self.current=self.options.initial or'none'
self.Events={}
self.subs={}
self.endstates={}
self.Scores={}
self._StartState="none"
self._Transitions={}
self._Processes={}
self._EndStates={}
self._Scores={}
self._EventSchedules={}
self.CallScheduler=SCHEDULER:New(self)
return self
end
function FSM:SetStartState(State)
self._StartState=State
self.current=State
end
function FSM:GetStartState()
return self._StartState or{}
end
function FSM:AddTransition(From,Event,To)
local Transition={}
Transition.From=From
Transition.Event=Event
Transition.To=To
self._Transitions[Transition]=Transition
self:_eventmap(self.Events,Transition)
end
function FSM:GetTransitions()
return self._Transitions or{}
end
function FSM:AddProcess(From,Event,Process,ReturnEvents)
local Sub={}
Sub.From=From
Sub.Event=Event
Sub.fsm=Process
Sub.StartEvent="Start"
Sub.ReturnEvents=ReturnEvents
self._Processes[Sub]=Sub
self:_submap(self.subs,Sub,nil)
self:AddTransition(From,Event,From)
return Process
end
function FSM:GetProcesses()
self:F({Processes=self._Processes})
return self._Processes or{}
end
function FSM:GetProcess(From,Event)
for ProcessID,Process in pairs(self:GetProcesses())do
if Process.From==From and Process.Event==Event then
return Process.fsm
end
end
error("Sub-Process from state "..From.." with event "..Event.." not found!")
end
function FSM:SetProcess(From,Event,Fsm)
for ProcessID,Process in pairs(self:GetProcesses())do
if Process.From==From and Process.Event==Event then
Process.fsm=Fsm
return true
end
end
error("Sub-Process from state "..From.." with event "..Event.." not found!")
end
function FSM:AddEndState(State)
self._EndStates[State]=State
self.endstates[State]=State
end
function FSM:GetEndStates()
return self._EndStates or{}
end
function FSM:AddScore(State,ScoreText,Score)
self:F({State,ScoreText,Score})
self._Scores[State]=self._Scores[State]or{}
self._Scores[State].ScoreText=ScoreText
self._Scores[State].Score=Score
return self
end
function FSM:AddScoreProcess(From,Event,State,ScoreText,Score)
self:F({From,Event,State,ScoreText,Score})
local Process=self:GetProcess(From,Event)
Process._Scores[State]=Process._Scores[State]or{}
Process._Scores[State].ScoreText=ScoreText
Process._Scores[State].Score=Score
return Process
end
function FSM:GetScores()
return self._Scores or{}
end
function FSM:GetSubs()
return self.options.subs
end
function FSM:LoadCallBacks(CallBackTable)
for name,callback in pairs(CallBackTable or{})do
self[name]=callback
end
end
function FSM:_eventmap(Events,EventStructure)
local Event=EventStructure.Event
local __Event="__"..EventStructure.Event
self[Event]=self[Event]or self:_create_transition(Event)
self[__Event]=self[__Event]or self:_delayed_transition(Event)
Events[Event]=self.Events[Event]or{map={}}
self:_add_to_map(Events[Event].map,EventStructure)
end
function FSM:_submap(subs,sub,name)
subs[sub.From]=subs[sub.From]or{}
subs[sub.From][sub.Event]=subs[sub.From][sub.Event]or{}
subs[sub.From][sub.Event][sub]={}
subs[sub.From][sub.Event][sub].fsm=sub.fsm
subs[sub.From][sub.Event][sub].StartEvent=sub.StartEvent
subs[sub.From][sub.Event][sub].ReturnEvents=sub.ReturnEvents or{}
subs[sub.From][sub.Event][sub].name=name
subs[sub.From][sub.Event][sub].fsmparent=self
end
function FSM:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
if self[handler]then
self._EventSchedules[EventName]=nil
local ErrorHandler=function(errmsg)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
local Result,Value=xpcall(function()
return self[handler](self,unpack(params))
end,ErrorHandler)
return Value
end
end
function FSM._handler(self,EventName,...)
local Can,To=self:can(EventName)
if To=="*"then
To=self.current
end
if Can then
local From=self.current
local Params={From,EventName,To,...}
if self["onleave"..From]or
self["OnLeave"..From]or
self["onbefore"..EventName]or
self["OnBefore"..EventName]or
self["onafter"..EventName]or
self["OnAfter"..EventName]or
self["onenter"..To]or
self["OnEnter"..To]then
if self:_call_handler("onbefore",EventName,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onbefore"..EventName)
return false
else
if self:_call_handler("OnBefore",EventName,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnBefore"..EventName)
return false
else
if self:_call_handler("onleave",From,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** onleave"..From)
return false
else
if self:_call_handler("OnLeave",From,Params,EventName)==false then
self:T("*** FSM ***    Cancel".." *** "..self.current.." --> "..EventName.." --> "..To.." *** OnLeave"..From)
return false
end
end
end
end
else
local ClassName=self:GetClassName()
if ClassName=="FSM"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To)
end
if ClassName=="FSM_TASK"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.TaskName)
end
if ClassName=="FSM_CONTROLLABLE"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** TaskUnit: "..self.Controllable.ControllableName.." *** ")
end
if ClassName=="FSM_PROCESS"then
self:T("*** FSM ***    Transit *** "..self.current.." --> "..EventName.." --> "..To.." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable.ControllableName.." *** ")
end
end
self.current=To
local execute=true
local subtable=self:_gosub(From,EventName)
for _,sub in pairs(subtable)do
self:T("*** FSM ***    Sub *** "..sub.StartEvent)
sub.fsm.fsmparent=self
sub.fsm.ReturnEvents=sub.ReturnEvents
sub.fsm[sub.StartEvent](sub.fsm)
execute=false
end
local fsmparent,Event=self:_isendstate(To)
if fsmparent and Event then
self:T("*** FSM ***    End *** "..Event)
self:_call_handler("onenter",To,Params,EventName)
self:_call_handler("OnEnter",To,Params,EventName)
self:_call_handler("onafter",EventName,Params,EventName)
self:_call_handler("OnAfter",EventName,Params,EventName)
self:_call_handler("onstate","change",Params,EventName)
fsmparent[Event](fsmparent)
execute=false
end
if execute then
self:_call_handler("onafter",EventName,Params,EventName)
self:_call_handler("OnAfter",EventName,Params,EventName)
self:_call_handler("onenter",To,Params,EventName)
self:_call_handler("OnEnter",To,Params,EventName)
self:_call_handler("onstate","change",Params,EventName)
end
else
self:T("*** FSM *** NO Transition *** "..self.current.." --> "..EventName.." -->  ? ")
end
return nil
end
function FSM:_delayed_transition(EventName)
return function(self,DelaySeconds,...)
self:T3("Delayed Event: "..EventName)
local CallID=0
if DelaySeconds~=nil then
if DelaySeconds<0 then
DelaySeconds=math.abs(DelaySeconds)
if not self._EventSchedules[EventName]then
CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true)
self._EventSchedules[EventName]=CallID
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID)))
else
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.",EventName,DelaySeconds))
end
else
CallID=self.CallScheduler:Schedule(self,self._handler,{EventName,...},DelaySeconds or 1,nil,nil,nil,4,true)
self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID)))
end
else
error("FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this.")
end
end
end
function FSM:_create_transition(EventName)
return function(self,...)
return self._handler(self,EventName,...)
end
end
function FSM:_gosub(ParentFrom,ParentEvent)
local fsmtable={}
if self.subs[ParentFrom]and self.subs[ParentFrom][ParentEvent]then
return self.subs[ParentFrom][ParentEvent]
else
return{}
end
end
function FSM:_isendstate(Current)
local FSMParent=self.fsmparent
if FSMParent and self.endstates[Current]then
FSMParent.current=Current
local ParentFrom=FSMParent.current
local Event=self.ReturnEvents[Current]
if Event then
return FSMParent,Event
else
end
end
return nil
end
function FSM:_add_to_map(Map,Event)
self:F3({Map,Event})
if type(Event.From)=='string'then
Map[Event.From]=Event.To
else
for _,From in ipairs(Event.From)do
Map[From]=Event.To
end
end
end
function FSM:GetState()
return self.current
end
function FSM:GetCurrentState()
return self.current
end
function FSM:Is(State)
return self.current==State
end
function FSM:is(state)
return self.current==state
end
function FSM:can(e)
local Event=self.Events[e]
local To=Event and Event.map[self.current]or Event.map['*']
return To~=nil,To
end
function FSM:cannot(e)
return not self:can(e)
end
end
do
FSM_CONTROLLABLE={
ClassName="FSM_CONTROLLABLE",
}
function FSM_CONTROLLABLE:New(Controllable)
local self=BASE:Inherit(self,FSM:New())
if Controllable then
self:SetControllable(Controllable)
end
self:AddTransition("*","Stop","Stopped")
return self
end
function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To)
self.CallScheduler:Clear()
end
function FSM_CONTROLLABLE:SetControllable(FSMControllable)
self.Controllable=FSMControllable
end
function FSM_CONTROLLABLE:GetControllable()
return self.Controllable
end
function FSM_CONTROLLABLE:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
local ErrorHandler=function(errmsg)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
if self[handler]then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** TaskUnit: "..self.Controllable:GetName())
self._EventSchedules[EventName]=nil
local Result,Value=xpcall(function()
return self[handler](self,self.Controllable,unpack(params))
end,ErrorHandler)
return Value
end
end
end
do
FSM_PROCESS={ClassName="FSM_PROCESS"}
function FSM_PROCESS:New(Controllable,Task)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
self:Assign(Controllable,Task)
return self
end
function FSM_PROCESS:Init(FsmProcess)
self:T("No Initialisation")
end
function FSM_PROCESS:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
local ErrorHandler=function(errmsg)
env.info("Error in FSM_PROCESS call handler:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
if self[handler]then
if handler~="onstatechange"then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.Task:GetName()..", TaskUnit: "..self.Controllable:GetName())
end
self._EventSchedules[EventName]=nil
local Result,Value
if self.Controllable and self.Controllable:IsAlive()==true then
Result,Value=xpcall(function()
return self[handler](self,self.Controllable,self.Task,unpack(params))
end,ErrorHandler)
end
return Value
end
end
function FSM_PROCESS:Copy(Controllable,Task)
local NewFsm=self:New(Controllable,Task)
NewFsm:Assign(Controllable,Task)
NewFsm:Init(self)
NewFsm:SetStartState(self:GetStartState())
for TransitionID,Transition in pairs(self:GetTransitions())do
NewFsm:AddTransition(Transition.From,Transition.Event,Transition.To)
end
for ProcessID,Process in pairs(self:GetProcesses())do
local FsmProcess=NewFsm:AddProcess(Process.From,Process.Event,Process.fsm:Copy(Controllable,Task),Process.ReturnEvents)
end
for EndStateID,EndState in pairs(self:GetEndStates())do
NewFsm:AddEndState(EndState)
end
for ScoreID,Score in pairs(self:GetScores())do
NewFsm:AddScore(ScoreID,Score.ScoreText,Score.Score)
end
return NewFsm
end
function FSM_PROCESS:Remove()
self:F({self:GetClassNameAndID()})
self:F("Clearing Schedules")
self.CallScheduler:Clear()
for ProcessID,Process in pairs(self:GetProcesses())do
if Process.fsm then
Process.fsm:Remove()
Process.fsm=nil
end
end
return self
end
function FSM_PROCESS:SetTask(Task)
self.Task=Task
return self
end
function FSM_PROCESS:GetTask()
return self.Task
end
function FSM_PROCESS:GetMission()
return self.Task.Mission
end
function FSM_PROCESS:GetCommandCenter()
return self:GetTask():GetMission():GetCommandCenter()
end
function FSM_PROCESS:Message(Message)
self:F({Message=Message})
local CC=self:GetCommandCenter()
local TaskGroup=self.Controllable:GetGroup()
local PlayerName=self.Controllable:GetPlayerName()
PlayerName=PlayerName and" ("..PlayerName..")"or""
local Callsign=self.Controllable:GetCallsign()
local Prefix=Callsign and" @ "..Callsign..PlayerName or""
Message=Prefix..": "..Message
CC:MessageToGroup(Message,TaskGroup)
end
function FSM_PROCESS:Assign(ProcessUnit,Task)
self:SetControllable(ProcessUnit)
self:SetTask(Task)
return self
end
function FSM_PROCESS:onenterFailed(ProcessUnit,Task,From,Event,To)
self:T("*** FSM ***    Failed *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To)
self.Task:Fail()
end
function FSM_PROCESS:onstatechange(ProcessUnit,Task,From,Event,To)
if From~=To then
self:T("*** FSM ***    Change *** "..Task:GetName().."/"..ProcessUnit:GetName().." *** "..From.." --> "..Event.." --> "..To)
end
if self._Scores[To]then
local Task=self.Task
local Scoring=Task:GetScoring()
if Scoring then
Scoring:_AddMissionTaskScore(Task.Mission,ProcessUnit,self._Scores[To].ScoreText,self._Scores[To].Score)
end
end
end
end
do
FSM_TASK={
ClassName="FSM_TASK",
}
function FSM_TASK:New(TaskName)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
self["onstatechange"]=self.OnStateChange
self.TaskName=TaskName
return self
end
function FSM_TASK:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
local ErrorHandler=function(errmsg)
env.info("Error in SCHEDULER function:"..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
if self[handler]then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3].." *** Task: "..self.TaskName)
self._EventSchedules[EventName]=nil
local Result,Value=xpcall(function()
return self[handler](self,unpack(params))
end,ErrorHandler)
return Value
end
end
end
do
FSM_SET={
ClassName="FSM_SET",
}
function FSM_SET:New(FSMSet)
self=BASE:Inherit(self,FSM:New())
if FSMSet then
self:Set(FSMSet)
end
return self
end
function FSM_SET:Set(FSMSet)
self:F(FSMSet)
self.Set=FSMSet
end
function FSM_SET:Get()
return self.Set
end
function FSM_SET:_call_handler(step,trigger,params,EventName)
local handler=step..trigger
if self[handler]then
self:T("*** FSM ***    "..step.." *** "..params[1].." --> "..params[2].." --> "..params[3])
self._EventSchedules[EventName]=nil
return self[handler](self,self.Set,unpack(params))
end
end
end
SPAWN={
ClassName="SPAWN",
SpawnTemplatePrefix=nil,
SpawnAliasPrefix=nil,
}
SPAWN.Takeoff={
Air=1,
Runway=2,
Hot=3,
Cold=4,
}
function SPAWN:New(SpawnTemplatePrefix)
local self=BASE:Inherit(self,BASE:New())
local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix)
if TemplateGroup then
self.SpawnTemplatePrefix=SpawnTemplatePrefix
self.SpawnIndex=0
self.SpawnCount=0
self.AliveUnits=0
self.SpawnIsScheduled=false
self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix)
self.Repeat=false
self.UnControlled=false
self.SpawnInitLimit=false
self.SpawnMaxUnitsAlive=0
self.SpawnMaxGroups=0
self.SpawnRandomize=false
self.SpawnVisible=false
self.AIOnOff=true
self.SpawnUnControlled=false
self.SpawnInitKeepUnitNames=false
self.DelayOnOff=false
self.SpawnGrouping=nil
self.SpawnInitLivery=nil
self.SpawnInitSkill=nil
self.SpawnInitFreq=nil
self.SpawnInitModu=nil
self.SpawnInitRadio=nil
self.SpawnInitModex=nil
self.SpawnInitModexPrefix=nil
self.SpawnInitModexPostfix=nil
self.SpawnInitAirbase=nil
self.TweakedTemplate=false
self.SpawnRandomCallsign=false
self.SpawnGroups={}
else
error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
end
self:SetEventPriority(5)
self.SpawnHookScheduler=SCHEDULER:New(nil)
return self
end
function SPAWN:NewWithAlias(SpawnTemplatePrefix,SpawnAliasPrefix)
local self=BASE:Inherit(self,BASE:New())
local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix)
if TemplateGroup then
self.SpawnTemplatePrefix=SpawnTemplatePrefix
self.SpawnAliasPrefix=SpawnAliasPrefix
self.SpawnIndex=0
self.SpawnCount=0
self.AliveUnits=0
self.SpawnIsScheduled=false
self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix)
self.Repeat=false
self.UnControlled=false
self.SpawnInitLimit=false
self.SpawnMaxUnitsAlive=0
self.SpawnMaxGroups=0
self.SpawnRandomize=false
self.SpawnVisible=false
self.AIOnOff=true
self.SpawnUnControlled=false
self.SpawnInitKeepUnitNames=false
self.DelayOnOff=false
self.SpawnGrouping=nil
self.SpawnInitLivery=nil
self.SpawnInitSkill=nil
self.SpawnInitFreq=nil
self.SpawnInitModu=nil
self.SpawnInitRadio=nil
self.SpawnInitModex=nil
self.SpawnInitModexPrefix=nil
self.SpawnInitModexPostfix=nil
self.SpawnInitAirbase=nil
self.TweakedTemplate=false
self.SpawnGroups={}
else
error("SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
end
self:SetEventPriority(5)
self.SpawnHookScheduler=SCHEDULER:New(nil)
return self
end
function SPAWN:NewFromTemplate(SpawnTemplate,SpawnTemplatePrefix,SpawnAliasPrefix,NoMooseNamingPostfix)
local self=BASE:Inherit(self,BASE:New())
if SpawnTemplatePrefix==nil or SpawnTemplatePrefix==""then
BASE:I("ERROR: in function NewFromTemplate, required parameter SpawnTemplatePrefix is not set")
return nil
end
if SpawnTemplate then
self.SpawnTemplate=UTILS.DeepCopy(SpawnTemplate)
self.SpawnTemplatePrefix=SpawnTemplatePrefix
self.SpawnAliasPrefix=SpawnAliasPrefix or SpawnTemplatePrefix
self.SpawnTemplate.name=SpawnTemplatePrefix
self.SpawnIndex=0
self.SpawnCount=0
self.AliveUnits=0
self.SpawnIsScheduled=false
self.Repeat=false
self.UnControlled=false
self.SpawnInitLimit=false
self.SpawnMaxUnitsAlive=0
self.SpawnMaxGroups=0
self.SpawnRandomize=false
self.SpawnVisible=false
self.AIOnOff=true
self.SpawnUnControlled=false
self.SpawnInitKeepUnitNames=false
self.DelayOnOff=false
self.Grouping=nil
self.SpawnInitLivery=nil
self.SpawnInitSkill=nil
self.SpawnInitFreq=nil
self.SpawnInitModu=nil
self.SpawnInitRadio=nil
self.SpawnInitModex=nil
self.SpawnInitModexPrefix=nil
self.SpawnInitModexPostfix=nil
self.SpawnInitAirbase=nil
self.TweakedTemplate=true
self.MooseNameing=true
if NoMooseNamingPostfix==true then
self.MooseNameing=false
end
self.SpawnGroups={}
else
error("There is no template provided for SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'")
end
self:SetEventPriority(5)
self.SpawnHookScheduler=SCHEDULER:New(nil)
return self
end
function SPAWN:InitLimit(SpawnMaxUnitsAlive,SpawnMaxGroups)
self.SpawnInitLimit=true
self.SpawnMaxUnitsAlive=SpawnMaxUnitsAlive
self.SpawnMaxGroups=SpawnMaxGroups
for SpawnGroupID=1,self.SpawnMaxGroups do
self:_InitializeSpawnGroups(SpawnGroupID)
end
return self
end
function SPAWN:InitKeepUnitNames(KeepUnitNames)
self.SpawnInitKeepUnitNames=false
if KeepUnitNames==true then self.SpawnInitKeepUnitNames=true end
return self
end
function SPAWN:InitLateActivated(LateActivated)
self.LateActivated=LateActivated or true
return self
end
function SPAWN:InitAirbase(AirbaseName,Takeoff,TerminalType)
self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName)
self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot
self.SpawnInitTerminalType=TerminalType
return self
end
function SPAWN:InitHeading(HeadingMin,HeadingMax)
self.SpawnInitHeadingMin=HeadingMin
self.SpawnInitHeadingMax=HeadingMax
return self
end
function SPAWN:InitGroupHeading(HeadingMin,HeadingMax,unitVar)
self:F({HeadingMin=HeadingMin,HeadingMax=HeadingMax,unitVar=unitVar})
self.SpawnInitGroupHeadingMin=HeadingMin
self.SpawnInitGroupHeadingMax=HeadingMax
self.SpawnInitGroupUnitVar=unitVar
return self
end
function SPAWN:InitCoalition(Coalition)
self:F({coalition=Coalition})
self.SpawnInitCoalition=Coalition
return self
end
function SPAWN:InitCountry(Country)
self.SpawnInitCountry=Country
return self
end
function SPAWN:InitCategory(Category)
self.SpawnInitCategory=Category
return self
end
function SPAWN:InitLivery(Livery)
self.SpawnInitLivery=Livery
return self
end
function SPAWN:InitSkill(Skill)
if Skill:lower()=="average"then
self.SpawnInitSkill="Average"
elseif Skill:lower()=="good"then
self.SpawnInitSkill="Good"
elseif Skill:lower()=="excellent"then
self.SpawnInitSkill="Excellent"
elseif Skill:lower()=="random"then
self.SpawnInitSkill="Random"
else
self.SpawnInitSkill="High"
end
return self
end
function SPAWN:InitSTN(Octal)
self.SpawnInitSTN=Octal or 77777
local num=UTILS.OctalToDecimal(Octal)
if num==nil or num<1 then
self:E("WARNING - STN "..tostring(Octal).." is not valid!")
return self
end
if _DATABASE.STNS[num]~=nil then
self:E("WARNING - STN already assigned: "..tostring(Octal).." is used for ".._DATABASE.STNS[Octal])
end
return self
end
function SPAWN:InitSADL(Octal)
self.SpawnInitSADL=Octal or 7777
local num=UTILS.OctalToDecimal(Octal)
if num==nil or num<1 then
self:E("WARNING - SADL "..tostring(Octal).." is not valid!")
return self
end
if _DATABASE.SADL[num]~=nil then
self:E("WARNING - SADL already assigned: "..tostring(Octal).." is used for ".._DATABASE.SADL[Octal])
end
return self
end
function SPAWN:InitSpeedMps(MPS)
if MPS==nil or tonumber(MPS)<0 then
MPS=125
end
self.InitSpeed=MPS
return self
end
function SPAWN:InitSpeedKnots(Knots)
if Knots==nil or tonumber(Knots)<0 then
Knots=300
end
self.InitSpeed=UTILS.KnotsToMps(Knots)
return self
end
function SPAWN:InitSpeedKph(KPH)
if KPH==nil or tonumber(KPH)<0 then
KPH=UTILS.KnotsToKmph(300)
end
self.InitSpeed=UTILS.KmphToMps(KPH)
return self
end
function SPAWN:InitRadioCommsOnOff(switch)
self.SpawnInitRadio=switch or true
return self
end
function SPAWN:InitRadioFrequency(frequency)
self.SpawnInitFreq=frequency
return self
end
function SPAWN:InitRadioModulation(modulation)
if modulation and modulation:lower()=="fm"then
self.SpawnInitModu=radio.modulation.FM
else
self.SpawnInitModu=radio.modulation.AM
end
return self
end
function SPAWN:InitModex(modex,prefix,postfix)
if modex then
self.SpawnInitModex=tonumber(modex)
end
self.SpawnInitModexPrefix=prefix
self.SpawnInitModexPostfix=postfix
return self
end
function SPAWN:InitRandomizeRoute(SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight)
self.SpawnRandomizeRoute=true
self.SpawnRandomizeRouteStartPoint=SpawnStartPoint
self.SpawnRandomizeRouteEndPoint=SpawnEndPoint
self.SpawnRandomizeRouteRadius=SpawnRadius
self.SpawnRandomizeRouteHeight=SpawnHeight
for GroupID=1,self.SpawnMaxGroups do
self:_RandomizeRoute(GroupID)
end
return self
end
function SPAWN:InitRandomizePosition(RandomizePosition,OuterRadius,InnerRadius)
self.SpawnRandomizePosition=RandomizePosition or false
self.SpawnRandomizePositionOuterRadius=OuterRadius or 0
self.SpawnRandomizePositionInnerRadius=InnerRadius or 0
for GroupID=1,self.SpawnMaxGroups do
self:_RandomizeRoute(GroupID)
end
return self
end
function SPAWN:InitRandomizeUnits(RandomizeUnits,OuterRadius,InnerRadius)
self.SpawnRandomizeUnits=RandomizeUnits or false
self.SpawnOuterRadius=OuterRadius or 0
self.SpawnInnerRadius=InnerRadius or 0
for GroupID=1,self.SpawnMaxGroups do
self:_RandomizeRoute(GroupID)
end
return self
end
function SPAWN:InitSetUnitRelativePositions(Positions)
self.SpawnUnitsWithRelativePositions=true
self.UnitsRelativePositions=Positions
return self
end
function SPAWN:InitSetUnitAbsolutePositions(Positions)
self.SpawnUnitsWithAbsolutePositions=true
self.UnitsAbsolutePositions=Positions
return self
end
function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable)
local temptable={}
for _,_temp in pairs(SpawnTemplatePrefixTable)do
temptable[#temptable+1]=_temp
end
self.SpawnTemplatePrefixTable=UTILS.ShuffleTable(temptable)
self.SpawnRandomizeTemplate=true
for SpawnGroupID=1,self.SpawnMaxGroups do
self:_RandomizeTemplate(SpawnGroupID,RandomizePositionInZone)
end
return self
end
function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone)
local setnames=SpawnTemplateSet:GetSetNames()
self:InitRandomizeTemplate(setnames,RandomizePositionInZone)
return self
end
function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes,RandomizePositionInZone)
local SpawnTemplateSet=SET_GROUP:New():FilterPrefixes(SpawnTemplatePrefixes):FilterOnce()
self:InitRandomizeTemplateSet(SpawnTemplateSet,RandomizePositionInZone)
return self
end
function SPAWN:InitGrouping(Grouping)
self.SpawnGrouping=Grouping
return self
end
function SPAWN:InitRandomizeZones(SpawnZoneTable,RandomizePositionInZone)
local temptable={}
for _,_temp in pairs(SpawnZoneTable)do
temptable[#temptable+1]=_temp
end
self.SpawnZoneTable=UTILS.ShuffleTable(temptable)
self.SpawnRandomizeZones=true
for SpawnGroupID=1,self.SpawnMaxGroups do
self:_RandomizeZones(SpawnGroupID,RandomizePositionInZone)
end
return self
end
function SPAWN:InitRandomizeCallsign()
self.SpawnRandomCallsign=true
return self
end
function SPAWN:InitCallSign(ID,Name,Minor,Major)
local Name=Name or"Enfield"
self.SpawnInitCallSign=true
self.SpawnInitCallSignID=ID or 1
self.SpawnInitCallSignMinor=Minor or 1
self.SpawnInitCallSignMajor=Major or 1
self.SpawnInitCallSignName=string.lower(Name):gsub("^%l",string.upper)
return self
end
function SPAWN:InitPositionCoordinate(Coordinate)
self:InitPositionVec2(Coordinate:GetVec2())
return self
end
function SPAWN:InitPositionVec2(Vec2)
self.SpawnInitPosition=Vec2
self.SpawnFromNewPosition=true
for SpawnGroupID=1,self.SpawnMaxGroups do
self:_SetInitialPosition(SpawnGroupID)
end
return self
end
function SPAWN:InitRepeat()
self.Repeat=true
self.RepeatOnEngineShutDown=false
self.RepeatOnLanding=true
return self
end
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:InitRepeat()
self.RepeatOnEngineShutDown=true
self.RepeatOnLanding=false
return self
end
function SPAWN:InitCleanUp(SpawnCleanUpInterval)
self.SpawnCleanUpInterval=SpawnCleanUpInterval
self.SpawnCleanUpTimeStamps={}
local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup()
self.CleanUpScheduler=SCHEDULER:New(self,self._SpawnCleanUpScheduler,{},1,SpawnCleanUpInterval,0.2)
return self
end
function SPAWN:InitArray(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.SpawnGroups[SpawnGroupID].Visible=true
self.SpawnGroups[SpawnGroupID].Spawned=false
SpawnXIndex=SpawnXIndex+1
if SpawnWidth and SpawnWidth~=0 then
if SpawnXIndex>=SpawnWidth then
SpawnXIndex=0
SpawnYIndex=SpawnYIndex+1
end
end
local SpawnRootX=self.SpawnGroups[SpawnGroupID].SpawnTemplate.x
local SpawnRootY=self.SpawnGroups[SpawnGroupID].SpawnTemplate.y
self:_TranslateRotate(SpawnGroupID,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle)
self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation=true
self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible=true
self.SpawnGroups[SpawnGroupID].Visible=true
self:HandleEvent(EVENTS.Birth,self._OnBirth)
self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.UnitLost,self._OnDeadOrCrash)
if self.Repeat then
self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff)
self:HandleEvent(EVENTS.Land,self._OnLand)
end
if self.RepeatOnEngineShutDown then
self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown)
end
self.SpawnGroups[SpawnGroupID].Group=_DATABASE:Spawn(self.SpawnGroups[SpawnGroupID].SpawnTemplate)
SpawnX=SpawnXIndex*SpawnDeltaX
SpawnY=SpawnYIndex*SpawnDeltaY
end
return self
end
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
return self
end
function SPAWN:InitAIOn()
return self:InitAIOnOff(true)
end
function SPAWN:InitAIOff()
return self:InitAIOnOff(false)
end
end
do
function SPAWN:InitDelayOnOff(DelayOnOff)
self.DelayOnOff=DelayOnOff
return self
end
function SPAWN:InitDelayOn()
return self:InitDelayOnOff(true)
end
function SPAWN:InitDelayOff()
return self:InitDelayOnOff(false)
end
end
function SPAWN:InitHiddenOnMap(OnOff)
self.SpawnHiddenOnMap=OnOff==false and false or true
return self
end
function SPAWN:InitHiddenOnMFD()
self.SpawnHiddenOnMFD=true
return self
end
function SPAWN:InitHiddenOnPlanner()
self.SpawnHiddenOnPlanner=true
return self
end
function SPAWN:Spawn()
if self.SpawnInitAirbase then
return self:SpawnAtAirbase(self.SpawnInitAirbase,self.SpawnInitTakeoff,nil,self.SpawnInitTerminalType)
else
return self:SpawnWithIndex(self.SpawnIndex+1)
end
end
function SPAWN:ReSpawn(SpawnIndex)
if not SpawnIndex then
SpawnIndex=1
end
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
local WayPoints=SpawnGroup and SpawnGroup.WayPoints or nil
if SpawnGroup then
local SpawnDCSGroup=SpawnGroup:GetDCSObject()
if SpawnDCSGroup then
SpawnGroup:Destroy()
end
end
local SpawnGroup=self:SpawnWithIndex(SpawnIndex)
if SpawnGroup and WayPoints then
SpawnGroup:WayPointInitialize(WayPoints)
SpawnGroup:WayPointExecute(1,5)
end
if SpawnGroup and SpawnGroup.ReSpawnFunction then
SpawnGroup:ReSpawnFunction()
end
if SpawnGroup then SpawnGroup:ResetEvents()end
return SpawnGroup
end
function SPAWN:SetSpawnIndex(SpawnIndex)
self.SpawnIndex=SpawnIndex or 0
end
function SPAWN:SpawnWithIndex(SpawnIndex,NoBirth)
if self:_GetSpawnIndex(SpawnIndex)then
if self.SpawnFromNewPosition then
self:_SetInitialPosition(SpawnIndex)
end
if self.SpawnGroups[self.SpawnIndex].Visible then
self.SpawnGroups[self.SpawnIndex].Group:Activate()
else
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
local SpawnZone=self.SpawnGroups[self.SpawnIndex].SpawnZone
if SpawnTemplate then
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
local CurrentY=SpawnTemplate.units[1].y
SpawnTemplate.x=RandomVec2.x
SpawnTemplate.y=RandomVec2.y
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].x=SpawnTemplate.units[UnitID].x+(RandomVec2.x-CurrentX)
SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-CurrentY)
end
end
if self.SpawnRandomizeUnits then
for UnitID=1,#SpawnTemplate.units do
local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius)
if(SpawnZone)then
local inZone=SpawnZone:IsVec2InZone(RandomVec2)
local numTries=1
while(not inZone)and(numTries<20)do
if not inZone then
RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius)
numTries=numTries+1
inZone=SpawnZone:IsVec2InZone(RandomVec2)
end
end
if(not inZone)then
RandomVec2=SpawnZone:GetRandomVec2()
end
end
SpawnTemplate.units[UnitID].x=RandomVec2.x
SpawnTemplate.units[UnitID].y=RandomVec2.y
end
end
local function _Heading(courseDeg)
local h
if courseDeg<=180 then
h=math.rad(courseDeg)
else
h=-math.rad(360-courseDeg)
end
return h
end
local Rad180=math.rad(180)
local function _HeadingRad(courseRad)
if courseRad<=Rad180 then
return courseRad
else
return-((2*Rad180)-courseRad)
end
end
local function _RandomInRange(min,max)
if min and max then
return min+(math.random()*(max-min))
else
return min
end
end
if self.SpawnInitGroupHeadingMin and#SpawnTemplate.units>0 then
local pivotX=SpawnTemplate.units[1].x
local pivotY=SpawnTemplate.units[1].y
local headingRad=math.rad(_RandomInRange(self.SpawnInitGroupHeadingMin or 0,self.SpawnInitGroupHeadingMax))
local cosHeading=math.cos(headingRad)
local sinHeading=math.sin(headingRad)
local unitVarRad=math.rad(self.SpawnInitGroupUnitVar or 0)
for UnitID=1,#SpawnTemplate.units do
if not self.SpawnRandomizeUnits then
if UnitID>1 then
local unitXOff=SpawnTemplate.units[UnitID].x-pivotX
local unitYOff=SpawnTemplate.units[UnitID].y-pivotY
SpawnTemplate.units[UnitID].x=pivotX+(unitXOff*cosHeading)-(unitYOff*sinHeading)
SpawnTemplate.units[UnitID].y=pivotY+(unitYOff*cosHeading)+(unitXOff*sinHeading)
end
end
local unitHeading=SpawnTemplate.units[UnitID].heading+headingRad
SpawnTemplate.units[UnitID].heading=_HeadingRad(_RandomInRange(unitHeading-unitVarRad,unitHeading+unitVarRad))
SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading
end
end
if self.SpawnInitHeadingMin then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].heading=_Heading(_RandomInRange(self.SpawnInitHeadingMin,self.SpawnInitHeadingMax))
SpawnTemplate.units[UnitID].psi=-SpawnTemplate.units[UnitID].heading
end
end
if self.SpawnUnitsWithRelativePositions and self.UnitsRelativePositions then
local BaseX=SpawnTemplate.units[1].x or 0
local BaseY=SpawnTemplate.units[1].y or 0
local BaseZ=SpawnTemplate.units[1].z or 0
for UnitID=1,#SpawnTemplate.units do
if self.UnitsRelativePositions[UnitID].heading then
SpawnTemplate.units[UnitID].heading=math.rad(self.UnitsRelativePositions[UnitID].heading or 0)
end
SpawnTemplate.units[UnitID].x=BaseX+(self.UnitsRelativePositions[UnitID].x or 0)
SpawnTemplate.units[UnitID].y=BaseY+(self.UnitsRelativePositions[UnitID].y or 0)
if self.UnitsRelativePositions[UnitID].z then
SpawnTemplate.units[UnitID].z=BaseZ+(self.UnitsRelativePositions[UnitID].z or 0)
end
end
end
if self.SpawnUnitsWithAbsolutePositions and self.UnitsAbsolutePositions then
for UnitID=1,#SpawnTemplate.units do
if self.UnitsAbsolutePositions[UnitID].heading then
SpawnTemplate.units[UnitID].heading=math.rad(self.UnitsAbsolutePositions[UnitID].heading or 0)
end
SpawnTemplate.units[UnitID].x=self.UnitsAbsolutePositions[UnitID].x or 0
SpawnTemplate.units[UnitID].y=self.UnitsAbsolutePositions[UnitID].y or 0
if self.UnitsAbsolutePositions[UnitID].z then
SpawnTemplate.units[UnitID].z=self.UnitsAbsolutePositions[UnitID].z or 0
end
end
end
if self.SpawnInitLivery then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].livery_id=self.SpawnInitLivery
end
end
if self.SpawnInitSkill then
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].skill=self.SpawnInitSkill
end
end
if self.SpawnInitModex then
for UnitID=1,#SpawnTemplate.units do
local modexnumber=string.format("%03d",self.SpawnInitModex+(UnitID-1))
if self.SpawnInitModexPrefix then modexnumber=self.SpawnInitModexPrefix..modexnumber end
if self.SpawnInitModexPostfix then modexnumber=modexnumber..self.SpawnInitModexPostfix end
SpawnTemplate.units[UnitID].onboard_num=modexnumber
end
end
if self.SpawnInitRadio then
SpawnTemplate.communication=self.SpawnInitRadio
end
if self.SpawnInitFreq then
SpawnTemplate.frequency=self.SpawnInitFreq
end
if self.SpawnInitModu then
SpawnTemplate.modulation=self.SpawnInitModu
end
if self.SpawnHiddenOnPlanner then
SpawnTemplate.hiddenOnPlanner=true
end
if self.SpawnHiddenOnMFD then
SpawnTemplate.hiddenOnMFD=true
end
if self.SpawnHiddenOnMap then
SpawnTemplate.hidden=self.SpawnHiddenOnMap
end
SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID
SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID
SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID
end
if not NoBirth then
self:HandleEvent(EVENTS.Birth,self._OnBirth)
end
self:HandleEvent(EVENTS.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)
self:HandleEvent(EVENTS.Land,self._OnLand)
end
if self.RepeatOnEngineShutDown then
self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown)
end
self.SpawnGroups[self.SpawnIndex].Group=_DATABASE:Spawn(SpawnTemplate)
local SpawnGroup=self.SpawnGroups[self.SpawnIndex].Group
if SpawnGroup then
SpawnGroup:SetAIOnOff(self.AIOnOff)
end
self:T3(SpawnTemplate.name)
if self.SpawnFunctionHook then
self.SpawnHookScheduler:Schedule(nil,self.SpawnFunctionHook,{self.SpawnGroups[self.SpawnIndex].Group,unpack(self.SpawnFunctionArguments)},0.3)
end
end
self.SpawnGroups[self.SpawnIndex].Spawned=true
self.SpawnGroups[self.SpawnIndex].Group.TemplateDonor=self.SpawnTemplatePrefix
return self.SpawnGroups[self.SpawnIndex].Group
else
end
return nil
end
function SPAWN:SpawnScheduled(SpawnTime,SpawnTimeVariation,WithDelay)
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
InitialDelay=math.random(SpawnTime-SpawnTime*SpawnTimeVariation,SpawnTime+SpawnTime*SpawnTimeVariation)
end
self.SpawnScheduler=SCHEDULER:New(self,self._Scheduler,{},InitialDelay,SpawnTime,SpawnTimeVariation)
end
return self
end
function SPAWN:SpawnScheduleStart()
self.SpawnScheduler:Start()
return self
end
function SPAWN:SpawnScheduleStop()
self.SpawnScheduler:Stop()
return self
end
function SPAWN:OnSpawnGroup(SpawnCallBackFunction,...)
self.SpawnFunctionHook=SpawnCallBackFunction
self.SpawnFunctionArguments={}
if arg then
self.SpawnFunctionArguments=arg
end
return self
end
function SPAWN:SpawnAtAirbase(SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType,EmergencyAirSpawn,Parkingdata)
local PointVec3=SpawnAirbase:GetCoordinate()
Takeoff=Takeoff or SPAWN.Takeoff.Hot
if EmergencyAirSpawn==nil then
EmergencyAirSpawn=true
end
if self:_GetSpawnIndex(self.SpawnIndex+1)then
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
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=unit:HasAttribute("Helicopters")
local nunits=#SpawnTemplate.units
local SpawnPoint=SpawnTemplate.route.points[1]
SpawnPoint.linkUnit=nil
SpawnPoint.helipadId=nil
SpawnPoint.airdromeId=nil
local AirbaseID=SpawnAirbase:GetID()
local AirbaseCategory=SpawnAirbase:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.HELIPAD then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
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)
local autoparking=false
if SpawnAirbase.isAirdrome then
autoparking=false
else
autoparking=true
end
local parkingspots={}
local parkingindex={}
local spots
if spawnonground and not SpawnTemplate.parked then
local nfree=0
local termtype=TerminalType
if Takeoff==SPAWN.Takeoff.Runway then
if SpawnAirbase.isShip then
if ishelo then
termtype=AIRBASE.TerminalType.HelicopterUsable
else
termtype=AIRBASE.TerminalType.OpenMedOrBig
end
else
termtype=AIRBASE.TerminalType.Runway
end
end
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
if 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
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,AIRBASE.TerminalType.HelicopterUsable,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
if termtype==nil then
if isbomber or istransport or istanker or isawacs then
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,AIRBASE.TerminalType.OpenBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,AIRBASE.TerminalType.OpenMedOrBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,AIRBASE.TerminalType.FighterAircraft,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(group,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
end
end
local _notenough=false
if autoparking then
if nfree>=1 then
for i=1,nunits do
table.insert(parkingspots,spots[1].Coordinate)
table.insert(parkingindex,spots[1].TerminalID)
end
PointVec3=spots[1].Coordinate
else
_notenough=true
end
else
if nfree>=nunits then
for i=1,nunits do
table.insert(parkingspots,spots[i].Coordinate)
table.insert(parkingindex,spots[i].TerminalID)
end
else
_notenough=true
end
end
if _notenough then
if EmergencyAirSpawn and not self.SpawnUnControlled then
self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
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)
PointVec3.z=PointVec3.z+math.random(-500,500)
if ishelo then
PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000)
else
PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500)
end
Takeoff=GROUP.Takeoff.Air
else
self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
return nil
end
end
else
if TakeoffAltitude then
PointVec3.y=TakeoffAltitude
else
if ishelo then
PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000)
else
PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500)
end
end
end
if not SpawnTemplate.parked then
SpawnTemplate.parked=true
for UnitID=1,nunits do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=PointVec3.x+(SX-BX)
local TY=PointVec3.z+(SY-BY)
if spawnonground then
if autoparking then
SpawnTemplate.units[UnitID].x=PointVec3.x
SpawnTemplate.units[UnitID].y=PointVec3.z
SpawnTemplate.units[UnitID].alt=PointVec3.y
else
SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x
SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z
SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y
end
else
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
SpawnTemplate.units[UnitID].alt=PointVec3.y
end
UnitTemplate.parking=nil
UnitTemplate.parking_id=nil
if parkingindex[UnitID]then
UnitTemplate.parking=parkingindex[UnitID]
end
end
end
SpawnPoint.x=PointVec3.x
SpawnPoint.y=PointVec3.z
SpawnPoint.alt=PointVec3.y
SpawnTemplate.x=PointVec3.x
SpawnTemplate.y=PointVec3.z
SpawnTemplate.uncontrolled=self.SpawnUnControlled
local GroupSpawned=self:SpawnWithIndex(self.SpawnIndex)
if Takeoff==GROUP.Takeoff.Air then
for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do
self:ScheduleOnce(5,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()})
end
end
return GroupSpawned
end
end
return nil
end
function SPAWN:SpawnAtParkingSpot(Airbase,Spots,Takeoff)
if type(Spots)~="table"then
Spots={Spots}
end
if type(Airbase)=="string"then
Airbase=AIRBASE:FindByName(Airbase)
end
local group=GROUP:FindByName(self.SpawnTemplatePrefix)
local nunits=self.SpawnGrouping or#group:GetUnits()
if nunits then
if#Spots<nunits then
self:E("ERROR: Number of provided parking spots is less than number of units in group!")
return nil
end
local Parkingdata={}
for _,TerminalID in pairs(Spots)do
local spot=Airbase:GetParkingSpotData(TerminalID)
if spot and spot.Free then
table.insert(Parkingdata,spot)
end
end
if#Parkingdata>=nunits then
return self:SpawnAtAirbase(Airbase,Takeoff,nil,nil,nil,Parkingdata)
else
self:E("ERROR: Could not find enough free parking spots!")
end
else
self:E("ERROR: Could not get number of units in group!")
end
return nil
end
function SPAWN:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex)
local PointVec3=SpawnAirbase:GetCoordinate()
local Takeoff=SPAWN.Takeoff.Cold
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
if SpawnTemplate then
local GroupAlive=self:GetGroupFromIndex(SpawnIndex)
local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
local TemplateUnit=TemplateGroup:GetUnit(1)
local ishelo=TemplateUnit:HasAttribute("Helicopters")
local isbomber=TemplateUnit:HasAttribute("Bombers")
local istransport=TemplateUnit:HasAttribute("Transports")
local isfighter=TemplateUnit:HasAttribute("Battleplanes")
local nunits=#SpawnTemplate.units
local SpawnPoint=SpawnTemplate.route.points[1]
SpawnPoint.linkUnit=nil
SpawnPoint.helipadId=nil
SpawnPoint.airdromeId=nil
local AirbaseID=SpawnAirbase:GetID()
local AirbaseCategory=SpawnAirbase:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.HELIPAD then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
SpawnPoint.airdromeId=AirbaseID
end
SpawnPoint.alt=0
SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2]
local spawnonground=not(Takeoff==SPAWN.Takeoff.Air)
local spawnonship=false
local spawnonfarp=false
local spawnonrunway=false
local spawnonairport=false
if spawnonground then
if AirbaseCategory==Airbase.Category.SHIP then
spawnonship=true
elseif AirbaseCategory==Airbase.Category.HELIPAD then
spawnonfarp=true
elseif AirbaseCategory==Airbase.Category.AIRDROME then
spawnonairport=true
end
spawnonrunway=Takeoff==SPAWN.Takeoff.Runway
end
local parkingspots={}
local parkingindex={}
local spots
if spawnonground and not SpawnTemplate.parked then
local nfree=0
local termtype=TerminalType
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
if spawnonship or spawnonfarp or spawnonrunway then
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true)
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true)
else
if ishelo then
if termtype==nil then
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterUsable,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
if termtype==nil then
if isbomber or istransport then
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
if nfree<nunits then
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenMedOrBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.FighterAircraft,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
else
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata)
nfree=#spots
end
end
end
local _notenough=false
if spawnonship or spawnonfarp or spawnonrunway then
if nfree>=1 then
for i=1,nunits do
table.insert(parkingspots,spots[1].Coordinate)
table.insert(parkingindex,spots[1].TerminalID)
end
PointVec3=spots[1].Coordinate
else
_notenough=true
end
elseif spawnonairport then
if nfree>=nunits then
for i=1,nunits do
table.insert(parkingspots,spots[i].Coordinate)
table.insert(parkingindex,spots[i].TerminalID)
end
else
_notenough=true
end
end
if _notenough then
if not self.SpawnUnControlled then
else
self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName()))
return nil
end
end
else
end
if not SpawnTemplate.parked then
SpawnTemplate.parked=true
for UnitID=1,nunits do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=PointVec3.x+(SX-BX)
local TY=PointVec3.z+(SY-BY)
if spawnonground then
if spawnonship or spawnonfarp or spawnonrunway then
SpawnTemplate.units[UnitID].x=PointVec3.x
SpawnTemplate.units[UnitID].y=PointVec3.z
SpawnTemplate.units[UnitID].alt=PointVec3.y
else
SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x
SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z
SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y
end
else
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
SpawnTemplate.units[UnitID].alt=PointVec3.y
end
UnitTemplate.parking=nil
UnitTemplate.parking_id=nil
if parkingindex[UnitID]then
UnitTemplate.parking=parkingindex[UnitID]
end
end
end
SpawnPoint.x=PointVec3.x
SpawnPoint.y=PointVec3.z
SpawnPoint.alt=PointVec3.y
SpawnTemplate.x=PointVec3.x
SpawnTemplate.y=PointVec3.z
SpawnTemplate.uncontrolled=true
local GroupSpawned=self:SpawnWithIndex(SpawnIndex,true)
if Takeoff==GROUP.Takeoff.Air then
for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do
SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5)
end
end
if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then
SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0)
end
end
end
function SPAWN:ParkAtAirbase(SpawnAirbase,TerminalType,Parkingdata)
self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,1)
for SpawnIndex=2,self.SpawnMaxGroups do
self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex)
end
self:SetSpawnIndex(0)
return nil
end
function SPAWN:SpawnFromVec3(Vec3,SpawnIndex)
local PointVec3=COORDINATE:NewFromVec3(Vec3)
if SpawnIndex then
else
SpawnIndex=self.SpawnIndex+1
end
if self:_GetSpawnIndex(SpawnIndex)then
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
local TemplateHeight=SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil
SpawnTemplate.route=SpawnTemplate.route or{}
SpawnTemplate.route.points=SpawnTemplate.route.points or{}
SpawnTemplate.route.points[1]=SpawnTemplate.route.points[1]or{}
SpawnTemplate.route.points[1].x=SpawnTemplate.route.points[1].x or 0
SpawnTemplate.route.points[1].y=SpawnTemplate.route.points[1].y or 0
for UnitID=1,#SpawnTemplate.units do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x or 0
local SY=UnitTemplate.y or 0
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=Vec3.x+(SX-BX)
local TY=Vec3.z+(SY-BY)
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
if SpawnTemplate.CategoryID~=Group.Category.SHIP then
SpawnTemplate.units[UnitID].alt=Vec3.y or TemplateHeight
end
end
SpawnTemplate.route.points[1].x=Vec3.x
SpawnTemplate.route.points[1].y=Vec3.z
if SpawnTemplate.CategoryID~=Group.Category.SHIP then
SpawnTemplate.route.points[1].alt=Vec3.y or TemplateHeight
end
SpawnTemplate.x=Vec3.x
SpawnTemplate.y=Vec3.z
SpawnTemplate.alt=Vec3.y or TemplateHeight
return self:SpawnWithIndex(self.SpawnIndex)
end
end
return nil
end
function SPAWN:SpawnFromCoordinate(Coordinate,SpawnIndex)
return self:SpawnFromVec3(Coordinate:GetVec3(),SpawnIndex)
end
function SPAWN:SpawnFromPointVec3(PointVec3,SpawnIndex)
return self:SpawnFromVec3(PointVec3:GetVec3(),SpawnIndex)
end
function SPAWN:SpawnFromVec2(Vec2,MinHeight,MaxHeight,SpawnIndex)
local Height=nil
if MinHeight and MaxHeight then
Height=math.random(MinHeight,MaxHeight)
end
return self:SpawnFromVec3({x=Vec2.x,y=Height,z=Vec2.y},SpawnIndex)
end
function SPAWN:SpawnFromPointVec2(PointVec2,MinHeight,MaxHeight,SpawnIndex)
return self:SpawnFromVec2(PointVec2:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
function SPAWN:SpawnFromUnit(HostUnit,MinHeight,MaxHeight,SpawnIndex)
if HostUnit and HostUnit:IsAlive()~=nil then
return self:SpawnFromVec2(HostUnit:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
return nil
end
function SPAWN:SpawnFromStatic(HostStatic,MinHeight,MaxHeight,SpawnIndex)
if HostStatic and HostStatic:IsAlive()then
return self:SpawnFromVec2(HostStatic:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
return nil
end
function SPAWN:SpawnInZone(Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex)
if Zone then
if RandomizeGroup then
return self:SpawnFromVec2(Zone:GetRandomVec2(),MinHeight,MaxHeight,SpawnIndex)
else
return self:SpawnFromVec2(Zone:GetVec2(),MinHeight,MaxHeight,SpawnIndex)
end
end
return nil
end
function SPAWN:InitUnControlled(UnControlled)
self:F2({self.SpawnTemplatePrefix,UnControlled})
self.SpawnUnControlled=(UnControlled==true)and true or nil
for SpawnGroupID=1,self.SpawnMaxGroups do
self.SpawnGroups[SpawnGroupID].UnControlled=self.SpawnUnControlled
end
return self
end
function SPAWN:GetCoordinate()
local LateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
if LateGroup then
return LateGroup:GetCoordinate()
end
return nil
end
function SPAWN:SpawnGroupName(SpawnIndex)
local SpawnPrefix=self.SpawnTemplatePrefix
if self.SpawnAliasPrefix then
SpawnPrefix=self.SpawnAliasPrefix
end
if SpawnIndex then
local SpawnName=string.format('%s#%03d',SpawnPrefix,SpawnIndex)
return SpawnName
else
return SpawnPrefix
end
end
function SPAWN:GetFirstAliveGroup()
for SpawnIndex=1,self.SpawnCount do
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
if SpawnGroup and SpawnGroup:IsAlive()then
return SpawnGroup,SpawnIndex
end
end
return nil,nil
end
function SPAWN:GetNextAliveGroup(SpawnIndexStart)
SpawnIndexStart=SpawnIndexStart+1
for SpawnIndex=SpawnIndexStart,self.SpawnCount do
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
if SpawnGroup and SpawnGroup:IsAlive()then
return SpawnGroup,SpawnIndex
end
end
return nil,nil
end
function SPAWN:GetLastAliveGroup()
for SpawnIndex=self.SpawnCount,1,-1 do
local SpawnGroup=self:GetGroupFromIndex(SpawnIndex)
if SpawnGroup and SpawnGroup:IsAlive()then
self.SpawnIndex=SpawnIndex
return SpawnGroup
end
end
self.SpawnIndex=nil
return nil
end
function SPAWN:GetGroupFromIndex(SpawnIndex)
if not SpawnIndex then
SpawnIndex=1
end
if self.SpawnGroups and self.SpawnGroups[SpawnIndex]then
local SpawnGroup=self.SpawnGroups[SpawnIndex].Group
return SpawnGroup
else
return nil
end
end
function SPAWN:_GetPrefixFromGroup(SpawnGroup)
local GroupName=SpawnGroup:GetName()
if GroupName then
local SpawnPrefix=self:_GetPrefixFromGroupName(GroupName)
return SpawnPrefix
end
return nil
end
function SPAWN:_GetPrefixFromGroupName(SpawnGroupName)
if SpawnGroupName then
local SpawnPrefix=string.match(SpawnGroupName,".*#")
if SpawnPrefix then
SpawnPrefix=SpawnPrefix:sub(1,-2)
end
return SpawnPrefix
end
return nil
end
function SPAWN:GetSpawnIndexFromGroup(SpawnGroup)
local IndexString=string.match(SpawnGroup:GetName(),"#(%d*)$"):sub(2)
local Index=tonumber(IndexString)
self:T3(IndexString,Index)
return Index
end
function SPAWN:_GetLastIndex()
return self.SpawnMaxGroups
end
function SPAWN:_InitializeSpawnGroups(SpawnIndex)
if not self.SpawnGroups[SpawnIndex]then
self.SpawnGroups[SpawnIndex]={}
self.SpawnGroups[SpawnIndex].Visible=false
self.SpawnGroups[SpawnIndex].Spawned=false
self.SpawnGroups[SpawnIndex].UnControlled=false
self.SpawnGroups[SpawnIndex].SpawnTime=0
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefix
self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex)
end
self:_RandomizeTemplate(SpawnIndex)
self:_RandomizeRoute(SpawnIndex)
return self.SpawnGroups[SpawnIndex]
end
function SPAWN:_GetGroupCategoryID(SpawnPrefix)
local TemplateGroup=Group.getByName(SpawnPrefix)
if TemplateGroup then
return TemplateGroup:getCategory()
else
return nil
end
end
function SPAWN:_GetGroupCoalitionID(SpawnPrefix)
local TemplateGroup=Group.getByName(SpawnPrefix)
if TemplateGroup then
return TemplateGroup:getCoalition()
else
return nil
end
end
function SPAWN:_GetGroupCountryID(SpawnPrefix)
local TemplateGroup=Group.getByName(SpawnPrefix)
if TemplateGroup then
local TemplateUnits=TemplateGroup:getUnits()
return TemplateUnits[1]:getCountry()
else
return nil
end
end
function SPAWN:_GetTemplate(SpawnTemplatePrefix)
local SpawnTemplate=nil
if _DATABASE.Templates.Groups[SpawnTemplatePrefix]==nil then
error('No Template exists for SpawnTemplatePrefix = '..SpawnTemplatePrefix)
end
local Template=_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template
SpawnTemplate=UTILS.DeepCopy(_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template)
if SpawnTemplate==nil then
error('No Template returned for SpawnTemplatePrefix = '..SpawnTemplatePrefix)
end
self:T3({SpawnTemplate})
return SpawnTemplate
end
function SPAWN:_Prepare(SpawnTemplatePrefix,SpawnIndex)
local SpawnTemplate
if self.TweakedTemplate~=nil and self.TweakedTemplate==true then
BASE:I("WARNING: You are using a tweaked template.")
SpawnTemplate=self.SpawnTemplate
if self.MooseNameing==true then
SpawnTemplate.name=self:SpawnGroupName(SpawnIndex)
else
SpawnTemplate.name=self:SpawnGroupName()
end
else
SpawnTemplate=self:_GetTemplate(SpawnTemplatePrefix)
SpawnTemplate.name=self:SpawnGroupName(SpawnIndex)
end
SpawnTemplate.groupId=nil
SpawnTemplate.lateActivation=self.LateActivated or false
if SpawnTemplate.CategoryID==Group.Category.GROUND then
self:T3("For ground units, visible needs to be false...")
SpawnTemplate.visible=false
end
if self.SpawnGrouping then
local UnitAmount=#SpawnTemplate.units
if UnitAmount>self.SpawnGrouping then
for UnitID=self.SpawnGrouping+1,UnitAmount do
SpawnTemplate.units[UnitID]=nil
end
else
if UnitAmount<self.SpawnGrouping then
for UnitID=UnitAmount+1,self.SpawnGrouping do
SpawnTemplate.units[UnitID]=UTILS.DeepCopy(SpawnTemplate.units[1])
SpawnTemplate.units[UnitID].unitId=nil
end
end
end
end
if self.SpawnInitKeepUnitNames==false then
for UnitID=1,#SpawnTemplate.units do
if not string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true)then
SpawnTemplate.units[UnitID].name=string.format(SpawnTemplate.name..'-%02d',UnitID)
end
SpawnTemplate.units[UnitID].unitId=nil
end
else
for UnitID=1,#SpawnTemplate.units do
local SpawnInitKeepUnitIFF=false
if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true)then
SpawnInitKeepUnitIFF=true
end
local UnitPrefix,Rest
if SpawnInitKeepUnitIFF==false then
UnitPrefix,Rest=string.match(SpawnTemplate.units[UnitID].name,"^([^#]+)#?"):gsub("^%s*(.-)%s*$","%1")
SpawnTemplate.units[UnitID].name=string.format('%s#%03d-%02d',UnitPrefix,SpawnIndex,UnitID)
end
SpawnTemplate.units[UnitID].unitId=nil
end
end
if self.SpawnRandomCallsign and SpawnTemplate.units[1].callsign then
if type(SpawnTemplate.units[1].callsign)~="number"then
local min=1
local max=8
local ctable=CALLSIGN.Aircraft
if string.find(SpawnTemplate.units[1].type,"A-10",1,true)then
max=12
end
if string.find(SpawnTemplate.units[1].type,"18",1,true)then
min=9
max=20
ctable=CALLSIGN.F18
end
if string.find(SpawnTemplate.units[1].type,"16",1,true)then
min=9
max=20
ctable=CALLSIGN.F16
end
if SpawnTemplate.units[1].type=="F-15E"then
min=9
max=18
ctable=CALLSIGN.F15E
end
local callsignnr=math.random(min,max)
local callsignname="Enfield"
for name,value in pairs(ctable)do
if value==callsignnr then
callsignname=name
end
end
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].callsign[1]=callsignnr
SpawnTemplate.units[UnitID].callsign[2]=UnitID
SpawnTemplate.units[UnitID].callsign[3]="1"
SpawnTemplate.units[UnitID].callsign["name"]=tostring(callsignname)..tostring(UnitID).."1"
end
else
for UnitID=1,#SpawnTemplate.units do
SpawnTemplate.units[UnitID].callsign=math.random(1,999)
end
end
end
if self.SpawnInitCallSign then
for UnitID=1,#SpawnTemplate.units do
local Callsign=SpawnTemplate.units[UnitID].callsign
if Callsign and type(Callsign)~="number"then
SpawnTemplate.units[UnitID].callsign[1]=self.SpawnInitCallSignID
SpawnTemplate.units[UnitID].callsign[2]=self.SpawnInitCallSignMinor
SpawnTemplate.units[UnitID].callsign[3]=self.SpawnInitCallSignMajor
SpawnTemplate.units[UnitID].callsign["name"]=string.format("%s%d%d",self.SpawnInitCallSignName,self.SpawnInitCallSignMinor,self.SpawnInitCallSignMajor)
end
end
end
for UnitID=1,#SpawnTemplate.units do
local Callsign=SpawnTemplate.units[UnitID].callsign
if Callsign then
if type(Callsign)~="number"and not self.SpawnInitCallSign then
Callsign[2]=((SpawnIndex-1)%10)+1
local CallsignName=SpawnTemplate.units[UnitID].callsign["name"]
CallsignName=string.match(CallsignName,"^(%a+)")
local CallsignLen=CallsignName:len()
SpawnTemplate.units[UnitID].callsign[2]=UnitID
SpawnTemplate.units[UnitID].callsign["name"]=CallsignName:sub(1,CallsignLen)..SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3]
elseif type(Callsign)=="number"then
SpawnTemplate.units[UnitID].callsign=Callsign+SpawnIndex
end
end
if self.InitSpeed then
SpawnTemplate.units[UnitID].speed=self.InitSpeed
end
local AddProps=SpawnTemplate.units[UnitID].AddPropAircraft
if AddProps then
if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then
if self.SpawnInitSTN then
local octal=self.SpawnInitSTN
if UnitID>1 then
octal=_DATABASE:GetNextSTN(self.SpawnInitSTN,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",octal)
else
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16)~=nil then
local octal=SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16
local num=UTILS.OctalToDecimal(octal)
if _DATABASE.STNS[num]~=nil or UnitID>1 then
octal=_DATABASE:GetNextSTN(octal,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",octal)
else
local OSTN=_DATABASE:GetNextSTN(1,SpawnTemplate.units[UnitID].name)
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16=string.format("%05d",OSTN)
end
end
end
if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then
if self.SpawnInitSADL then
local octal=self.SpawnInitSADL
if UnitID>1 then
octal=_DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",octal)
else
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN)~=nil then
local octal=SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN
local num=UTILS.OctalToDecimal(octal)
self.SpawnInitSADL=num
if _DATABASE.SADL[num]~=nil or UnitID>1 then
octal=_DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",octal)
else
local OSTN=_DATABASE:GetNextSADL(1,SpawnTemplate.units[UnitID].name)
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN=string.format("%04d",OSTN)
end
end
end
if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type(Callsign)~="number"then
SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber=SpawnTemplate.units[UnitID].callsign[2]..SpawnTemplate.units[UnitID].callsign[3]
end
if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel and type(Callsign)~="number"then
local CallsignName=SpawnTemplate.units[UnitID].callsign["name"]
CallsignName=string.match(CallsignName,"^(%a+)")
local label="NY"
if not string.find(CallsignName," ")then
label=string.upper(string.match(CallsignName,"^%a")..string.match(CallsignName,"%a$"))
end
SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel=label
end
if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.settings then
SpawnTemplate.units[UnitID].datalinks.Link16.settings.flightLead=UnitID==1 and true or false
end
if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.SADL and SpawnTemplate.units[UnitID].datalinks.SADL.settings then
SpawnTemplate.units[UnitID].datalinks.SADL.settings.flightLead=UnitID==1 and true or false
end
end
end
for UnitID=1,#SpawnTemplate.units do
if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then
local team={}
local isF16=string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true)and true or false
for ID=1,#SpawnTemplate.units do
local member={}
member.missionUnitId=ID
if isF16 then
member.TDOA=true
end
table.insert(team,member)
end
SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers=team
end
end
self:T3({"Template:",SpawnTemplate})
return SpawnTemplate
end
function SPAWN:_RandomizeRoute(SpawnIndex)
if self.SpawnRandomizeRoute then
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
local RouteCount=#SpawnTemplate.route.points
for t=self.SpawnRandomizeRouteStartPoint+1,(RouteCount-self.SpawnRandomizeRouteEndPoint)do
SpawnTemplate.route.points[t].x=SpawnTemplate.route.points[t].x+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius)
SpawnTemplate.route.points[t].y=SpawnTemplate.route.points[t].y+math.random(self.SpawnRandomizeRouteRadius*-1,self.SpawnRandomizeRouteRadius)
if SpawnTemplate.CategoryID==Group.Category.AIRPLANE or SpawnTemplate.CategoryID==Group.Category.HELICOPTER then
if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then
SpawnTemplate.route.points[t].alt=SpawnTemplate.route.points[t].alt+math.random(1,self.SpawnRandomizeRouteHeight)
end
else
SpawnTemplate.route.points[t].alt=nil
end
end
end
self:_RandomizeZones(SpawnIndex)
return self
end
function SPAWN:_RandomizeTemplate(SpawnIndex)
if self.SpawnRandomizeTemplate then
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefixTable[math.random(1,#self.SpawnTemplatePrefixTable)]
self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex)
self.SpawnGroups[SpawnIndex].SpawnTemplate.route=UTILS.DeepCopy(self.SpawnTemplate.route)
self.SpawnGroups[SpawnIndex].SpawnTemplate.x=self.SpawnTemplate.x
self.SpawnGroups[SpawnIndex].SpawnTemplate.y=self.SpawnTemplate.y
self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time=self.SpawnTemplate.start_time
local OldX=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x
local OldY=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y
for UnitID=1,#self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading=self.SpawnTemplate.units[1].heading
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x=self.SpawnTemplate.units[1].x+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x-OldX)
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y=self.SpawnTemplate.units[1].y+(self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y-OldY)
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt=self.SpawnTemplate.units[1].alt
end
end
self:_RandomizeRoute(SpawnIndex)
return self
end
function SPAWN:_SetInitialPosition(SpawnIndex)
if self.SpawnFromNewPosition then
local SpawnVec2=self.SpawnInitPosition
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
SpawnTemplate.route=SpawnTemplate.route or{}
SpawnTemplate.route.points=SpawnTemplate.route.points or{}
SpawnTemplate.route.points[1]=SpawnTemplate.route.points[1]or{}
SpawnTemplate.route.points[1].x=SpawnTemplate.route.points[1].x or 0
SpawnTemplate.route.points[1].y=SpawnTemplate.route.points[1].y or 0
for UnitID=1,#SpawnTemplate.units do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=SpawnVec2.x+(SX-BX)
local TY=SpawnVec2.y+(SY-BY)
UnitTemplate.x=TX
UnitTemplate.y=TY
end
SpawnTemplate.route.points[1].x=SpawnVec2.x
SpawnTemplate.route.points[1].y=SpawnVec2.y
SpawnTemplate.x=SpawnVec2.x
SpawnTemplate.y=SpawnVec2.y
end
return self
end
function SPAWN:_RandomizeZones(SpawnIndex,RandomizePositionInZone)
if self.SpawnRandomizeZones then
local SpawnZone=nil
while not SpawnZone do
local ZoneID=math.random(#self.SpawnZoneTable)
SpawnZone=self.SpawnZoneTable[ZoneID]:GetZoneMaybe()
end
local SpawnVec2=SpawnZone:GetVec2()
if RandomizePositionInZone~=false then
SpawnVec2=SpawnZone:GetRandomVec2()
end
local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate
self.SpawnGroups[SpawnIndex].SpawnZone=SpawnZone
for UnitID=1,#SpawnTemplate.units do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=SpawnVec2.x+(SX-BX)
local TY=SpawnVec2.y+(SY-BY)
UnitTemplate.x=TX
UnitTemplate.y=TY
end
SpawnTemplate.x=SpawnVec2.x
SpawnTemplate.y=SpawnVec2.y
SpawnTemplate.route.points[1].x=SpawnVec2.x
SpawnTemplate.route.points[1].y=SpawnVec2.y
end
return self
end
function SPAWN:_TranslateRotate(SpawnIndex,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle)
local TranslatedX=SpawnX
local TranslatedY=SpawnY
local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle))
local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle))
self.SpawnGroups[SpawnIndex].SpawnTemplate.x=SpawnRootX-RotatedX
self.SpawnGroups[SpawnIndex].SpawnTemplate.y=SpawnRootY+RotatedY
local SpawnUnitCount=table.getn(self.SpawnGroups[SpawnIndex].SpawnTemplate.units)
for u=1,SpawnUnitCount do
local TranslatedX=SpawnX
local TranslatedY=SpawnY-10*(u-1)
local RotatedX=-TranslatedX*math.cos(math.rad(SpawnAngle))+TranslatedY*math.sin(math.rad(SpawnAngle))
local RotatedY=TranslatedX*math.sin(math.rad(SpawnAngle))+TranslatedY*math.cos(math.rad(SpawnAngle))
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x=SpawnRootX-RotatedX
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y=SpawnRootY+RotatedY
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading=self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading+math.rad(SpawnAngle)
end
return self
end
function SPAWN:_GetSpawnIndex(SpawnIndex)
if(self.SpawnMaxGroups==0)or(SpawnIndex<=self.SpawnMaxGroups)then
if(self.SpawnMaxUnitsAlive==0)or(self.AliveUnits+#self.SpawnTemplate.units<=self.SpawnMaxUnitsAlive)or self.UnControlled==true then
if SpawnIndex and SpawnIndex>=self.SpawnCount+1 then
self.SpawnCount=self.SpawnCount+1
SpawnIndex=self.SpawnCount
end
self.SpawnIndex=SpawnIndex
if not self.SpawnGroups[self.SpawnIndex]then
self:_InitializeSpawnGroups(self.SpawnIndex)
end
else
return nil
end
else
return nil
end
return self.SpawnIndex
end
function SPAWN:_OnBirth(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
self.AliveUnits=self.AliveUnits+1
end
end
end
end
function SPAWN:_CountAliveUnits()
local count=0
if self.SpawnAliasPrefix then
if not self.SpawnAliasPrefixEscaped then self.SpawnAliasPrefixEscaped=string.gsub(self.SpawnAliasPrefix,"[%p%s]",".")end
local SpawnAliasPrefix=self.SpawnAliasPrefixEscaped
local agroups=GROUP:FindAllByMatching(SpawnAliasPrefix)
for _,_grp in pairs(agroups)do
local game=self:_GetPrefixFromGroupName(_grp.GroupName)
if game and game==self.SpawnAliasPrefix then
count=count+_grp:CountAliveUnits()
end
end
else
if not self.SpawnTemplatePrefixEscaped then self.SpawnTemplatePrefixEscaped=string.gsub(self.SpawnTemplatePrefix,"[%p%s]",".")end
local SpawnTemplatePrefix=self.SpawnTemplatePrefixEscaped
local groups=GROUP:FindAllByMatching(SpawnTemplatePrefix)
for _,_grp in pairs(groups)do
local game=self:_GetPrefixFromGroupName(_grp.GroupName)
if game and game==self.SpawnTemplatePrefix then
count=count+_grp:CountAliveUnits()
end
end
end
self.AliveUnits=count
return self
end
function SPAWN:_OnDeadOrCrash(EventData)
local unit=UNIT:FindByName(EventData.IniUnitName)
if unit then
local EventPrefix=self:_GetPrefixFromGroupName(unit.GroupName)
if EventPrefix then
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)and self.AliveUnits>0 then
self:ScheduleOnce(1,self._CountAliveUnits,self)
end
end
end
end
function SPAWN:_OnTakeOff(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",false)
end
end
end
end
function SPAWN:_OnLand(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
SpawnGroup:SetState(SpawnGroup,"Spawn_Landed",true)
if self.RepeatOnLanding then
local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup)
SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},self.RepeatOnLandingTime or 3)
end
end
end
end
end
function SPAWN:_OnEngineShutDown(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.SpawnTemplatePrefix or(self.SpawnAliasPrefix and EventPrefix==self.SpawnAliasPrefix)then
local Landed=SpawnGroup:GetState(SpawnGroup,"Spawn_Landed")
if Landed and self.RepeatOnEngineShutDown then
local SpawnGroupIndex=self:GetSpawnIndexFromGroup(SpawnGroup)
SCHEDULER:New(nil,self.ReSpawn,{self,SpawnGroupIndex},3)
end
end
end
end
end
function SPAWN:_Scheduler()
self:F2({"_Scheduler",self.SpawnTemplatePrefix,self.SpawnAliasPrefix,self.SpawnIndex,self.SpawnMaxGroups,self.SpawnMaxUnitsAlive})
self:Spawn()
return true
end
function SPAWN:_SpawnCleanUpScheduler()
local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup()
local IsHelo=false
while SpawnGroup do
IsHelo=SpawnGroup:IsHelicopter()
local SpawnUnits=SpawnGroup:GetUnits()
for UnitID,UnitData in pairs(SpawnUnits)do
local SpawnUnit=UnitData
local SpawnUnitName=SpawnUnit:GetName()
self.SpawnCleanUpTimeStamps[SpawnUnitName]=self.SpawnCleanUpTimeStamps[SpawnUnitName]or{}
local Stamp=self.SpawnCleanUpTimeStamps[SpawnUnitName]
if Stamp.Vec2 then
if(SpawnUnit:InAir()==false and SpawnUnit:GetVelocityKMH()<1)or IsHelo then
local NewVec2=SpawnUnit:GetVec2()or{x=0,y=0}
if(Stamp.Vec2.x==NewVec2.x and Stamp.Vec2.y==NewVec2.y)or(SpawnUnit:GetLife()<=1)then
if Stamp.Time+self.SpawnCleanUpInterval<timer.getTime()then
SCHEDULER:New(nil,self.ReSpawn,{self,SpawnCursor},3)
Stamp.Vec2=nil
Stamp.Time=nil
end
else
Stamp.Time=timer.getTime()
Stamp.Vec2=SpawnUnit:GetVec2()
end
else
Stamp.Vec2=nil
Stamp.Time=nil
end
else
if SpawnUnit:InAir()==false or(IsHelo and SpawnUnit:GetLife()<=1)then
Stamp.Vec2=SpawnUnit:GetVec2()or{x=0,y=0}
if(SpawnUnit:GetVelocityKMH()<1)then
Stamp.Time=timer.getTime()
end
else
Stamp.Time=nil
Stamp.Vec2=nil
end
end
end
SpawnGroup,SpawnCursor=self:GetNextAliveGroup(SpawnCursor)
end
return true
end
SPAWNSTATIC={
ClassName="SPAWNSTATIC",
SpawnIndex=0,
}
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName,SpawnCountryID)
local self=BASE:Inherit(self,BASE:New())
local TemplateStatic,CoalitionID,CategoryID,CountryID=_DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
if TemplateStatic then
self.SpawnTemplatePrefix=SpawnTemplateName
self.TemplateStaticUnit=UTILS.DeepCopy(TemplateStatic.units[1])
self.CountryID=SpawnCountryID or CountryID
self.CategoryID=CategoryID
self.CoalitionID=CoalitionID
self.SpawnIndex=0
else
error("SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '"..tostring(SpawnTemplateName).."'")
end
self:SetEventPriority(5)
return self
end
function SPAWNSTATIC:NewFromTemplate(SpawnTemplate,CountryID)
local self=BASE:Inherit(self,BASE:New())
self.TemplateStaticUnit=UTILS.DeepCopy(SpawnTemplate)
self.SpawnTemplatePrefix=SpawnTemplate.name
self.CountryID=CountryID or country.id.USA
return self
end
function SPAWNSTATIC:NewFromType(StaticType,StaticCategory,CountryID)
local self=BASE:Inherit(self,BASE:New())
self.InitStaticType=StaticType
self.InitStaticCategory=StaticCategory
self.CountryID=CountryID or country.id.USA
self.SpawnTemplatePrefix=self.InitStaticType
self.TemplateStaticUnit={}
self.InitStaticCoordinate=COORDINATE:New(0,0,0)
self.InitStaticHeading=0
return self
end
function SPAWNSTATIC:_InitResourceTable(CombinedWeight)
if not self.TemplateStaticUnit.resourcePayload then
self.TemplateStaticUnit.resourcePayload={
["weapons"]={},
["aircrafts"]={},
["gasoline"]=0,
["diesel"]=0,
["methanol_mixture"]=0,
["jet_fuel"]=0,
}
end
self:InitCargo(true)
self:InitCargoMass(CombinedWeight or 1)
return self
end
function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight)
if not self.TemplateStaticUnit.resourcePayload then
self:_InitResourceTable(CombinedWeight)
end
if Type==STORAGE.Type.LIQUIDS and type(Name)=="string"then
self.TemplateStaticUnit.resourcePayload[Name]=Amount
else
self.TemplateStaticUnit.resourcePayload[Type]={
[Name]={
["amount"]=Amount,
}
}
end
UTILS.PrintTableToLog(self.TemplateStaticUnit)
return self
end
function SPAWNSTATIC:ResetCargoResources()
self.TemplateStaticUnit.resourcePayload=nil
self:_InitResourceTable()
return self
end
function SPAWNSTATIC:InitCoordinate(Coordinate)
self.InitStaticCoordinate=Coordinate
return self
end
function SPAWNSTATIC:InitHeading(Heading)
self.InitStaticHeading=Heading
return self
end
function SPAWNSTATIC:InitLivery(LiveryName)
self.InitStaticLivery=LiveryName
return self
end
function SPAWNSTATIC:InitType(StaticType)
self.InitStaticType=StaticType
return self
end
function SPAWNSTATIC:InitShape(StaticShape)
self.InitStaticShape=StaticShape
return self
end
function SPAWNSTATIC:InitFARP(CallsignID,Frequency,Modulation)
self.InitFarp=true
self.InitFarpCallsignID=CallsignID or 1
self.InitFarpFreq=Frequency or 127.5
self.InitFarpModu=Modulation or 0
return self
end
function SPAWNSTATIC:InitCargoMass(Mass)
self.InitStaticCargoMass=Mass
return self
end
function SPAWNSTATIC:InitCargo(IsCargo)
self.InitStaticCargo=IsCargo
return self
end
function SPAWNSTATIC:InitDead(IsDead)
self.InitStaticDead=IsDead
return self
end
function SPAWNSTATIC:InitCountry(CountryID)
self.CountryID=CountryID
return self
end
function SPAWNSTATIC:InitNamePrefix(NamePrefix)
self.SpawnTemplatePrefix=NamePrefix
return self
end
function SPAWNSTATIC:InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle)
self.InitLinkUnit=Unit
self.InitOffsetX=OffsetX or 0
self.InitOffsetY=OffsetY or 0
self.InitOffsetAngle=OffsetAngle or 0
return self
end
function SPAWNSTATIC:OnSpawnStatic(SpawnCallBackFunction,...)
self:F("OnSpawnStatic")
self.SpawnFunctionHook=SpawnCallBackFunction
self.SpawnFunctionArguments={}
if arg then
self.SpawnFunctionArguments=arg
end
return self
end
function SPAWNSTATIC:Spawn(Heading,NewName)
if Heading then
self.InitStaticHeading=Heading
end
if NewName then
self.InitStaticName=NewName
end
return self:_SpawnStatic(self.TemplateStaticUnit,self.CountryID)
end
function SPAWNSTATIC:SpawnFromPointVec2(PointVec2,Heading,NewName)
local vec2={x=PointVec2:GetX(),y=PointVec2:GetY()}
local Coordinate=COORDINATE:NewFromVec2(vec2)
return self:SpawnFromCoordinate(Coordinate,Heading,NewName)
end
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate,Heading,NewName)
self.InitStaticCoordinate=Coordinate
if Heading then
self.InitStaticHeading=Heading
end
if NewName then
self.InitStaticName=NewName
end
return self:_SpawnStatic(self.TemplateStaticUnit,self.CountryID)
end
function SPAWNSTATIC:SpawnFromZone(Zone,Heading,NewName)
local Static=self:SpawnFromPointVec2(Zone:GetPointVec2(),Heading,NewName)
return Static
end
function SPAWNSTATIC:_SpawnStatic(Template,CountryID)
Template=Template or{}
local CountryID=CountryID or self.CountryID
if self.InitStaticType then
Template.type=self.InitStaticType
end
if self.InitStaticCategory then
Template.category=self.InitStaticCategory
end
if self.InitStaticCoordinate then
Template.x=self.InitStaticCoordinate.x
Template.y=self.InitStaticCoordinate.z
Template.alt=self.InitStaticCoordinate.y
end
if self.InitStaticHeading then
Template.heading=math.rad(self.InitStaticHeading)
end
if self.InitStaticShape then
Template.shape_name=self.InitStaticShape
end
if self.InitStaticLivery then
Template.livery_id=self.InitStaticLivery
end
if self.InitStaticDead~=nil then
Template.dead=self.InitStaticDead
end
if self.InitStaticCargo~=nil then
Template.canCargo=self.InitStaticCargo
end
if self.InitStaticCargoMass~=nil then
Template.mass=self.InitStaticCargoMass
end
if self.InitLinkUnit then
Template.linkUnit=self.InitLinkUnit:GetID()
Template.linkOffset=true
Template.offsets={}
Template.offsets.y=self.InitOffsetY
Template.offsets.x=self.InitOffsetX
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle)or 0
end
if self.InitFarp then
Template.heliport_callsign_id=self.InitFarpCallsignID
Template.heliport_frequency=self.InitFarpFreq
Template.heliport_modulation=self.InitFarpModu
Template.unitId=nil
end
self.SpawnIndex=self.SpawnIndex+1
Template.name=self.InitStaticName or string.format("%s#%05d",self.SpawnTemplatePrefix,self.SpawnIndex)
local Static=nil
if self.InitFarp then
local TemplateGroup={}
TemplateGroup.units={}
TemplateGroup.units[1]=Template
TemplateGroup.visible=true
TemplateGroup.hidden=false
TemplateGroup.x=Template.x
TemplateGroup.y=Template.y
TemplateGroup.name=Template.name
self:T("Spawning FARP")
self:T({Template=Template})
self:T({TemplateGroup=TemplateGroup})
Static=coalition.addGroup(CountryID,-1,TemplateGroup)
local Event={
id=EVENTS.Birth,
time=timer.getTime(),
initiator=Static
}
world.onEvent(Event)
else
self:T("Spawning Static")
self:T2({Template=Template})
Static=coalition.addStaticObject(CountryID,Template)
if Static then
self:T(string.format("Succesfully spawned static object \"%s\" ID=%d",Static:getName(),Static:getID()))
else
self:E(string.format("ERROR: DCS static object \"%s\" is nil!",tostring(Template.name)))
end
end
local mystatic=_DATABASE:AddStatic(Template.name)
if self.SpawnFunctionHook then
self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionArguments))
end
return mystatic
end
TIMER={
ClassName="TIMER",
lid=nil,
}
_TIMERID=0
TIMER.version="0.2.0"
function TIMER:New(Function,...)
local self=BASE:Inherit(self,BASE:New())
self.func=Function
self.para=arg or{}
self.ncalls=0
self.isrunning=false
_TIMERID=_TIMERID+1
self.uid=_TIMERID
self.lid=string.format("TIMER UID=%d | ",self.uid)
return self
end
function TIMER:Start(Tstart,dT,Duration)
local Tnow=timer.getTime()
self.Tstart=Tstart and Tnow+math.max(Tstart,0.001)or Tnow+0.001
self.dT=dT
if Duration then
self.Tstop=self.Tstart+Duration
end
self.tid=timer.scheduleFunction(self._Function,self,self.Tstart)
self.lid=string.format("TIMER UID=%d/%d | ",self.uid,self.tid)
self.isrunning=true
self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s",self.Tstart-Tnow,tostring(self.dT),tostring(self.Tstop)))
return self
end
function TIMER:StartIf(Condition,Tstart,dT,Duration)
if Condition then
self:Start(Tstart,dT,Duration)
end
return self
end
function TIMER:Stop(Delay)
if Delay and Delay>0 then
self.Tstop=timer.getTime()+Delay
else
if self.tid then
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!",self.ncalls))
local status=pcall(
function()
timer.removeFunction(self.tid)
end
)
if status then
self:T2(self.lid..string.format("Stopped timer!"))
else
self:E(self.lid..string.format("WARNING: Could not remove timer function! isrunning=%s",tostring(self.isrunning)))
end
self.isrunning=false
end
end
return self
end
function TIMER:SetMaxFunctionCalls(Nmax)
self.ncallsMax=Nmax
return self
end
function TIMER:SetTimeInterval(dT)
self.dT=dT
return self
end
function TIMER:IsRunning()
return self.isrunning
end
function TIMER:_Function(time)
self.func(unpack(self.para))
self.ncalls=self.ncalls+1
local Tnext=self.dT and time+self.dT or nil
local stopme=false
if Tnext==nil then
self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls",self.ncalls))
stopme=true
elseif self.Tstop and Tnext>self.Tstop then
self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls",Tnext,self.Tstop,self.ncalls))
stopme=true
elseif self.ncallsMax and self.ncalls>=self.ncallsMax then
self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls",self.ncallsMax,self.ncalls))
stopme=true
end
if stopme then
self:Stop()
return nil
else
return Tnext
end
end
do
GOAL={
ClassName="GOAL",
}
GOAL.Players={}
GOAL.TotalContributions=0
function GOAL:New()
local self=BASE:Inherit(self,FSM:New())
self:F({})
self:SetStartState("Pending")
self:AddTransition("*","Achieved","Achieved")
self:SetEventPriority(5)
return self
end
function GOAL:AddPlayerContribution(PlayerName)
self:F({PlayerName})
self.Players[PlayerName]=self.Players[PlayerName]or 0
self.Players[PlayerName]=self.Players[PlayerName]+1
self.TotalContributions=self.TotalContributions+1
end
function GOAL:GetPlayerContribution(PlayerName)
return self.Players[PlayerName]or 0
end
function GOAL:GetPlayerContributions()
return self.Players or{}
end
function GOAL:GetTotalContributions()
return self.TotalContributions or 0
end
function GOAL:IsAchieved()
return self:Is("Achieved")
end
end
do
SPOT={
ClassName="SPOT",
}
function SPOT:New(Recce)
local self=BASE:Inherit(self,FSM:New())
self:F({})
self:SetStartState("Off")
self:AddTransition("Off","LaseOn","On")
self:AddTransition("Off","LaseOnCoordinate","On")
self:AddTransition("On","Lasing","On")
self:AddTransition({"On","Destroyed"},"LaseOff","Off")
self:AddTransition("*","Destroyed","Destroyed")
self.Recce=Recce
self.RecceName=self.Recce:GetName()
self.LaseScheduler=SCHEDULER:New(self)
self:SetEventPriority(5)
self.Lasing=false
return self
end
function SPOT:onafterLaseOn(From,Event,To,Target,LaserCode,Duration)
self:T({From,Event,To})
self:T2({"LaseOn",Target,LaserCode,Duration})
local function StopLase(self)
self:LaseOff()
end
self.Target=Target
self.TargetName=Target:GetName()
self.LaserCode=LaserCode
self.Lasing=true
local RecceDcsUnit=self.Recce:GetDCSObject()
local relativespot=self.relstartpos or{x=0,y=2,z=0}
self.SpotIR=Spot.createInfraRed(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3())
self.SpotLaser=Spot.createLaser(RecceDcsUnit,relativespot,Target:GetPointVec3():AddY(1):GetVec3(),LaserCode)
if Duration then
self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration)
end
self:HandleEvent(EVENTS.Dead)
self:__Lasing(-1)
return self
end
function SPOT:onafterLaseOnCoordinate(From,Event,To,Coordinate,LaserCode,Duration)
self:T2({"LaseOnCoordinate",Coordinate,LaserCode,Duration})
local function StopLase(self)
self:LaseOff()
end
self.Target=nil
self.TargetCoord=Coordinate
self.LaserCode=LaserCode
self.Lasing=true
local RecceDcsUnit=self.Recce:GetDCSObject()
self.SpotIR=Spot.createInfraRed(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3())
self.SpotLaser=Spot.createLaser(RecceDcsUnit,{x=0,y=1,z=0},Coordinate:GetVec3(),LaserCode)
if Duration then
self.ScheduleID=self.LaseScheduler:Schedule(self,StopLase,{self},Duration)
end
self:__Lasing(-1)
return self
end
function SPOT:OnEventDead(EventData)
self:T2({Dead=EventData.IniDCSUnitName,Target=self.Target})
if self.Target then
if EventData.IniDCSUnitName==self.TargetName then
self:F({"Target dead ",self.TargetName})
self:Destroyed()
self:LaseOff()
end
end
if self.Recce then
if EventData.IniDCSUnitName==self.RecceName then
self:F({"Recce dead ",self.RecceName})
self:LaseOff()
end
end
return self
end
function SPOT:onafterLasing(From,Event,To)
self:T({From,Event,To})
if self.Lasing then
if self.Target and self.Target:IsAlive()then
self.SpotIR:setPoint(self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/200):AddX(math.random(-100,100)/200):GetVec3())
self.SpotLaser:setPoint(self.Target:GetPointVec3():AddY(1):GetVec3())
self:__Lasing(0.2)
elseif self.TargetCoord then
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/200,y=self.TargetCoord.y+math.random(-100,100)/200,z=self.TargetCoord.z}
local lsvec3={x=self.TargetCoord.x,y=self.TargetCoord.y,z=self.TargetCoord.z}
self.SpotIR:setPoint(irvec3)
self.SpotLaser:setPoint(lsvec3)
self:__Lasing(0.2)
else
self:F({"Target is not alive",self.Target:IsAlive()})
end
end
return self
end
function SPOT:onafterLaseOff(From,Event,To)
self:T({From,Event,To})
self:T2({"Stopped lasing for ",self.Target and self.Target:GetName()or"coord",SpotIR=self.SportIR,SpotLaser=self.SpotLaser})
self.Lasing=false
self.SpotIR:destroy()
self.SpotLaser:destroy()
self.SpotIR=nil
self.SpotLaser=nil
if self.ScheduleID then
self.LaseScheduler:Stop(self.ScheduleID)
end
self.ScheduleID=nil
self.Target=nil
return self
end
function SPOT:IsLasing()
return self.Lasing
end
function SPOT:SetRelativeStartPosition(position)
self.relstartpos=position or{x=0,y=2,z=0}
return self
end
end
MARKEROPS_BASE={
ClassName="MARKEROPS",
Tag="mytag",
Keywords={},
version="0.1.3",
debug=false,
Casesensitive=true,
}
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
local self=BASE:Inherit(self,FSM:New())
self.lid=string.format("MARKEROPS_BASE %s | ",tostring(self.version))
self.Tag=Tagname or"mytag"
self.Keywords=Keywords or{}
self.debug=false
self.Casesensitive=true
if Casesensitive and Casesensitive==false then
self.Casesensitive=false
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","MarkAdded","*")
self:AddTransition("*","MarkChanged","*")
self:AddTransition("*","MarkDeleted","*")
self:AddTransition("Running","Stop","Stopped")
self:HandleEvent(EVENTS.MarkAdded,self.OnEventMark)
self:HandleEvent(EVENTS.MarkChange,self.OnEventMark)
self:HandleEvent(EVENTS.MarkRemoved,self.OnEventMark)
self:I(self.lid..string.format("started for %s",self.Tag))
self:__Start(1)
return self
end
function MARKEROPS_BASE:OnEventMark(Event)
self:T({Event})
if Event==nil or Event.idx==nil then
self:E("Skipping onEvent. Event or Event.idx unknown.")
return true
end
local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z}
local coord=COORDINATE:NewFromVec3(vec3)
if self.debug then
local coordtext=coord:ToStringLLDDM()
local text=tostring(Event.text)
local m=MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
local coalition=Event.MarkCoalition
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T({event="S_EVENT_MARK_ADDED",carrier=Event.IniGroupName,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
local matchtable=self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end
end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE",carrier=Event.IniGroupName,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
local matchtable=self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
end
end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T({event="S_EVENT_MARK_REMOVED",carrier=Event.IniGroupName,vec3=Event.pos})
local Eventtext=tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext)then
self:MarkDeleted()
end
end
end
end
function MARKEROPS_BASE:_MatchTag(Eventtext)
local matches=false
if not self.Casesensitive then
local type=string.lower(self.Tag)
if string.find(string.lower(Eventtext),type)then
matches=true
end
else
local type=self.Tag
if string.find(Eventtext,type)then
matches=true
end
end
return matches
end
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
local matchtable={}
local keytable=self.Keywords
for _index,_word in pairs(keytable)do
if string.find(string.lower(Eventtext),string.lower(_word))then
table.insert(matchtable,_word)
end
end
return matchtable
end
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
self:T({self.lid,From,Event,To})
end
function MARKEROPS_BASE:onenterStopped(From,Event,To)
self:T({self.lid,From,Event,To})
self:UnHandleEvent(EVENTS.MarkAdded)
self:UnHandleEvent(EVENTS.MarkChange)
self:UnHandleEvent(EVENTS.MarkRemoved)
end
TEXTANDSOUND={
ClassName="TEXTANDSOUND",
version="0.0.1",
lid="",
locale="en",
entries={},
textclass="",
}
function TEXTANDSOUND:New(ClassName,Defaultlocale)
local self=BASE:Inherit(self,BASE:New())
self.lid=string.format("%s (%s) | ",self.ClassName,self.version)
self.locale=Defaultlocale or(_SETTINGS:GetLocale()or"en")
self.textclass=ClassName or"none"
self.entries={}
local initentry={}
initentry.Classname=ClassName
initentry.Data={}
initentry.Locale=self.locale
self.entries[self.locale]=initentry
self:I(self.lid.."Instantiated.")
self:T({self.entries[self.locale]})
return self
end
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
self:T(self.lid.."AddEntry")
local locale=Locale or self.locale
local dataentry={}
dataentry.ID=ID or"1"
dataentry.Text=Text or"none"
dataentry.Soundfile=Soundfile
dataentry.Soundlength=Soundlength or 0
dataentry.Subtitle=Subtitle
if not self.entries[locale]then
local initentry={}
initentry.Classname=self.textclass
initentry.Data={}
initentry.Locale=locale
self.entries[locale]=initentry
end
self.entries[locale].Data[ID]=dataentry
self:T({self.entries[locale].Data})
return self
end
function TEXTANDSOUND:GetEntry(ID,Locale)
self:T(self.lid.."GetEntry")
local locale=Locale or self.locale
if not self.entries[locale]then
locale=self.locale
end
local Text,Soundfile,Soundlength,Subtitle=nil,nil,0,nil
if self.entries[locale]then
if self.entries[locale].Data then
local data=self.entries[locale].Data[ID]
if data then
Text=data.Text
Soundfile=data.Soundfile
Soundlength=data.Soundlength
Subtitle=data.Subtitle
elseif self.entries[self.locale].Data[ID]then
local data=self.entries[self.locale].Data[ID]
Text=data.Text
Soundfile=data.Soundfile
Soundlength=data.Soundlength
Subtitle=data.Subtitle
end
end
else
return nil,nil,0,nil
end
return Text,Soundfile,Soundlength,Subtitle
end
function TEXTANDSOUND:GetDefaultLocale()
self:T(self.lid.."GetDefaultLocale")
return self.locale
end
function TEXTANDSOUND:SetDefaultLocale(locale)
self:T(self.lid.."SetDefaultLocale")
self.locale=locale or"en"
return self
end
function TEXTANDSOUND:HasLocale(Locale)
self:T(self.lid.."HasLocale")
return self.entries[Locale]and true or false
end
function TEXTANDSOUND:FlushToLog()
self:I(self.lid.."Flushing entries:")
local text=string.format("Textclass: %s | Default Locale: %s",self.textclass,self.locale)
for _,_entry in pairs(self.entries)do
local entry=_entry
local text=string.format("Textclassname: %s | Locale: %s",entry.Classname,entry.Locale)
self:I(text)
for _ID,_data in pairs(entry.Data)do
local data=_data
local text=string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID),data.Text or"none",data.Soundfile or"none",data.Soundlength or 0,data.Subtitle or"none")
self:I(text)
end
end
return self
end
PATHLINE={
ClassName="PATHLINE",
lid=nil,
points={},
}
PATHLINE.version="0.1.1"
function PATHLINE:New(Name)
local self=BASE:Inherit(self,BASE:New())
self.name=Name or"Unknown Path"
self.lid=string.format("PATHLINE %s | ",Name)
return self
end
function PATHLINE:NewFromVec2Array(Name,Vec2Array)
local self=PATHLINE:New(Name)
for i=1,#Vec2Array do
self:AddPointFromVec2(Vec2Array[i])
end
return self
end
function PATHLINE:NewFromVec3Array(Name,Vec3Array)
local self=PATHLINE:New(Name)
for i=1,#Vec3Array do
self:AddPointFromVec3(Vec3Array[i])
end
return self
end
function PATHLINE:FindByName(Name)
local pathline=_DATABASE:FindPathline(Name)
return pathline
end
function PATHLINE:AddPointFromVec2(Vec2)
if Vec2 then
local point=self:_CreatePoint(Vec2)
table.insert(self.points,point)
end
return self
end
function PATHLINE:AddPointFromVec3(Vec3)
if Vec3 then
local point=self:_CreatePoint(Vec3)
table.insert(self.points,point)
end
return self
end
function PATHLINE:GetName()
return self.name
end
function PATHLINE:GetNumberOfPoints()
local N=#self.points
return N
end
function PATHLINE:GetPoints()
return self.points
end
function PATHLINE:GetPoints3D()
local vecs={}
for _,_point in pairs(self.points)do
local point=_point
table.insert(vecs,point.vec3)
end
return vecs
end
function PATHLINE:GetPoints2D()
local vecs={}
for _,_point in pairs(self.points)do
local point=_point
table.insert(vecs,point.vec2)
end
return vecs
end
function PATHLINE:GetCoordinates()
local vecs={}
for _,_point in pairs(self.points)do
local point=_point
local coord=COORDINATE:NewFromVec3(point.vec3)
table.insert(vecs,coord)
end
return vecs
end
function PATHLINE:GetPointFromIndex(n)
local N=self:GetNumberOfPoints()
n=n or 1
local point=nil
if n>=1 and n<=N then
point=self.points[n]
else
self:E(self.lid..string.format("ERROR: No point in pathline for N=%s",tostring(n)))
end
return point
end
function PATHLINE:GetPoint3DFromIndex(n)
local point=self:GetPointFromIndex(n)
if point then
return point.vec3
end
return nil
end
function PATHLINE:GetPoint2DFromIndex(n)
local point=self:GetPointFromIndex(n)
if point then
return point.vec2
end
return nil
end
function PATHLINE:MarkPoints(Switch)
for i,_point in pairs(self.points)do
local point=_point
if Switch==false then
if point.markerID then
UTILS.RemoveMark(point.markerID,Delay)
end
else
if point.markerID then
UTILS.RemoveMark(point.markerID)
end
point.markerID=UTILS.GetMarkID()
local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m",self.name,i,point.surfaceType,point.landHeight,point.depth)
trigger.action.markToAll(point.markerID,text,point.vec3,"")
end
end
end
function PATHLINE:_CreatePoint(Vec)
local point={}
if Vec.z then
point.vec3=UTILS.DeepCopy(Vec)
point.vec2={x=Vec.x,y=Vec.z}
else
point.vec2=UTILS.DeepCopy(Vec)
point.vec3={x=Vec.x,y=land.getHeight(Vec),z=Vec.y}
end
point.surfaceType=land.getSurfaceType(point.vec2)
point.landHeight,point.depth=land.getSurfaceHeightWithSeabed(point.vec2)
point.markerID=nil
return point
end
CLIENTMENU={
ClassName="CLIENTMENU",
lid="",
version="0.1.3",
name=nil,
path=nil,
group=nil,
client=nil,
GroupID=nil,
Children={},
Once=false,
Generic=false,
debug=false,
Controller=nil,
groupname=nil,
active=false,
}
CLIENTMENU_ID=0
function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
local self=BASE:Inherit(self,BASE:New())
CLIENTMENU_ID=CLIENTMENU_ID+1
self.ID=CLIENTMENU_ID
if Client then
self.group=Client:GetGroup()
self.client=Client
self.GroupID=self.group:GetID()
self.groupname=self.group:GetName()or"Unknown Groupname"
else
self.Generic=true
end
self.name=Text or"unknown entry"
if Parent then
if Parent:IsInstanceOf("MENU_BASE")then
self.parentpath=Parent.MenuPath
else
self.parentpath=Parent:GetPath()
Parent:AddChild(self)
end
end
self.Parent=Parent
self.Function=Function
self.Functionargs=arg or{}
table.insert(self.Functionargs,self.group)
table.insert(self.Functionargs,self.client)
if self.Functionargs and self.debug then
self:T({"Functionargs",self.Functionargs})
end
if not self.Generic and self.active==false then
if Function~=nil then
local ErrorHandler=function(errmsg)
env.info("MOOSE Error in CLIENTMENU COMMAND function: "..errmsg)
if BASE.Debug~=nil then
env.info(BASE.Debug.traceback())
end
return errmsg
end
self.CallHandler=function()
local function MenuFunction()
return self.Function(unpack(self.Functionargs))
end
local Status,Result=xpcall(MenuFunction,ErrorHandler)
if self.Once==true then
self:Clear()
end
end
self.path=missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath,self.CallHandler)
self.active=true
else
self.path=missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath)
self.active=true
end
else
if self.parentpath then
self.path=UTILS.DeepCopy(self.parentpath)
else
self.path={}
end
self.path[#self.path+1]=Text
end
self.UUID=table.concat(self.path,";")
self:T({self.UUID})
self.Once=false
self.lid=string.format("CLIENTMENU %s | %s | ",self.ID,self.name)
self:T(self.lid.."Created")
return self
end
function CLIENTMENU:CreateUUID(Parent,Text)
local path={}
if Parent and Parent.path then
path=Parent.path
end
path[#path+1]=Text
local UUID=table.concat(path,";")
return UUID
end
function CLIENTMENU:SetController(Controller)
self.Controller=Controller
return self
end
function CLIENTMENU:SetOnce()
self:T(self.lid.."SetOnce")
self.Once=true
return self
end
function CLIENTMENU:RemoveF10()
self:T(self.lid.."RemoveF10")
if self.GroupID then
local function RemoveFunction()
return missionCommands.removeItemForGroup(self.GroupID,self.path)
end
local status,err=pcall(RemoveFunction)
if not status then
self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname))
end
self.active=false
end
return self
end
function CLIENTMENU:GetPath()
self:T(self.lid.."GetPath")
return self.path
end
function CLIENTMENU:GetUUID()
self:T(self.lid.."GetUUID")
return self.UUID
end
function CLIENTMENU:AddChild(Child)
self:T(self.lid.."AddChild "..Child.ID)
table.insert(self.Children,Child.ID,Child)
return self
end
function CLIENTMENU:RemoveChild(Child)
self:T(self.lid.."RemoveChild "..Child.ID)
table.remove(self.Children,Child.ID)
return self
end
function CLIENTMENU:RemoveSubEntries()
self:T(self.lid.."RemoveSubEntries")
self:T({self.Children})
for _id,_entry in pairs(self.Children)do
self:T("Removing ".._id)
if _entry then
_entry:RemoveSubEntries()
_entry:RemoveF10()
if _entry.Parent then
_entry.Parent:RemoveChild(self)
end
end
end
return self
end
function CLIENTMENU:Clear()
self:T(self.lid.."Clear")
for _id,_entry in pairs(self.Children)do
if _entry then
_entry:RemoveSubEntries()
_entry=nil
end
end
self:RemoveF10()
if self.Parent then
self.Parent:RemoveChild(self)
end
return self
end
CLIENTMENUMANAGER={
ClassName="CLIENTMENUMANAGER",
lid="",
version="0.1.6",
name=nil,
clientset=nil,
menutree={},
flattree={},
playertree={},
entrycount=0,
rootentries={},
debug=true,
PlayerMenu={},
Coalition=nil,
}
function CLIENTMENUMANAGER:New(ClientSet,Alias,Coalition)
local self=BASE:Inherit(self,BASE:New())
self.clientset=ClientSet
self.PlayerMenu={}
self.name=Alias or"Nightshift"
self.Coalition=Coalition or coalition.side.BLUE
self.lid=string.format("CLIENTMENUMANAGER %s | %s | ",self.version,self.name)
if self.debug then
self:I(self.lid.."Created")
end
return self
end
function CLIENTMENUMANAGER:_EventHandler(EventData,Retry)
self:T(self.lid.."_EventHandler: "..EventData.id)
if EventData.id==EVENTS.PlayerLeaveUnit or EventData.id==EVENTS.Ejection or EventData.id==EVENTS.Crash or EventData.id==EVENTS.PilotDead then
self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName))
local Client=_DATABASE:FindClient(EventData.IniUnitName)
if Client then
self:ResetMenu(Client)
end
elseif(EventData.id==EVENTS.PlayerEnterAircraft)and EventData.IniCoalition==self.Coalition then
if EventData.IniPlayerName and EventData.IniGroup then
if(not self.clientset:IsIncludeObject(_DATABASE:FindClient(EventData.IniUnitName)))then
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
if not Retry then
self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true)
end
return self
end
local player=_DATABASE:FindClient(EventData.IniUnitName)
self:Propagate(player)
end
elseif EventData.id==EVENTS.PlayerEnterUnit then
local grp=GROUP:FindByName(EventData.IniGroupName)
if grp:IsGround()then
self:T(string.format("Player %s entered GROUND unit %s!",EventData.IniPlayerName,EventData.IniUnitName))
local IsPlayer=EventData.IniDCSUnit:getPlayerName()
if IsPlayer then
local client=_DATABASE.CLIENTS[EventData.IniDCSUnitName]
if not client then
self:I(string.format("Player '%s' joined ground unit '%s' of group '%s'",tostring(EventData.IniPlayerName),tostring(EventData.IniDCSUnitName),tostring(EventData.IniDCSGroupName)))
client=_DATABASE:AddClient(EventData.IniDCSUnitName)
client:AddPlayer(EventData.IniPlayerName)
if not _DATABASE.PLAYERS[EventData.IniPlayerName]then
_DATABASE:AddPlayer(EventData.IniUnitName,EventData.IniPlayerName)
end
local Settings=SETTINGS:Set(EventData.IniPlayerName)
Settings:SetPlayerMenu(EventData.IniUnit)
end
self:Propagate(client)
end
end
end
return self
end
function CLIENTMENUMANAGER:InitAutoPropagation()
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:SetEventPriority(6)
return self
end
function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...)
self:T(self.lid.."NewEntry "..Text or"None")
self.entrycount=self.entrycount+1
local entry=CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg))
if not Parent then
self.rootentries[self.entrycount]=entry
end
local depth=#entry.path
if not self.menutree[depth]then self.menutree[depth]={}end
table.insert(self.menutree[depth],entry.UUID)
self.flattree[entry.UUID]=entry
return entry
end
function CLIENTMENUMANAGER:EntryUUIDExists(UUID)
local exists=self.flattree[UUID]and true or false
return exists
end
function CLIENTMENUMANAGER:FindEntryByUUID(UUID)
self:T(self.lid.."FindEntryByUUID "..UUID or"None")
local entry=nil
for _gid,_entry in pairs(self.flattree)do
local Entry=_entry
if Entry and Entry.UUID==UUID then
entry=Entry
end
end
return entry
end
function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent)
self:T(self.lid.."FindUUIDsByText "..Text or"None")
local matches={}
local entries={}
local n=0
for _uuid,_entry in pairs(self.flattree)do
local Entry=_entry
if Parent then
if Entry and string.find(Entry.name,Text,1,true)and string.find(Entry.UUID,Parent.UUID,1,true)then
table.insert(matches,_uuid)
table.insert(entries,Entry)
n=n+1
end
else
if Entry and string.find(Entry.name,Text,1,true)then
table.insert(matches,_uuid)
table.insert(entries,Entry)
n=n+1
end
end
end
return matches,entries,n
end
function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent)
self:T(self.lid.."FindEntriesByText "..Text or"None")
local matches,objects,number=self:FindUUIDsByText(Text,Parent)
return objects,number
end
function CLIENTMENUMANAGER:FindUUIDsByParent(Parent)
self:T(self.lid.."FindUUIDsByParent")
local matches={}
local entries={}
local n=0
for _uuid,_entry in pairs(self.flattree)do
local Entry=_entry
if Parent then
if Entry and string.find(Entry.UUID,Parent.UUID,1,true)then
table.insert(matches,_uuid)
table.insert(entries,Entry)
n=n+1
end
end
end
return matches,entries,n
end
function CLIENTMENUMANAGER:FindEntriesByParent(Parent)
self:T(self.lid.."FindEntriesByParent")
local matches,objects,number=self:FindUUIDsByParent(Parent)
return objects,number
end
function CLIENTMENUMANAGER:ChangeEntryText(Entry,Text,Client)
self:T(self.lid.."ChangeEntryText "..Text or"None")
local newentry=CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs))
self:DeleteF10Entry(Entry,Client)
self:DeleteGenericEntry(Entry)
if not Entry.Parent then
self.rootentries[self.entrycount]=newentry
end
local depth=#newentry.path
if not self.menutree[depth]then self.menutree[depth]={}end
table.insert(self.menutree[depth],newentry.UUID)
self.flattree[newentry.UUID]=newentry
self:AddEntry(newentry,Client)
return self
end
function CLIENTMENUMANAGER:Propagate(Client)
self:T(self.lid.."Propagate")
local knownunits={}
local Set=self.clientset.Set
if Client then
Set={Client}
end
self:ResetMenu(Client)
for _,_client in pairs(Set)do
local client=_client
if client and client:IsAlive()then
local playerunit=client:GetName()
local playername=client:GetPlayerName()or"none"
if not knownunits[playerunit]then
knownunits[playerunit]=true
else
self:I("Player in multi seat unit: "..playername)
break
end
if not self.playertree[playername]then
self.playertree[playername]={}
end
for level,branch in pairs(self.menutree)do
self:T("Building branch:"..level)
for _,leaf in pairs(branch)do
self:T("Building leaf:"..leaf)
local entry=self:FindEntryByUUID(leaf)
if entry then
self:T("Found generic entry:"..entry.UUID)
local parent=nil
if entry.Parent and entry.Parent.UUID then
parent=self.playertree[playername][entry.Parent.UUID]or self:FindEntryByUUID(entry.Parent.UUID)
end
self.playertree[playername][entry.UUID]=CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
self.playertree[playername][entry.UUID].Once=entry.Once
else
self:T("NO generic entry for:"..leaf)
end
end
end
end
end
return self
end
function CLIENTMENUMANAGER:AddEntry(Entry,Client)
self:T(self.lid.."AddEntry")
local Set=self.clientset.Set
local knownunits={}
if Client then
Set={Client}
end
for _,_client in pairs(Set)do
local client=_client
if client and client:IsAlive()then
local playername=client:GetPlayerName()or"None"
local unitname=client:GetName()
if not knownunits[unitname]then
knownunits[unitname]=true
else
self:I("Player in multi seat unit: "..playername)
break
end
if Entry then
self:T("Adding generic entry:"..Entry.UUID)
local parent=nil
if not self.playertree[playername]then
self.playertree[playername]={}
end
if Entry.Parent and Entry.Parent.UUID then
parent=self.playertree[playername][Entry.Parent.UUID]or self:FindEntryByUUID(Entry.Parent.UUID)
end
self.playertree[playername][Entry.UUID]=CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs))
self.playertree[playername][Entry.UUID].Once=Entry.Once
else
self:T("NO generic entry given")
end
end
end
return self
end
function CLIENTMENUMANAGER:ResetMenu(Client)
self:T(self.lid.."ResetMenu")
for _,_entry in pairs(self.rootentries)do
if _entry then
self:DeleteF10Entry(_entry,Client)
end
end
return self
end
function CLIENTMENUMANAGER:ResetMenuComplete()
self:T(self.lid.."ResetMenuComplete")
for _,_entry in pairs(self.rootentries)do
if _entry then
self:DeleteF10Entry(_entry)
end
end
self.playertree=nil
self.playertree={}
self.rootentries=nil
self.rootentries={}
self.menutree=nil
self.menutree={}
return self
end
function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client)
self:T(self.lid.."DeleteF10Entry")
local Set=self.clientset.Set
if Client then
Set={Client}
end
for _,_client in pairs(Set)do
if _client and _client:IsAlive()then
local playername=_client:GetPlayerName()
if self.playertree[playername]then
local centry=self.playertree[playername][Entry.UUID]
if centry then
centry:Clear()
end
end
end
end
return self
end
function CLIENTMENUMANAGER:DeleteGenericEntry(Entry)
self:T(self.lid.."DeleteGenericEntry")
if Entry.Children and#Entry.Children>0 then
self:RemoveGenericSubEntries(Entry)
end
local depth=#Entry.path
local uuid=Entry.UUID
local tbl=UTILS.DeepCopy(self.menutree)
if tbl[depth]then
for i=depth,#tbl do
for _id,_uuid in pairs(tbl[i])do
self:T(_uuid)
if string.find(_uuid,uuid,1,true)or _uuid==uuid then
self.menutree[i][_id]=nil
self.flattree[_uuid]=nil
end
end
end
end
return self
end
function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry)
self:T(self.lid.."RemoveGenericSubEntries")
local depth=#Entry.path+1
local uuid=Entry.UUID
local tbl=UTILS.DeepCopy(self.menutree)
if tbl[depth]then
for i=depth,#tbl do
self:T("Level = "..i)
for _id,_uuid in pairs(tbl[i])do
self:T(_uuid)
if string.find(_uuid,uuid,1,true)then
self:T("Match for ".._uuid)
self.menutree[i][_id]=nil
self.flattree[_uuid]=nil
end
end
end
end
return self
end
function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client)
self:T(self.lid.."RemoveSubEntries")
local Set=self.clientset.Set
if Client then
Set={Client}
end
for _,_client in pairs(Set)do
if _client and _client:IsAlive()then
local playername=_client:GetPlayerName()
if self.playertree[playername]then
local centry=self.playertree[playername][Entry.UUID]
centry:RemoveSubEntries()
end
end
end
return self
end
OBJECT={
ClassName="OBJECT",
ObjectName="",
}
function OBJECT:New(ObjectName)
local self=BASE:Inherit(self,BASE:New())
self:F2(ObjectName)
self.ObjectName=ObjectName
return self
end
function OBJECT:GetID()
local DCSObject=self:GetDCSObject()
if DCSObject then
local ObjectID=DCSObject:getID()
return ObjectID
end
self:E({"Cannot GetID",Name=self.ObjectName,Class=self:GetClassName()})
return nil
end
function OBJECT:Destroy()
local DCSObject=self:GetDCSObject()
if DCSObject then
DCSObject:destroy(false)
return true
end
self:E({"Cannot Destroy",Name=self.ObjectName,Class=self:GetClassName()})
return nil
end
IDENTIFIABLE={
ClassName="IDENTIFIABLE",
IdentifiableName="",
}
local _CategoryName={
[Unit.Category.AIRPLANE]="Airplane",
[Unit.Category.HELICOPTER]="Helicopter",
[Unit.Category.GROUND_UNIT]="Ground Identifiable",
[Unit.Category.SHIP]="Ship",
[Unit.Category.STRUCTURE]="Structure",
}
function IDENTIFIABLE:New(IdentifiableName)
local self=BASE:Inherit(self,OBJECT:New(IdentifiableName))
self:F2(IdentifiableName)
self.IdentifiableName=IdentifiableName
return self
end
function IDENTIFIABLE:IsAlive()
self:F3(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableIsAlive=DCSIdentifiable:isExist()
return IdentifiableIsAlive
end
return false
end
function IDENTIFIABLE:GetName()
local IdentifiableName=self.IdentifiableName
return IdentifiableName
end
function IDENTIFIABLE:GetTypeName()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableTypeName=DCSIdentifiable:getTypeName()
self:T3(IdentifiableTypeName)
return IdentifiableTypeName
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCategory()
self:F2(self.ObjectName)
local DCSObject=self:GetDCSObject()
if DCSObject then
local ObjectCategory,UnitCategory=DCSObject:getCategory()
self:T3(ObjectCategory)
return ObjectCategory,UnitCategory
end
return nil,nil
end
function IDENTIFIABLE:GetCategoryName()
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCategoryName=_CategoryName[self:GetDesc().category]
return IdentifiableCategoryName
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCoalition()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCoalition=DCSIdentifiable:getCoalition()
self:T3(IdentifiableCoalition)
return IdentifiableCoalition
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCoalitionName()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCoalition=DCSIdentifiable:getCoalition()
self:T3(IdentifiableCoalition)
return UTILS.GetCoalitionName(IdentifiableCoalition)
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCountry()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableCountry=DCSIdentifiable:getCountry()
self:T3(IdentifiableCountry)
return IdentifiableCountry
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCountryName()
self:F2(self.IdentifiableName)
local countryid=self:GetCountry()
for name,id in pairs(country.id)do
if countryid==id then
return name
end
end
end
function IDENTIFIABLE:GetDesc()
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableDesc=DCSIdentifiable:getDesc()
self:T2(IdentifiableDesc)
return IdentifiableDesc
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:HasAttribute(AttributeName)
self:F2(self.IdentifiableName)
local DCSIdentifiable=self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableHasAttribute=DCSIdentifiable:hasAttribute(AttributeName)
self:T2(IdentifiableHasAttribute)
return IdentifiableHasAttribute
end
self:F(self.ClassName.." "..self.IdentifiableName.." not found!")
return nil
end
function IDENTIFIABLE:GetCallsign()
return''
end
function IDENTIFIABLE:GetThreatLevel()
return 0,"Scenery"
end
POSITIONABLE={
ClassName="POSITIONABLE",
PositionableName="",
coordinate=nil,
pointvec3=nil,
}
POSITIONABLE.__={}
POSITIONABLE.__.Cargo={}
function POSITIONABLE:New(PositionableName)
local self=BASE:Inherit(self,IDENTIFIABLE:New(PositionableName))
self.PositionableName=PositionableName
return self
end
function POSITIONABLE:Destroy(GenerateEvent)
self:F2(self.ObjectName)
local DCSObject=self:GetDCSObject()
if DCSObject then
local UnitGroup=self:GetGroup()
local UnitGroupName=UnitGroup:GetName()
self:F({UnitGroupName=UnitGroupName})
if GenerateEvent and GenerateEvent==true then
if self:IsAir()then
self:CreateEventCrash(timer.getTime(),DCSObject)
else
self:CreateEventDead(timer.getTime(),DCSObject)
end
elseif GenerateEvent==false then
else
self:CreateEventRemoveUnit(timer.getTime(),DCSObject)
end
USERFLAG:New(UnitGroupName):Set(100)
DCSObject:destroy()
end
return nil
end
function POSITIONABLE:GetDCSObject()
return nil
end
function POSITIONABLE:GetPosition()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if self:IsInstanceOf("GROUP")then
DCSPositionable=self:GetFirstUnitAlive():GetDCSObject()
end
if DCSPositionable then
local PositionablePosition=DCSPositionable:getPosition()
self:T3(PositionablePosition)
return PositionablePosition
end
BASE:E({"Cannot GetPosition",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetOrientation()
local position=self:GetPosition()
if position then
return position.x,position.y,position.z
else
BASE:E({"Cannot GetOrientation",Positionable=self,Alive=self:IsAlive()})
return nil,nil,nil
end
end
function POSITIONABLE:GetOrientationX()
local position=self:GetPosition()
if position then
return position.x
else
BASE:E({"Cannot GetOrientationX",Positionable=self,Alive=self:IsAlive()})
return nil
end
end
function POSITIONABLE:GetOrientationY()
local position=self:GetPosition()
if position then
return position.y
else
BASE:E({"Cannot GetOrientationY",Positionable=self,Alive=self:IsAlive()})
return nil
end
end
function POSITIONABLE:GetOrientationZ()
local position=self:GetPosition()
if position then
return position.z
else
BASE:E({"Cannot GetOrientationZ",Positionable=self,Alive=self:IsAlive()})
return nil
end
end
function POSITIONABLE:GetPositionVec3()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePosition=DCSPositionable:getPosition().p
self:T3(PositionablePosition)
return PositionablePosition
end
BASE:E({"Cannot GetPositionVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetVec3()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local vec3=DCSPositionable:getPoint()
return vec3
end
self:E({"Cannot get the Positionable DCS Object for GetVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetVec2()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local Vec3=DCSPositionable:getPoint()
return{x=Vec3.x,y=Vec3.z}
end
self:E({"Cannot GetVec2",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetPointVec2()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=DCSPositionable:getPosition().p
local PositionablePointVec2=COORDINATE:NewFromVec3(PositionableVec3)
return PositionablePointVec2
end
self:E({"Cannot Coordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetPointVec3()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetPositionVec3()
if false and self.pointvec3 then
self.pointvec3.x=PositionableVec3.x
self.pointvec3.y=PositionableVec3.y
self.pointvec3.z=PositionableVec3.z
else
self.pointvec3=COORDINATE:NewFromVec3(PositionableVec3)
end
return self.pointvec3
end
BASE:E({"Cannot GetPointVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetCoord()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetVec3()
if self.coordinate then
self.coordinate:UpdateFromVec3(PositionableVec3)
else
self.coordinate=COORDINATE:NewFromVec3(PositionableVec3)
end
return self.coordinate
end
BASE:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetCoordinate()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableVec3=self:GetVec3()
local coord=COORDINATE:NewFromVec3(PositionableVec3)
local heading=self:GetHeading()
coord.Heading=heading
return coord
end
self:E({"Cannot GetCoordinate",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:Explode(power,delay)
power=power or 100
if delay and delay>0 then
self:ScheduleOnce(delay,POSITIONABLE.Explode,self,power,0)
else
local coord=self:GetCoord()
if coord then
coord:Explosion(power)
end
end
return self
end
function POSITIONABLE:GetOffsetCoordinate(x,y,z)
x=x or 0
y=y or 0
z=z or 0
local X=self:GetOrientationX()
local Y=self:GetOrientationY()
local Z=self:GetOrientationZ()
local A={x=x,y=y,z=z}
local x={x=X.x*A.x,y=X.y*A.x,z=X.z*A.x}
local y={x=Y.x*A.y,y=Y.y*A.y,z=Y.z*A.y}
local z={x=Z.x*A.z,y=Z.y*A.z,z=Z.z*A.z}
local a={x=x.x+y.x+z.x,y=x.y+y.y+z.y,z=x.z+y.z+z.z}
local u=self:GetVec3()
local v={x=a.x+u.x,y=a.y+u.y,z=a.z+u.z}
local coord=COORDINATE:NewFromVec3(v)
return coord
end
function POSITIONABLE:GetRelativeCoordinate(x,y,z)
x=x or 0
y=y or 0
z=z or 0
local selfPos=self:GetVec3()
local X=self:GetOrientationX()
local Y=self:GetOrientationY()
local Z=self:GetOrientationZ()
local off={
x=x-selfPos.x,
y=y-selfPos.y,
z=z-selfPos.z
}
local res={x=0,y=0,z=0}
local mat={
{X.x,Y.x,Z.x,off.x},
{X.y,Y.y,Z.y,off.y},
{X.z,Y.z,Z.z,off.z}
}
local m=3
local n=4
local h=1
local k=1
while h<=m and k<=n do
local v_max=math.abs(mat[h][k])
local i_max=h
for i=h,m,1 do
local value=math.abs(mat[i][k])
if value>v_max then
i_max=i
v_max=value
end
end
if mat[i_max][k]==0 then
k=k+1
else
local tmp=mat[h]
mat[h]=mat[i_max]
mat[i_max]=tmp
for i=h+1,m,1 do
local f=mat[i][k]/mat[h][k]
mat[i][k]=0
for j=k+1,n,1 do
mat[i][j]=mat[i][j]-f*mat[h][j]
end
end
h=h+1
k=k+1
end
end
res.z=mat[3][4]/mat[3][3]
res.y=(mat[2][4]-res.z*mat[2][3])/mat[2][2]
res.x=(mat[1][4]-res.y*mat[1][2]-res.z*mat[1][3])/mat[1][1]
local coord=COORDINATE:NewFromVec3(res)
return coord
end
function POSITIONABLE:GetRandomVec3(Radius)
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePointVec3=DCSPositionable:getPosition().p
if Radius then
local PositionableRandomVec3={}
local angle=math.random()*math.pi*2
PositionableRandomVec3.x=PositionablePointVec3.x+math.cos(angle)*math.random()*Radius
PositionableRandomVec3.y=PositionablePointVec3.y
PositionableRandomVec3.z=PositionablePointVec3.z+math.sin(angle)*math.random()*Radius
self:T3(PositionableRandomVec3)
return PositionableRandomVec3
else
self:F("Radius is nil, returning the PointVec3 of the POSITIONABLE",PositionablePointVec3)
return PositionablePointVec3
end
end
BASE:E({"Cannot GetRandomVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetBoundingBox()
self:F2()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionableDesc=DCSPositionable:getDesc()
if PositionableDesc then
local PositionableBox=PositionableDesc.box
return PositionableBox
end
end
BASE:E({"Cannot GetBoundingBox",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetObjectSize()
local box=self:GetBoundingBox()
if box then
local x=box.max.x+math.abs(box.min.x)
local y=box.max.y+math.abs(box.min.y)
local z=box.max.z+math.abs(box.min.z)
return math.max(x,z),x,y,z
end
return 0,0,0,0
end
function POSITIONABLE:GetBoundingRadius(MinDist)
self:F2()
local Box=self:GetBoundingBox()
local boxmin=MinDist or 0
if Box then
local X=Box.max.x-Box.min.x
local Z=Box.max.z-Box.min.z
local CX=X/2
local CZ=Z/2
return math.max(math.max(CX,CZ),boxmin)
end
BASE:T({"Cannot GetBoundingRadius",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetAltitude()
self:F2()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePointVec3=DCSPositionable:getPoint()
return PositionablePointVec3.y
end
BASE:E({"Cannot GetAltitude",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsAboveRunway()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local Vec2=self:GetVec2()
local SurfaceType=land.getSurfaceType(Vec2)
local IsAboveRunway=SurfaceType==land.SurfaceType.RUNWAY
self:T2(IsAboveRunway)
return IsAboveRunway
end
BASE:E({"Cannot IsAboveRunway",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetSize()
local DCSObject=self:GetDCSObject()
if DCSObject then
return 1
else
return 0
end
end
function POSITIONABLE:GetHeading()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local PositionablePosition=DCSPositionable:getPosition()
if PositionablePosition then
local PositionableHeading=math.atan2(PositionablePosition.x.z,PositionablePosition.x.x)
if PositionableHeading<0 then
PositionableHeading=PositionableHeading+2*math.pi
end
PositionableHeading=PositionableHeading*180/math.pi
return PositionableHeading
end
end
self:E({"Cannot GetHeading",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsAir()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
self:T3({UnitDescriptor.category,Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
local IsAirResult=(UnitDescriptor.category==Unit.Category.AIRPLANE)or(UnitDescriptor.category==Unit.Category.HELICOPTER)
self:T3(IsAirResult)
return IsAirResult
end
self:E({"Cannot check IsAir",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsGround()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
self:T3({UnitDescriptor.category,Unit.Category.GROUND_UNIT})
local IsGroundResult=(UnitDescriptor.category==Unit.Category.GROUND_UNIT)
self:T3(IsGroundResult)
return IsGroundResult
end
self:E({"Cannot check IsGround",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsShip()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
self:T3({UnitDescriptor.category,Unit.Category.SHIP})
local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP)
self:T3(IsShipResult)
return IsShipResult
end
self:E({"Cannot check IsShip",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:IsSubmarine()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
if UnitDescriptor.attributes["Submarines"]==true then
return true
else
return false
end
end
self:E({"Cannot check IsSubmarine",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:InAir()
self:F2(self.PositionableName)
return nil
end
function POSITIONABLE:GetVelocity()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local Velocity=VELOCITY:New(self)
return Velocity
end
BASE:E({"Cannot GetVelocity",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetVelocityVec3()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local PositionableVelocityVec3=DCSPositionable:getVelocity()
self:T3(PositionableVelocityVec3)
return PositionableVelocityVec3
end
BASE:E({"Cannot GetVelocityVec3",Positionable=self,Alive=self:IsAlive()})
return nil
end
function POSITIONABLE:GetRelativeVelocity(Positionable)
self:F2(self.PositionableName)
local v1=self:GetVelocityVec3()
local v2=Positionable:GetVelocityVec3()
local vtot=UTILS.VecAdd(v1,v2)
return UTILS.VecNorm(vtot)
end
function POSITIONABLE:GetHeight()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local PositionablePosition=DCSPositionable:getPosition()
if PositionablePosition then
local PositionableHeight=PositionablePosition.p.y
self:T2(PositionableHeight)
return PositionableHeight
end
end
return nil
end
function POSITIONABLE:GetVelocityKMH()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local VelocityVec3=self:GetVelocityVec3()
local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
local Velocity=Velocity*3.6
self:T3(Velocity)
return Velocity
end
return 0
end
function POSITIONABLE:GetVelocityMPS()
self:F2(self.PositionableName)
local DCSPositionable=self:GetDCSObject()
if DCSPositionable and DCSPositionable:isExist()then
local VelocityVec3=self:GetVelocityVec3()
local Velocity=(VelocityVec3.x^2+VelocityVec3.y^2+VelocityVec3.z^2)^0.5
self:T3(Velocity)
return Velocity
end
return 0
end
function POSITIONABLE:GetVelocityKNOTS()
self:F2(self.PositionableName)
return UTILS.MpsToKnots(self:GetVelocityMPS())
end
function POSITIONABLE:GetAirspeedTrue()
local tas=0
local coord=self:GetCoord()
if coord then
local alt=coord.y
local wvec3=coord:GetWindVec3(alt,false)
local vvec3=self:GetVelocityVec3()
local tasvec3=UTILS.VecSubstract(vvec3,wvec3)
tas=UTILS.VecNorm(tasvec3)
end
return tas
end
function POSITIONABLE:GetAirspeedIndicated(oatcorr)
local tas=self:GetAirspeedTrue()
local altitude=self:GetAltitude()
local ias=UTILS.TasToIas(tas,altitude,oatcorr)
return ias
end
function POSITIONABLE:GetGroundSpeed()
local gs=0
local vel=self:GetVelocityVec3()
if vel then
local vec2={x=vel.x,y=vel.z}
gs=UTILS.Vec2Norm(vel)
end
return gs
end
function POSITIONABLE:GetAoA()
local unitpos=self:GetPosition()
if unitpos then
local unitvel=self:GetVelocityVec3()
if unitvel and UTILS.VecNorm(unitvel)~=0 then
local wind=self:GetCoordinate():GetWindWithTurbulenceVec3()
unitvel.x=unitvel.x-wind.x
unitvel.y=unitvel.y-wind.y
unitvel.z=unitvel.z-wind.z
local AxialVel={}
AxialVel.x=UTILS.VecDot(unitpos.x,unitvel)
AxialVel.y=UTILS.VecDot(unitpos.y,unitvel)
AxialVel.z=UTILS.VecDot(unitpos.z,unitvel)
local AoA=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=AxialVel.y,z=0})/UTILS.VecNorm({x=AxialVel.x,y=AxialVel.y,z=0}))
if AxialVel.y>0 then
AoA=-AoA
end
return math.deg(AoA)
end
end
return nil
end
function POSITIONABLE:GetClimbAngle()
local unitpos=self:GetPosition()
if unitpos then
local unitvel=self:GetVelocityVec3()
if unitvel and UTILS.VecNorm(unitvel)~=0 then
local angle=math.asin(unitvel.y/UTILS.VecNorm(unitvel))
return math.deg(angle)
else
return 0
end
end
return nil
end
function POSITIONABLE:GetPitch()
local unitpos=self:GetPosition()
if unitpos then
return math.deg(math.asin(unitpos.x.y))
end
return nil
end
function POSITIONABLE:GetRoll()
local unitpos=self:GetPosition()
if unitpos then
local cp=UTILS.VecCross(unitpos.x,{x=0,y=1,z=0})
local dp=UTILS.VecDot(cp,unitpos.z)
local Roll=math.acos(dp/(UTILS.VecNorm(cp)*UTILS.VecNorm(unitpos.z)))
if unitpos.z.y>0 then
Roll=-Roll
end
return math.deg(Roll)
end
return nil
end
function POSITIONABLE:GetYaw()
local unitpos=self:GetPosition()
if unitpos then
local unitvel=self:GetVelocityVec3()
if unitvel and UTILS.VecNorm(unitvel)~=0 then
local AxialVel={}
AxialVel.x=UTILS.VecDot(unitpos.x,unitvel)
AxialVel.y=UTILS.VecDot(unitpos.y,unitvel)
AxialVel.z=UTILS.VecDot(unitpos.z,unitvel)
local Yaw=math.acos(UTILS.VecDot({x=1,y=0,z=0},{x=AxialVel.x,y=0,z=AxialVel.z})/UTILS.VecNorm({x=AxialVel.x,y=0,z=AxialVel.z}))
if AxialVel.z>0 then
Yaw=-Yaw
end
return Yaw
end
end
return nil
end
function POSITIONABLE:GetMessageText(Message,Name)
local DCSObject=self:GetDCSObject()
if DCSObject then
local Callsign=string.format("%s",((Name~=""and Name)or self:GetCallsign()~=""and self:GetCallsign())or self:GetName())
local MessageText=string.format("%s - %s",Callsign,Message)
return MessageText
end
return nil
end
function POSITIONABLE:GetMessage(Message,Duration,Name)
local DCSObject=self:GetDCSObject()
if DCSObject then
local MessageText=self:GetMessageText(Message,Name)
return MESSAGE:New(MessageText,Duration)
end
return nil
end
function POSITIONABLE:GetMessageType(Message,MessageType,Name)
local DCSObject=self:GetDCSObject()
if DCSObject then
local MessageText=self:GetMessageText(Message,Name)
return MESSAGE:NewType(MessageText,MessageType)
end
return nil
end
function POSITIONABLE:MessageToAll(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToAll()
end
return nil
end
function POSITIONABLE:MessageToCoalition(Message,Duration,MessageCoalition,Name)
self:F2({Message,Duration})
local Name=Name or""
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToCoalition(MessageCoalition)
end
return nil
end
function POSITIONABLE:MessageTypeToCoalition(Message,MessageType,MessageCoalition,Name)
self:F2({Message,MessageType})
local Name=Name or""
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessageType(Message,MessageType,Name):ToCoalition(MessageCoalition)
end
return nil
end
function POSITIONABLE:MessageToRed(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToRed()
end
return nil
end
function POSITIONABLE:MessageToBlue(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToBlue()
end
return nil
end
function POSITIONABLE:MessageToClient(Message,Duration,Client,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToClient(Client)
end
return nil
end
function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
if MessageUnit:IsAlive()then
self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit)
else
BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit})
end
else
BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit})
end
end
end
function POSITIONABLE:MessageToGroup(Message,Duration,MessageGroup,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
if MessageGroup:IsAlive()then
self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup)
else
BASE:E({"Message not sent to Group; Group is not alive...",Message=Message,MessageGroup=MessageGroup})
end
else
BASE:E({
"Message not sent to Group; Positionable is not alive ...",
Message=Message,
Positionable=self,
MessageGroup=MessageGroup
})
end
end
return nil
end
function POSITIONABLE:MessageToUnit(Message,Duration,MessageUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
if MessageUnit:IsAlive()then
self:GetMessage(Message,Duration,Name):ToUnit(MessageUnit)
else
BASE:E({"Message not sent to Unit; Unit is not alive...",Message=Message,MessageUnit=MessageUnit})
end
else
BASE:E({"Message not sent to Unit; Positionable is not alive ...",Message=Message,Positionable=self,MessageUnit=MessageUnit})
end
end
return nil
end
function POSITIONABLE:MessageTypeToGroup(Message,MessageType,MessageGroup,Name)
self:F2({Message,MessageType})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
self:GetMessageType(Message,MessageType,Name):ToGroup(MessageGroup)
end
end
return nil
end
function POSITIONABLE:MessageToSetGroup(Message,Duration,MessageSetGroup,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
MessageSetGroup:ForEachGroupAlive(function(MessageGroup)
self:GetMessage(Message,Duration,Name):ToGroup(MessageGroup)
end)
end
end
return nil
end
function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
MessageSetUnit:ForEachUnit(
function(MessageGroup)
self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup)
end
)
end
end
return nil
end
function POSITIONABLE:MessageToSetUnit(Message,Duration,MessageSetUnit,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
if DCSObject:isExist()then
MessageSetUnit:ForEachUnit(
function(MessageGroup)
self:GetMessage(Message,Duration,Name):ToUnit(MessageGroup)
end
)
end
end
return nil
end
function POSITIONABLE:Message(Message,Duration,Name)
self:F2({Message,Duration})
local DCSObject=self:GetDCSObject()
if DCSObject then
self:GetMessage(Message,Duration,Name):ToGroup(self)
end
return nil
end
function POSITIONABLE:GetRadio()
self:F2(self)
return RADIO:New(self)
end
function POSITIONABLE:GetBeacon()
self:F2(self)
return BEACON:New(self)
end
function POSITIONABLE:LaseUnit(Target,LaserCode,Duration)
self:F2()
LaserCode=LaserCode or math.random(1000,9999)
local RecceDcsUnit=self:GetDCSObject()
local TargetVec3=Target:GetVec3()
self:F("building spot")
self.Spot=SPOT:New(self)
self.Spot:LaseOn(Target,LaserCode,Duration)
self.LaserCode=LaserCode
return self.Spot
end
function POSITIONABLE:LaseCoordinate(Coordinate,LaserCode,Duration)
self:F2()
LaserCode=LaserCode or math.random(1000,9999)
self.Spot=SPOT:New(self)
self.Spot:LaseOnCoordinate(Coordinate,LaserCode,Duration)
self.LaserCode=LaserCode
return self.Spot
end
function POSITIONABLE:LaseOff()
self:F2()
if self.Spot then
self.Spot:LaseOff()
self.Spot=nil
end
return self
end
function POSITIONABLE:IsLasing()
self:F2()
local Lasing=false
if self.Spot then
Lasing=self.Spot:IsLasing()
end
return Lasing
end
function POSITIONABLE:GetSpot()
return self.Spot
end
function POSITIONABLE:GetLaserCode()
return self.LaserCode
end
do
function POSITIONABLE:AddCargo(Cargo)
self.__.Cargo[Cargo]=Cargo
return self
end
function POSITIONABLE:GetCargo()
return self.__.Cargo
end
function POSITIONABLE:RemoveCargo(Cargo)
self.__.Cargo[Cargo]=nil
return self
end
function POSITIONABLE:HasCargo(Cargo)
return self.__.Cargo[Cargo]
end
function POSITIONABLE:ClearCargo()
self.__.Cargo={}
end
function POSITIONABLE:IsCargoEmpty()
local IsEmpty=true
for _,Cargo in pairs(self.__.Cargo)do
IsEmpty=false
break
end
return IsEmpty
end
function POSITIONABLE:CargoItemCount()
local ItemCount=0
for CargoName,Cargo in pairs(self.__.Cargo)do
ItemCount=ItemCount+Cargo:GetCount()
end
return ItemCount
end
function POSITIONABLE:GetTroopCapacity()
local DCSunit=self:GetDCSObject()
local capacity=DCSunit:getDescentCapacity()
return capacity
end
function POSITIONABLE:GetCargoBayFreeWeight()
if not self.__.CargoBayWeightLimit then
self:SetCargoBayWeightLimit()
end
local CargoWeight=0
for CargoName,Cargo in pairs(self.__.Cargo)do
CargoWeight=CargoWeight+Cargo:GetWeight()
end
return self.__.CargoBayWeightLimit-CargoWeight
end
POSITIONABLE.DefaultInfantryWeight=95
POSITIONABLE.CargoBayCapacityValues={
["Air"]={
["C_130"]=70000,
},
["Naval"]={
["Type_071"]=245000,
["LHA_Tarawa"]=500000,
["Ropucha_class"]=150000,
["Dry_cargo_ship_1"]=70000,
["Dry_cargo_ship_2"]=70000,
["Higgins_boat"]=3700,
["USS_Samuel_Chase"]=25000,
["LST_Mk2"]=2100000,
["speedboat"]=500,
["Seawise_Giant"]=261000000,
},
["Ground"]={
["AAV7"]=25*POSITIONABLE.DefaultInfantryWeight,
["Bedford_MWD"]=8*POSITIONABLE.DefaultInfantryWeight,
["Blitz_36_6700A"]=10*POSITIONABLE.DefaultInfantryWeight,
["BMD_1"]=9*POSITIONABLE.DefaultInfantryWeight,
["BMP_1"]=8*POSITIONABLE.DefaultInfantryWeight,
["BMP_2"]=7*POSITIONABLE.DefaultInfantryWeight,
["BMP_3"]=8*POSITIONABLE.DefaultInfantryWeight,
["Boman"]=25*POSITIONABLE.DefaultInfantryWeight,
["BTR_80"]=9*POSITIONABLE.DefaultInfantryWeight,
["BTR_82A"]=9*POSITIONABLE.DefaultInfantryWeight,
["BTR_D"]=12*POSITIONABLE.DefaultInfantryWeight,
["Cobra"]=8*POSITIONABLE.DefaultInfantryWeight,
["Land_Rover_101_FC"]=11*POSITIONABLE.DefaultInfantryWeight,
["Land_Rover_109_S3"]=7*POSITIONABLE.DefaultInfantryWeight,
["LAV_25"]=6*POSITIONABLE.DefaultInfantryWeight,
["M_2_Bradley"]=6*POSITIONABLE.DefaultInfantryWeight,
["M1043_HMMWV_Armament"]=4*POSITIONABLE.DefaultInfantryWeight,
["M1045_HMMWV_TOW"]=4*POSITIONABLE.DefaultInfantryWeight,
["M1126_Stryker_ICV"]=9*POSITIONABLE.DefaultInfantryWeight,
["M1134_Stryker_ATGM"]=9*POSITIONABLE.DefaultInfantryWeight,
["M2A1_halftrack"]=9*POSITIONABLE.DefaultInfantryWeight,
["M_113"]=9*POSITIONABLE.DefaultInfantryWeight,
["Marder"]=6*POSITIONABLE.DefaultInfantryWeight,
["MCV_80"]=9*POSITIONABLE.DefaultInfantryWeight,
["MLRS_FDDM"]=4*POSITIONABLE.DefaultInfantryWeight,
["MTLB"]=25*POSITIONABLE.DefaultInfantryWeight,
["GAZ_66"]=8*POSITIONABLE.DefaultInfantryWeight,
["GAZ_3307"]=12*POSITIONABLE.DefaultInfantryWeight,
["GAZ_3308"]=14*POSITIONABLE.DefaultInfantryWeight,
["Grad_FDDM"]=6*POSITIONABLE.DefaultInfantryWeight,
["KAMAZ_Truck"]=12*POSITIONABLE.DefaultInfantryWeight,
["KrAZ6322"]=12*POSITIONABLE.DefaultInfantryWeight,
["M_818"]=12*POSITIONABLE.DefaultInfantryWeight,
["Tigr_233036"]=6*POSITIONABLE.DefaultInfantryWeight,
["TPZ"]=10*POSITIONABLE.DefaultInfantryWeight,
["UAZ_469"]=4*POSITIONABLE.DefaultInfantryWeight,
["Ural_375"]=12*POSITIONABLE.DefaultInfantryWeight,
["Ural_4320_31"]=14*POSITIONABLE.DefaultInfantryWeight,
["Ural_4320_APA_5D"]=10*POSITIONABLE.DefaultInfantryWeight,
["Ural_4320T"]=14*POSITIONABLE.DefaultInfantryWeight,
["ZBD04A"]=7*POSITIONABLE.DefaultInfantryWeight,
["VAB_Mephisto"]=8*POSITIONABLE.DefaultInfantryWeight,
["tt_KORD"]=6*POSITIONABLE.DefaultInfantryWeight,
["tt_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight,
["HL_KORD"]=6*POSITIONABLE.DefaultInfantryWeight,
["HL_DSHK"]=6*POSITIONABLE.DefaultInfantryWeight,
["CCKW_353"]=16*POSITIONABLE.DefaultInfantryWeight,
["MaxxPro_MRAP"]=7*POSITIONABLE.DefaultInfantryWeight,
}
}
function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit)
if WeightLimit then
self.__.CargoBayWeightLimit=WeightLimit
elseif self.__.CargoBayWeightLimit~=nil then
else
local Desc=self:GetDesc()
self:F({Desc=Desc})
local TypeName=Desc.typeName or"Unknown Type"
TypeName=string.gsub(TypeName,"[%p%s]","_")
if self:IsAir()then
local Weights=POSITIONABLE.CargoBayCapacityValues.Air
local massMax=Desc.massMax or 0
local maxTakeoff=Weights[TypeName]
if maxTakeoff then
massMax=maxTakeoff
end
local massEmpty=Desc.massEmpty or 0
local massFuelMax=Desc.fuelMassMax or 0
local relFuel=math.min(self:GetFuel()or 1.0,1.0)
local massFuel=massFuelMax*relFuel
local CargoWeight=massMax-(massEmpty+massFuel)
self:T(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg",TypeName,CargoWeight,massMax,massEmpty,massFuelMax,relFuel,massFuel))
self.__.CargoBayWeightLimit=CargoWeight
elseif self:IsShip()then
local Weights=POSITIONABLE.CargoBayCapacityValues.Naval
self.__.CargoBayWeightLimit=(Weights[TypeName]or 50000)
else
local Weights=POSITIONABLE.CargoBayCapacityValues.Ground
local CargoBayWeightLimit=(Weights[TypeName]or 0)
self.__.CargoBayWeightLimit=CargoBayWeightLimit
end
end
self:F({CargoBayWeightLimit=self.__.CargoBayWeightLimit})
end
function POSITIONABLE:GetCargoBayWeightLimit()
if self.__.CargoBayWeightLimit==nil then
self:SetCargoBayWeightLimit()
end
return self.__.CargoBayWeightLimit
end
end
function POSITIONABLE:Flare(FlareColor)
self:F2()
trigger.action.signalFlare(self:GetVec3(),FlareColor,0)
end
function POSITIONABLE:FlareWhite()
self:F2()
trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.White,0)
end
function POSITIONABLE:FlareYellow()
self:F2()
trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Yellow,0)
end
function POSITIONABLE:FlareGreen()
self:F2()
trigger.action.signalFlare(self:GetVec3(),trigger.flareColor.Green,0)
end
function POSITIONABLE:FlareRed()
self:F2()
local Vec3=self:GetVec3()
if Vec3 then
trigger.action.signalFlare(Vec3,trigger.flareColor.Red,0)
end
end
function POSITIONABLE:Smoke(SmokeColor,Range,AddHeight)
self:F2()
if Range then
local Vec3=self:GetRandomVec3(Range)
Vec3.y=Vec3.y+AddHeight or 0
trigger.action.smoke(Vec3,SmokeColor)
else
local Vec3=self:GetVec3()
Vec3.y=Vec3.y+AddHeight or 0
trigger.action.smoke(self:GetVec3(),SmokeColor)
end
end
function POSITIONABLE:SmokeGreen()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Green)
end
function POSITIONABLE:SmokeRed()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Red)
end
function POSITIONABLE:SmokeWhite()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.White)
end
function POSITIONABLE:SmokeOrange()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Orange)
end
function POSITIONABLE:SmokeBlue()
self:F2()
trigger.action.smoke(self:GetVec3(),trigger.smokeColor.Blue)
end
function POSITIONABLE:IsInZone(Zone)
self:F2({self.PositionableName,Zone})
if self:IsAlive()then
local IsInZone=Zone:IsVec3InZone(self:GetVec3())
return IsInZone
end
return false
end
function POSITIONABLE:IsNotInZone(Zone)
self:F2({self.PositionableName,Zone})
if self:IsAlive()then
local IsNotInZone=not Zone:IsVec3InZone(self:GetVec3())
return IsNotInZone
else
return false
end
end
CONTROLLABLE={
ClassName="CONTROLLABLE",
ControllableName="",
WayPointFunctions={},
}
function CONTROLLABLE:New(ControllableName)
local self=BASE:Inherit(self,POSITIONABLE:New(ControllableName))
self.ControllableName=ControllableName
self.TaskScheduler=SCHEDULER:New(self)
return self
end
function CONTROLLABLE:_GetController()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local ControllableController=DCSControllable:getController()
return ControllableController
end
return nil
end
function CONTROLLABLE:GetLife()
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local UnitLife=0
local Units=self:GetUnits()
if#Units==1 then
local Unit=Units[1]
UnitLife=Unit:GetLife()
else
local UnitLifeTotal=0
for UnitID,Unit in pairs(Units)do
local Unit=Unit
UnitLifeTotal=UnitLifeTotal+Unit:GetLife()
end
UnitLife=UnitLifeTotal/#Units
end
return UnitLife
end
return nil
end
function CONTROLLABLE:GetLife0()
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local UnitLife=0
local Units=self:GetUnits()
if#Units==1 then
local Unit=Units[1]
UnitLife=Unit:GetLife0()
else
local UnitLifeTotal=0
for UnitID,Unit in pairs(Units)do
local Unit=Unit
UnitLifeTotal=UnitLifeTotal+Unit:GetLife0()
end
UnitLife=UnitLifeTotal/#Units
end
return UnitLife
end
return nil
end
function CONTROLLABLE:GetFuelMin()
self:F(self.ControllableName)
return nil
end
function CONTROLLABLE:GetFuelAve()
self:F(self.ControllableName)
return nil
end
function CONTROLLABLE:GetFuel()
self:F(self.ControllableName)
return nil
end
function CONTROLLABLE:ClearTasks()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:resetTask()
return self
end
return nil
end
function CONTROLLABLE:PopCurrentTask()
self:F2()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:popTask()
return self
end
return nil
end
function CONTROLLABLE:PushTask(DCSTask,WaitTime)
self:F2()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DCSControllableName=self:GetName()
local function PushTask(Controller,DCSTask)
if self and self:IsAlive()then
local Controller=self:_GetController()
Controller:pushTask(DCSTask)
else
BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask})
end
end
if not WaitTime or WaitTime==0 then
PushTask(self,DCSTask)
else
self.TaskScheduler:Schedule(self,PushTask,{DCSTask},WaitTime)
end
return self
end
return nil
end
function CONTROLLABLE:SetTask(DCSTask,WaitTime)
self:F({"SetTask",WaitTime,DCSTask=DCSTask})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DCSControllableName=self:GetName()
self:T2("Controllable Name = "..DCSControllableName)
local function SetTask(Controller,DCSTask)
if self and self:IsAlive()then
local Controller=self:_GetController()
Controller:setTask(DCSTask)
self:T({ControllableName=self:GetName(),DCSTask=DCSTask})
else
BASE:E({DCSControllableName.." is not alive anymore.",DCSTask=DCSTask})
end
end
if not WaitTime or WaitTime==0 then
SetTask(self,DCSTask)
self:T({ControllableName=self:GetName(),DCSTask=DCSTask})
else
self.TaskScheduler:Schedule(self,SetTask,{DCSTask},WaitTime)
end
return self
end
return nil
end
function CONTROLLABLE:HasTask()
local HasTaskResult=false
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
HasTaskResult=Controller:hasTask()
end
return HasTaskResult
end
function CONTROLLABLE:TaskCondition(time,userFlag,userFlagValue,condition,duration,lastWayPoint)
local DCSStopCondition={}
DCSStopCondition.time=time
DCSStopCondition.userFlag=userFlag
DCSStopCondition.userFlagValue=userFlagValue
DCSStopCondition.condition=condition
DCSStopCondition.duration=duration
DCSStopCondition.lastWayPoint=lastWayPoint
return DCSStopCondition
end
function CONTROLLABLE:TaskControlled(DCSTask,DCSStopCondition)
local DCSTaskControlled={
id='ControlledTask',
params={
task=DCSTask,
stopCondition=DCSStopCondition,
},
}
return DCSTaskControlled
end
function CONTROLLABLE:TaskCombo(DCSTasks)
local DCSTaskCombo={
id='ComboTask',
params={
tasks=DCSTasks,
},
}
return DCSTaskCombo
end
function CONTROLLABLE:TaskWrappedAction(DCSCommand,Index)
local DCSTaskWrappedAction={
id="WrappedAction",
enabled=true,
number=Index or 1,
auto=false,
params={
action=DCSCommand,
},
}
return DCSTaskWrappedAction
end
function CONTROLLABLE:TaskEmptyTask()
local DCSTaskWrappedAction={
["id"]="WrappedAction",
["params"]={
["action"]={
["id"]="Script",
["params"]={
["command"]="",
},
},
},
}
return DCSTaskWrappedAction
end
function CONTROLLABLE:SetTaskWaypoint(Waypoint,Task)
Waypoint.task=self:TaskCombo({Task})
self:F({Waypoint.task})
return Waypoint.task
end
function CONTROLLABLE:SetCommand(DCSCommand)
self:F2(DCSCommand)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:setCommand(DCSCommand)
return self
end
return nil
end
function CONTROLLABLE:CommandSwitchWayPoint(FromWayPoint,ToWayPoint)
self:F2({FromWayPoint,ToWayPoint})
local CommandSwitchWayPoint={
id='SwitchWaypoint',
params={
fromWaypointIndex=FromWayPoint,
goToWaypointIndex=ToWayPoint,
},
}
self:T3({CommandSwitchWayPoint})
return CommandSwitchWayPoint
end
function CONTROLLABLE:CommandStopRoute(StopRoute)
self:F2({StopRoute})
local CommandStopRoute={
id='StopRoute',
params={
value=StopRoute,
},
}
self:T3({CommandStopRoute})
return CommandStopRoute
end
function CONTROLLABLE:StartUncontrolled(delay)
if delay and delay>0 then
SCHEDULER:New(nil,CONTROLLABLE.StartUncontrolled,{self},delay)
else
self:SetCommand({id='Start',params={}})
end
return self
end
function CONTROLLABLE:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing,Delay)
AA=AA or self:IsAir()
UnitID=UnitID or self:GetID()
local CommandActivateBeacon={
id="ActivateBeacon",
params={
["type"]=Type,
["system"]=System,
["frequency"]=Frequency,
["unitId"]=UnitID,
["channel"]=Channel,
["modeChannel"]=ModeChannel,
["AA"]=AA,
["callsign"]=Callsign,
["bearing"]=Bearing,
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateBeacon,{self,Type,System,Frequency,UnitID,Channel,ModeChannel,AA,Callsign,Bearing},Delay)
else
self:SetCommand(CommandActivateBeacon)
end
return self
end
function CONTROLLABLE:CommandActivateACLS(UnitID,Name,Delay)
local CommandActivateACLS={
id='ActivateACLS',
params={
unitId=UnitID or self:GetID(),
name=Name or"ACL",
}
}
self:T({CommandActivateACLS})
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateACLS,{self,UnitID,Name},Delay)
else
local controller=self:_GetController()
controller:setCommand(CommandActivateACLS)
end
return self
end
function CONTROLLABLE:CommandDeactivateACLS(Delay)
local CommandDeactivateACLS={
id='DeactivateACLS',
params={}
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateACLS,{self},Delay)
else
local controller=self:_GetController()
controller:setCommand(CommandDeactivateACLS)
end
return self
end
function CONTROLLABLE:CommandActivateICLS(Channel,UnitID,Callsign,Delay)
local CommandActivateICLS={
id="ActivateICLS",
params={
["type"]=BEACON.Type.ICLS,
["channel"]=Channel,
["unitId"]=UnitID or self:GetID(),
["callsign"]=Callsign,
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateICLS,{self,Channel,UnitID,Callsign},Delay)
else
self:SetCommand(CommandActivateICLS)
end
return self
end
function CONTROLLABLE:CommandActivateLink4(Frequency,UnitID,Callsign,Delay)
local freq=Frequency or 336
local CommandActivateLink4={
id="ActivateLink4",
params={
["frequency"]=freq*1000000,
["unitId"]=UnitID or self:GetID(),
["name"]=Callsign or"LNK",
}
}
self:T({CommandActivateLink4})
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandActivateLink4,{self,Frequency,UnitID,Callsign},Delay)
else
local controller=self:_GetController()
controller:setCommand(CommandActivateLink4)
end
return self
end
function CONTROLLABLE:CommandDeactivateBeacon(Delay)
local CommandDeactivateBeacon={id='DeactivateBeacon',params={}}
local CommandDeactivateBeacon={id='DeactivateBeacon',params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateBeacon,{self},Delay)
else
self:SetCommand(CommandDeactivateBeacon)
end
return self
end
function CONTROLLABLE:CommandDeactivateLink4(Delay)
local CommandDeactivateLink4={id='DeactivateLink4',params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateLink4,{self},Delay)
else
local controller=self:_GetController()
controller:setCommand(CommandDeactivateLink4)
end
return self
end
function CONTROLLABLE:CommandDeactivateICLS(Delay)
local CommandDeactivateICLS={id='DeactivateICLS',params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandDeactivateICLS,{self},Delay)
else
self:SetCommand(CommandDeactivateICLS)
end
return self
end
function CONTROLLABLE:CommandSetCallsign(CallName,CallNumber,Delay)
local CommandSetCallsign={id='SetCallsign',params={callname=CallName,number=CallNumber or 1}}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSetCallsign,{self,CallName,CallNumber},Delay)
else
self:SetCommand(CommandSetCallsign)
end
return self
end
function CONTROLLABLE:CommandEPLRS(SwitchOnOff,Delay)
if SwitchOnOff==nil then
SwitchOnOff=true
end
local CommandEPLRS={
id='EPLRS',
params={
value=SwitchOnOff,
groupId=self:GetID(),
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandEPLRS,{self,SwitchOnOff},Delay)
else
self:T(string.format("EPLRS=%s for controllable %s (id=%s)",tostring(SwitchOnOff),tostring(self:GetName()),tostring(self:GetID())))
self:SetCommand(CommandEPLRS)
end
return self
end
function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff,Delay)
local CommandSetFuel={
id='SetUnlimitedFuel',
params={
value=OnOff
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSetUnlimitedFuel,{self,OnOff},Delay)
else
self:SetCommand(CommandSetFuel)
end
return self
end
function CONTROLLABLE:CommandSetFrequency(Frequency,Modulation,Power,Delay)
local CommandSetFrequency={
id='SetFrequency',
params={
frequency=Frequency*1000000,
modulation=Modulation or radio.modulation.AM,
power=Power or 10,
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSetFrequency,{self,Frequency,Modulation,Power},Delay)
else
self:SetCommand(CommandSetFrequency)
end
return self
end
function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay)
local CommandSetFrequencyForUnit={
id='SetFrequencyForUnit',
params={
frequency=Frequency*1000000,
modulation=Modulation or radio.modulation.AM,
unitId=UnitID or self:GetID(),
power=Power or 10,
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID},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
end
local CommandEPLRS={
id='EPLRS',
params={
value=SwitchOnOff,
groupId=self:GetID(),
},
}
return self:TaskWrappedAction(CommandEPLRS,idx or 1)
end
function CONTROLLABLE:TaskAttackGroup(AttackGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack)
local DCSTask={id='AttackGroup',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType or 1073741822,
expend=WeaponExpend or"Auto",
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty or 1,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
groupAttack=GroupAttack and true or false,
},
}
return DCSTask
end
function CONTROLLABLE:TaskAttackUnit(AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
local DCSTask={
id='AttackUnit',
params={
unitId=AttackUnit:GetID(),
groupAttack=GroupAttack and GroupAttack or false,
expend=WeaponExpend or"Auto",
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty,
weaponType=WeaponType or 1073741822,
},
}
return DCSTask
end
function CONTROLLABLE:TaskBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,Divebomb)
local DCSTask={
id='Bombing',
params={
point=Vec2,
x=Vec2.x,
y=Vec2.y,
groupAttack=GroupAttack and GroupAttack or false,
expend=WeaponExpend or"Auto",
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty or 1,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude or 2000,
weaponType=WeaponType or 1073741822,
attackType=Divebomb and"Dive"or nil,
},
}
return DCSTask
end
function CONTROLLABLE:TaskStrafing(Vec2,AttackQty,Length,WeaponType,WeaponExpend,Direction,GroupAttack)
local DCSTask={
id='Strafing',
params={
point=Vec2,
weaponType=WeaponType or 805337088,
expend=WeaponExpend or"Auto",
attackQty=AttackQty or 1,
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,
length=Length,
}
}
return DCSTask
end
function CONTROLLABLE:TaskAttackMapObject(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType)
local DCSTask={
id='AttackMapObject',
params={
point=Vec2,
x=Vec2.x,
y=Vec2.y,
groupAttack=GroupAttack or false,
expend=WeaponExpend or"Auto",
attackQtyLimit=AttackQty and true or false,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
weaponType=WeaponType or 1073741822,
},
}
return DCSTask
end
function CONTROLLABLE:TaskCarpetBombing(Vec2,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType,CarpetLength)
local DCSTask={
id='CarpetBombing',
params={
attackType="Carpet",
x=Vec2.x,
y=Vec2.y,
groupAttack=GroupAttack and GroupAttack or false,
carpetLength=CarpetLength or 500,
weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb,
expend=WeaponExpend or"All",
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty or 1,
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or 0,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
},
}
return DCSTask
end
function CONTROLLABLE:TaskFollowBigFormation(FollowControllable,Vec3,LastWaypointIndex)
local DCSTask={
id='FollowBigFormation',
params={
groupId=FollowControllable:GetID(),
pos=Vec3,
lastWptIndexFlag=LastWaypointIndex and true or false,
lastWptIndex=LastWaypointIndex,
},
}
return DCSTask
end
function CONTROLLABLE:TaskEmbarking(Coordinate,GroupSetForEmbarking,Duration,Distribution)
local g4e={}
if GroupSetForEmbarking then
for _,_group in pairs(GroupSetForEmbarking:GetSet())do
local group=_group
table.insert(g4e,group:GetID())
end
else
self:E("ERROR: No groups for embarking specified!")
return nil
end
local groupID=self and self:GetID()
local DCSTask={
id='Embarking',
params={
selectedTransport=groupID,
x=Coordinate.x,
y=Coordinate.z,
groupsForEmbarking=g4e,
durationFlag=Duration and true or false,
duration=Duration,
distributionFlag=Distribution and true or false,
distribution=Distribution,
},
}
return DCSTask
end
function CONTROLLABLE:TaskEmbarkToTransport(Coordinate,Radius,UnitType)
local EmbarkToTransport={
id="EmbarkToTransport",
params={
x=Coordinate.x,
y=Coordinate.z,
zoneRadius=Radius or 200,
selectedType=UnitType,
},
}
return EmbarkToTransport
end
function CONTROLLABLE:TaskDisembarking(Coordinate,GroupSetToDisembark)
local g4e={}
if GroupSetToDisembark then
for _,_group in pairs(GroupSetToDisembark:GetSet())do
local group=_group
table.insert(g4e,group:GetID())
end
else
self:E("ERROR: No groups for disembarking specified!")
return nil
end
local Disembarking={
id="Disembarking",
params={
x=Coordinate.x,
y=Coordinate.z,
groupsForEmbarking=g4e,
},
}
return Disembarking
end
function CONTROLLABLE:TaskOrbitCircleAtVec2(Point,Altitude,Speed)
self:F2({self.ControllableName,Point,Altitude,Speed})
local DCSTask={
id='Orbit',
params={
pattern=AI.Task.OrbitPattern.CIRCLE,
point=Point,
speed=Speed,
altitude=Altitude+land.getHeight(Point),
},
}
return DCSTask
end
function CONTROLLABLE:TaskOrbit(Coord,Altitude,Speed,CoordRaceTrack)
local Pattern=AI.Task.OrbitPattern.CIRCLE
local P1={x=Coord.x,y=Coord.z or Coord.y}
local P2=nil
if CoordRaceTrack then
Pattern=AI.Task.OrbitPattern.RACE_TRACK
P2={x=CoordRaceTrack.x,y=CoordRaceTrack.z or CoordRaceTrack.y}
end
local Task={
id='Orbit',
params={
pattern=Pattern,
point=P1,
point2=P2,
speed=Speed or UTILS.KnotsToMps(250),
altitude=Altitude or Coord.y,
},
}
return Task
end
function CONTROLLABLE:TaskOrbitCircle(Altitude,Speed,Coordinate)
self:F2({self.ControllableName,Altitude,Speed})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local OrbitVec2=Coordinate and Coordinate:GetVec2()or self:GetVec2()
return self:TaskOrbitCircleAtVec2(OrbitVec2,Altitude,Speed)
end
return nil
end
function CONTROLLABLE:TaskHoldPosition()
self:F2({self.ControllableName})
return self:TaskOrbitCircle(30,10)
end
function CONTROLLABLE:TaskBombingRunway(Airbase,WeaponType,WeaponExpend,AttackQty,Direction,GroupAttack)
local DCSTask={
id='BombingRunway',
params={
runwayId=Airbase:GetID(),
weaponType=WeaponType or ENUMS.WeaponFlag.AnyBomb,
expend=WeaponExpend or AI.Task.WeaponExpend.ALL,
attackQty=AttackQty or 1,
direction=Direction and math.rad(Direction)or 0,
groupAttack=GroupAttack and true or false,
},
}
return DCSTask
end
function CONTROLLABLE:TaskRefueling()
local DCSTask={
id='Refueling',
params={},
}
return DCSTask
end
function CONTROLLABLE:TaskRecoveryTanker(CarrierGroup,Speed,Altitude,LastWptNumber)
local LastWptFlag=type(LastWptNumber)=="number"and true or false
local DCSTask={
id="RecoveryTanker",
params={
groupId=CarrierGroup:GetID(),
speed=Speed,
altitude=Altitude,
lastWptIndexFlag=LastWptFlag,
lastWptIndex=LastWptNumber
}
}
return DCSTask
end
function CONTROLLABLE:TaskLandAtVec2(Vec2,Duration,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,CombatLanding,DirectionAfterLand)
local Point=RandomPoint and Zone:GetRandomVec2()or Zone:GetVec2()
local DCSTask=CONTROLLABLE.TaskLandAtVec2(self,Point,Duration,CombatLanding,DirectionAfterLand)
return DCSTask
end
function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex)
self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex})
local LastWaypointIndexFlag=false
local lastWptIndexFlagChangedManually=false
if LastWaypointIndex then
LastWaypointIndexFlag=true
lastWptIndexFlagChangedManually=true
end
local DCSTask={
id='Follow',
params={
groupId=FollowControllable:GetID(),
pos=Vec3,
lastWptIndexFlag=LastWaypointIndexFlag,
lastWptIndex=LastWaypointIndex,
lastWptIndexFlagChangedManually=lastWptIndexFlagChangedManually,
},
}
self:T3({DCSTask})
return DCSTask
end
function CONTROLLABLE:TaskGroundEscort(FollowControllable,LastWaypointIndex,OrbitDistance,TargetTypes)
local DCSTask={
id='GroundEscort',
params={
groupId=FollowControllable and FollowControllable:GetID()or nil,
engagementDistMax=OrbitDistance or 2000,
lastWptIndexFlag=LastWaypointIndex and true or false,
lastWptIndex=LastWaypointIndex,
targetTypes=TargetTypes or{"Ground vehicles"},
lastWptIndexFlagChangedManually=true,
},
}
return DCSTask
end
function CONTROLLABLE:TaskEscort(FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes)
local DCSTask={
id='Escort',
params={
groupId=FollowControllable and FollowControllable:GetID()or nil,
pos=Vec3,
lastWptIndexFlag=LastWaypointIndex and true or false,
lastWptIndex=LastWaypointIndex,
engagementDistMax=EngagementDistance,
targetTypes=TargetTypes or{"Air"},
},
}
return DCSTask
end
function CONTROLLABLE:TaskFireAtPoint(Vec2,Radius,AmmoCount,WeaponType,Altitude,ASL)
local DCSTask={
id='FireAtPoint',
params={
point=Vec2,
x=Vec2.x,
y=Vec2.y,
zoneRadius=Radius,
radius=Radius,
expendQty=1,
expendQtyEnabled=false,
alt_type=ASL and 0 or 1,
},
}
if AmmoCount then
DCSTask.params.expendQty=AmmoCount
DCSTask.params.expendQtyEnabled=true
end
if Altitude then
DCSTask.params.altitude=Altitude
end
if WeaponType then
DCSTask.params.weaponType=WeaponType
end
return DCSTask
end
function CONTROLLABLE:TaskHold()
local DCSTask={id='Hold',params={}}
return DCSTask
end
function CONTROLLABLE:TaskFAC_AttackGroup(AttackGroup,WeaponType,Designation,Datalink,Frequency,Modulation,CallsignName,CallsignNumber)
local DCSTask={
id='FAC_AttackGroup',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType or ENUMS.WeaponFlag.AutoDCS,
designation=Designation or"Auto",
datalink=Datalink and Datalink or true,
frequency=(Frequency or 133)*1000000,
modulation=Modulation or radio.modulation.AM,
callname=CallsignName,
number=CallsignNumber,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageTargets(Distance,TargetTypes,Priority)
local DCSTask={
id='EngageTargets',
params={
maxDistEnabled=Distance and true or false,
maxDist=Distance,
targetTypes=TargetTypes or{"Air"},
priority=Priority or 0,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageTargetsInZone(Vec2,Radius,TargetTypes,Priority)
local DCSTask={
id='EngageTargetsInZone',
params={
point=Vec2,
zoneRadius=Radius,
targetTypes=TargetTypes or{"Air"},
priority=Priority or 0
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes,Priority)
local DCSTask={
id='EngageTargets',
key="AntiShip",
params={
targetTypes=TargetTypes or{"Ships"},
priority=Priority or 0
}
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes,Priority)
local DCSTask={
id='EngageTargets',
key="SEAD",
params={
targetTypes=TargetTypes or{"Air Defence"},
priority=Priority or 0
}
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskCAP(TargetTypes,Priority)
local DCSTask={
id='EngageTargets',
key="CAP",
enabled=true,
params={
targetTypes=TargetTypes or{"Air"},
priority=Priority or 0
}
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageGroup(AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit)
local DCSTask={
id='EngageGroup',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType,
expend=WeaponExpend or"Auto",
directionEnabled=Direction and true or false,
direction=Direction,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty,
priority=Priority or 1,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEngageUnit(EngageUnit,Priority,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,Visible,ControllableAttack)
local DCSTask={
id='EngageUnit',
params={
unitId=EngageUnit:GetID(),
priority=Priority or 1,
groupAttack=GroupAttack and GroupAttack or false,
visible=Visible and Visible or false,
expend=WeaponExpend or"Auto",
directionEnabled=Direction and true or false,
direction=Direction and math.rad(Direction)or nil,
altitudeEnabled=Altitude and true or false,
altitude=Altitude,
attackQtyLimit=AttackQty and true or false,
attackQty=AttackQty,
controllableAttack=ControllableAttack,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskAWACS()
local DCSTask={
id='AWACS',
params={},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskTanker()
local DCSTask={
id='Tanker',
params={},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskEWR()
local DCSTask={
id='EWR',
params={},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskFAC_EngageGroup(AttackGroup,Priority,WeaponType,Designation,Datalink,Frequency,Modulation,CallsignID,CallsignNumber)
local DCSTask={
id='FAC_EngageGroup',
params={
groupId=AttackGroup:GetID(),
weaponType=WeaponType or"Auto",
designation=Designation,
datalink=Datalink and Datalink or false,
frequency=(Frequency or 133)*1000000,
modulation=Modulation or radio.modulation.AM,
callname=CallsignID,
number=CallsignNumber,
priority=Priority or 0,
},
}
return DCSTask
end
function CONTROLLABLE:EnRouteTaskFAC(Frequency,Modulation,CallsignID,CallsignNumber,Priority)
local DCSTask={
id='FAC',
params={
frequency=(Frequency or 133)*1000000,
modulation=Modulation or radio.modulation.AM,
callname=CallsignID,
number=CallsignNumber,
priority=Priority or 0
}
}
return DCSTask
end
function CONTROLLABLE:TaskFunction(FunctionString,...)
local DCSScript={}
DCSScript[#DCSScript+1]="local MissionControllable = GROUP:Find( ... ) "
if arg and arg.n>0 then
local ArgumentKey='_'..tostring(arg):match("table: (.*)")
self:SetState(self,ArgumentKey,arg)
DCSScript[#DCSScript+1]="local Arguments = MissionControllable:GetState( MissionControllable, '"..ArgumentKey.."' ) "
DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )"
else
DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )"
end
local DCSTask=self:TaskWrappedAction(self:CommandDoScript(table.concat(DCSScript)))
return DCSTask
end
function CONTROLLABLE:TaskMission(TaskMission)
local DCSTask={
id='Mission',
params={
TaskMission,
},
}
return DCSTask
end
do
function CONTROLLABLE:PatrolRoute()
local PatrolGroup=self
if not self:IsInstanceOf("GROUP")then
PatrolGroup=self:GetGroup()
end
self:F({PatrolGroup=PatrolGroup:GetName()})
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
local Waypoints=PatrolGroup:GetTemplateRoutePoints()
local FromCoord=PatrolGroup:GetCoordinate()
local depth=0
local IsSub=false
if PatrolGroup:IsShip()then
local navalvec3=FromCoord:GetVec3()
if navalvec3.y<0 then
depth=navalvec3.y
IsSub=true
end
end
local Waypoint=Waypoints[1]
local Speed=Waypoint.speed or(20/3.6)
local From=FromCoord:WaypointGround(Speed)
if IsSub then
From=FromCoord:WaypointNaval(Speed,Waypoint.alt)
end
table.insert(Waypoints,1,From)
local TaskRoute=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRoute")
self:F({Waypoints=Waypoints})
local Waypoint=Waypoints[#Waypoints]
PatrolGroup:SetTaskWaypoint(Waypoint,TaskRoute)
PatrolGroup:Route(Waypoints,2)
end
end
function CONTROLLABLE:PatrolRouteRandom(Speed,Formation,ToWaypoint)
local PatrolGroup=self
if not self:IsInstanceOf("GROUP")then
PatrolGroup=self:GetGroup()
end
self:F({PatrolGroup=PatrolGroup:GetName()})
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
local Waypoints=PatrolGroup:GetTemplateRoutePoints()
local FromCoord=PatrolGroup:GetCoordinate()
local FromWaypoint=1
if ToWaypoint then
FromWaypoint=ToWaypoint
end
local depth=0
local IsSub=false
if PatrolGroup:IsShip()then
local navalvec3=FromCoord:GetVec3()
if navalvec3.y<0 then
depth=navalvec3.y
IsSub=true
end
end
local ToWaypoint
repeat
ToWaypoint=math.random(1,#Waypoints)
until(ToWaypoint~=FromWaypoint)
self:F({FromWaypoint=FromWaypoint,ToWaypoint=ToWaypoint})
local Waypoint=Waypoints[ToWaypoint]
local ToCoord=COORDINATE:NewFromVec2({x=Waypoint.x,y=Waypoint.y})
local Route={}
if IsSub then
Route[#Route+1]=FromCoord:WaypointNaval(Speed,depth)
Route[#Route+1]=ToCoord:WaypointNaval(Speed,Waypoint.alt)
else
Route[#Route+1]=FromCoord:WaypointGround(Speed,Formation)
Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
end
local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRouteRandom",Speed,Formation,ToWaypoint)
PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
PatrolGroup:Route(Route,1)
end
end
function CONTROLLABLE:PatrolZones(ZoneList,Speed,Formation,DelayMin,DelayMax)
if type(ZoneList)~="table"then
ZoneList={ZoneList}
end
local PatrolGroup=self
if not self:IsInstanceOf("GROUP")then
PatrolGroup=self:GetGroup()
end
DelayMin=DelayMin or 1
if not DelayMax or DelayMax<DelayMin then
DelayMax=DelayMin
end
local Delay=math.random(DelayMin,DelayMax)
self:F({PatrolGroup=PatrolGroup:GetName()})
if PatrolGroup:IsGround()or PatrolGroup:IsShip()then
local FromCoord=PatrolGroup:GetCoordinate()
local depth=0
local IsSub=false
if PatrolGroup:IsShip()then
local navalvec3=FromCoord:GetVec3()
if navalvec3.y<0 then
depth=navalvec3.y
IsSub=true
end
end
local RandomZone=ZoneList[math.random(1,#ZoneList)]
local ToCoord=RandomZone:GetRandomCoordinate(10)
local Route={}
if IsSub then
Route[#Route+1]=FromCoord:WaypointNaval(Speed,depth)
Route[#Route+1]=ToCoord:WaypointNaval(Speed,depth)
else
Route[#Route+1]=FromCoord:WaypointGround(Speed,Formation)
Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation)
end
local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolZones",ZoneList,Speed,Formation,DelayMin,DelayMax)
PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
PatrolGroup:Route(Route,Delay)
end
end
end
function CONTROLLABLE:TaskRoute(Points)
local DCSTask={
id='Mission',
params={
airborne=self:IsAir(),
route={points=Points},
},
}
return DCSTask
end
do
function CONTROLLABLE:RouteToVec2(Point,Speed)
self:F2({Point,Speed})
local ControllablePoint=self:GetUnit(1):GetVec2()
local PointFrom={}
PointFrom.x=ControllablePoint.x
PointFrom.y=ControllablePoint.y
PointFrom.type="Turning Point"
PointFrom.action="Turning Point"
PointFrom.speed=Speed
PointFrom.speed_locked=true
PointFrom.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local PointTo={}
PointTo.x=Point.x
PointTo.y=Point.y
PointTo.type="Turning Point"
PointTo.action="Fly Over Point"
PointTo.speed=Speed
PointTo.speed_locked=true
PointTo.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
function CONTROLLABLE:RouteToVec3(Point,Speed)
self:F2({Point,Speed})
local ControllableVec3=self:GetUnit(1):GetVec3()
local PointFrom={}
PointFrom.x=ControllableVec3.x
PointFrom.y=ControllableVec3.z
PointFrom.alt=ControllableVec3.y
PointFrom.alt_type="BARO"
PointFrom.type="Turning Point"
PointFrom.action="Turning Point"
PointFrom.speed=Speed
PointFrom.speed_locked=true
PointFrom.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local PointTo={}
PointTo.x=Point.x
PointTo.y=Point.z
PointTo.alt=Point.y
PointTo.alt_type="BARO"
PointTo.type="Turning Point"
PointTo.action="Fly Over Point"
PointTo.speed=Speed
PointTo.speed_locked=true
PointTo.properties={
["vnav"]=1,
["scale"]=0,
["angle"]=0,
["vangle"]=0,
["steer"]=2,
}
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
function CONTROLLABLE:Route(Route,DelaySeconds)
self:F2(Route)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local RouteTask=self:TaskRoute(Route)
self:SetTask(RouteTask,DelaySeconds or 1)
return self
end
return nil
end
function CONTROLLABLE:RoutePush(Route,DelaySeconds)
self:F2(Route)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local RouteTask=self:TaskRoute(Route)
self:PushTask(RouteTask,DelaySeconds or 1)
return self
end
return nil
end
function CONTROLLABLE:RouteStop()
self:F(self:GetName().." RouteStop")
local CommandStop=self:CommandStopRoute(true)
self:SetCommand(CommandStop)
end
function CONTROLLABLE:RouteResume()
self:F(self:GetName().." RouteResume")
local CommandResume=self:CommandStopRoute(false)
self:SetCommand(CommandResume)
end
function CONTROLLABLE:RouteGroundTo(ToCoordinate,Speed,Formation,DelaySeconds,WaypointFunction,WaypointFunctionArguments)
local FromCoordinate=self:GetCoordinate()
local FromWP=FromCoordinate:WaypointGround(Speed,Formation)
local ToWP=ToCoordinate:WaypointGround(Speed,Formation)
local route={FromWP,ToWP}
if WaypointFunction then
local N=#route
for n,waypoint in pairs(route)do
waypoint.task={}
waypoint.task.id="ComboTask"
waypoint.task.params={}
waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))}
end
end
self:Route(route,DelaySeconds)
return self
end
function CONTROLLABLE:RouteGroundOnRoad(ToCoordinate,Speed,DelaySeconds,OffRoadFormation,WaypointFunction,WaypointFunctionArguments)
Speed=Speed or 20
DelaySeconds=DelaySeconds or 1
OffRoadFormation=OffRoadFormation or"Off Road"
local route=self:TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,nil,nil,WaypointFunction,WaypointFunctionArguments)
self:Route(route,DelaySeconds)
return self
end
function CONTROLLABLE:RouteGroundOnRailRoads(ToCoordinate,Speed,DelaySeconds,WaypointFunction,WaypointFunctionArguments)
Speed=Speed or 20
DelaySeconds=DelaySeconds or 1
local route=self:TaskGroundOnRailRoads(ToCoordinate,Speed,WaypointFunction,WaypointFunctionArguments)
self:Route(route,DelaySeconds)
return self
end
function CONTROLLABLE:TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments)
self:T({ToCoordinate=ToCoordinate,Speed=Speed,OffRoadFormation=OffRoadFormation,WaypointFunction=WaypointFunction,Args=WaypointFunctionArguments})
Speed=Speed or 20
OffRoadFormation=OffRoadFormation or"Off Road"
FromCoordinate=FromCoordinate or self:GetCoordinate()
local PathOnRoad,LengthOnRoad,GotPath=FromCoordinate:GetPathOnRoad(ToCoordinate,true)
local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate,false)
local LengthOffRoad
local LongRoad
local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate)
if GotPath and LengthRoad then
LengthOffRoad=LengthOnRoad-LengthRoad
LongRoad=LengthOnRoad and((LengthOnRoad>LengthDirect*10)or(LengthRoad/LengthOnRoad*100<5))
self:T(string.format("Length on road   = %.3f km",LengthOnRoad/1000))
self:T(string.format("Length directly  = %.3f km",LengthDirect/1000))
self:T(string.format("Length fraction  = %.3f km",LengthOnRoad/LengthDirect))
self:T(string.format("Length only road = %.3f km",LengthRoad/1000))
self:T(string.format("Length off road  = %.3f km",LengthOffRoad/1000))
self:T(string.format("Percent on road  = %.1f",LengthRoad/LengthOnRoad*100))
end
local route={}
local canroad=false
if GotPath and LengthRoad and LengthDirect>2000 then
if LongRoad and Shortcut then
table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation))
else
table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,PathOnRoad[2]:WaypointGround(Speed,"On Road"))
table.insert(route,PathOnRoad[#PathOnRoad-1]:WaypointGround(Speed,"On Road"))
local dist=ToCoordinate:Get2DDistance(PathOnRoad[#PathOnRoad-1])
if dist>10 then
table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation))
table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation))
end
end
canroad=true
else
table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation))
table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation))
end
if WaypointFunction then
local N=#route
for n,waypoint in pairs(route)do
waypoint.task={}
waypoint.task.id="ComboTask"
waypoint.task.params={}
waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))}
end
end
return route,canroad
end
function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate,Speed,WaypointFunction,WaypointFunctionArguments)
self:F2({ToCoordinate=ToCoordinate,Speed=Speed})
Speed=Speed or 20
local FromCoordinate=self:GetCoordinate()
local PathOnRail,LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate,false,true)
self:T(string.format("Length on railroad = %.3f km",LengthOnRail/1000))
local route={}
if PathOnRail then
table.insert(route,PathOnRail[1]:WaypointGround(Speed,"On Railroad"))
table.insert(route,PathOnRail[2]:WaypointGround(Speed,"On Railroad"))
end
if WaypointFunction then
local N=#route
for n,waypoint in pairs(route)do
waypoint.task={}
waypoint.task.id="ComboTask"
waypoint.task.params={}
waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))}
end
end
return route
end
function CONTROLLABLE.___PassingWaypoint(controllable,n,N,waypointfunction,...)
waypointfunction(controllable,n,N,...)
end
function CONTROLLABLE:RouteAirTo(ToCoordinate,AltType,Type,Action,Speed,DelaySeconds)
local FromCoordinate=self:GetCoordinate()
local FromWP=FromCoordinate:WaypointAir()
local ToWP=ToCoordinate:WaypointAir(AltType,Type,Action,Speed)
self:Route({FromWP,ToWP},DelaySeconds)
return self
end
function CONTROLLABLE:TaskRouteToZone(Zone,Randomize,Speed,Formation)
self:F2(Zone)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local ControllablePoint=self:GetVec2()
local PointFrom={}
PointFrom.x=ControllablePoint.x
PointFrom.y=ControllablePoint.y
PointFrom.type="Turning Point"
PointFrom.action=Formation or"Cone"
PointFrom.speed=20/3.6
local PointTo={}
local ZonePoint
if Randomize then
ZonePoint=Zone:GetRandomVec2()
else
ZonePoint=Zone:GetVec2()
end
PointTo.x=ZonePoint.x
PointTo.y=ZonePoint.y
PointTo.type="Turning Point"
if Formation then
PointTo.action=Formation
else
PointTo.action="Cone"
end
if Speed then
PointTo.speed=Speed
else
PointTo.speed=20/3.6
end
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
return nil
end
function CONTROLLABLE:TaskRouteToVec2(Vec2,Speed,Formation)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local ControllablePoint=self:GetVec2()
local PointFrom={}
PointFrom.x=ControllablePoint.x
PointFrom.y=ControllablePoint.y
PointFrom.type="Turning Point"
PointFrom.action=Formation or"Cone"
PointFrom.speed=20/3.6
local PointTo={}
PointTo.x=Vec2.x
PointTo.y=Vec2.y
PointTo.type="Turning Point"
if Formation then
PointTo.action=Formation
else
PointTo.action="Cone"
end
if Speed then
PointTo.speed=Speed
else
PointTo.speed=20/3.6
end
local Points={PointFrom,PointTo}
self:T3(Points)
self:Route(Points)
return self
end
return nil
end
end
function CONTROLLABLE:CommandDoScript(DoScript)
local DCSDoScript={
id="Script",
params={
command=DoScript,
},
}
self:T3(DCSDoScript)
return DCSDoScript
end
function CONTROLLABLE:GetTaskMission()
self:F2(self.ControllableName)
return UTILS.DeepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template)
end
function CONTROLLABLE:GetTaskRoute()
self:F2(self.ControllableName)
return UTILS.DeepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template.route.points)
end
function CONTROLLABLE:CopyRoute(Begin,End,Randomize,Radius)
self:F2({Begin,End})
local Points={}
local ControllableName=string.match(self:GetName(),".*#")
if ControllableName then
ControllableName=ControllableName:sub(1,-2)
else
ControllableName=self:GetName()
end
self:T3({ControllableName})
local Template=_DATABASE.Templates.Controllables[ControllableName].Template
if Template then
if not Begin then
Begin=0
end
if not End then
End=0
end
for TPointID=Begin+1,#Template.route.points-End do
if Template.route.points[TPointID]then
Points[#Points+1]=UTILS.DeepCopy(Template.route.points[TPointID])
if Randomize then
if not Radius then
Radius=500
end
Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius)
Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius)
end
end
end
return Points
else
error("Template not found for Controllable : "..ControllableName)
end
return nil
end
function CONTROLLABLE:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil
local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.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 Params={}
if DetectionVisual then
Params[#Params+1]=DetectionVisual
end
if DetectionOptical then
Params[#Params+1]=DetectionOptical
end
if DetectionRadar then
Params[#Params+1]=DetectionRadar
end
if DetectionIRST then
Params[#Params+1]=DetectionIRST
end
if DetectionRWR then
Params[#Params+1]=DetectionRWR
end
if DetectionDLINK then
Params[#Params+1]=DetectionDLINK
end
self:T2({DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK})
return self:_GetController():getDetectedTargets(Params[1],Params[2],Params[3],Params[4],Params[5],Params[6])
end
return nil
end
function CONTROLLABLE:IsTargetDetected(DCSObject,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil
local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.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,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity
=Controller:isTargetDetected(DCSObject,DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK)
return TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity
end
return nil
end
function CONTROLLABLE:IsUnitDetected(Unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
if Unit and Unit:IsAlive()then
return self:IsTargetDetected(Unit:GetDCSObject(),DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
end
return nil
end
function CONTROLLABLE:IsGroupDetected(Group,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self:F2(self.ControllableName)
if Group and Group:IsAlive()then
for _,_unit in pairs(Group:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
local isdetected=self:IsUnitDetected(unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
if isdetected then
return true
end
end
end
return false
end
return nil
end
function CONTROLLABLE:GetDetectedUnitSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local unitset=SET_UNIT:New()
for DetectionObjectID,Detection in pairs(detectedtargets or{})do
local DetectedObject=Detection.object
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
local unit=UNIT:Find(DetectedObject)
if unit and unit:IsAlive()then
if not unitset:FindUnit(unit:GetName())then
unitset:AddUnit(unit)
end
end
end
end
return unitset
end
function CONTROLLABLE:GetDetectedGroupSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local groupset=SET_GROUP:New()
for DetectionObjectID,Detection in pairs(detectedtargets or{})do
local DetectedObject=Detection.object
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
local unit=UNIT:Find(DetectedObject)
if unit and unit:IsAlive()then
local group=unit:GetGroup()
if group and not groupset:FindGroup(group:GetName())then
groupset:AddGroup(group)
end
end
end
end
return groupset
end
function CONTROLLABLE:SetOption(OptionID,OptionValue)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
Controller:setOption(OptionID,OptionValue)
return self
end
return nil
end
function CONTROLLABLE:OptionROE(ROEvalue)
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,ROEvalue)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,ROEvalue)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,ROEvalue)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEHoldFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()or self:IsGround()or self:IsShip()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEHoldFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.WEAPON_HOLD)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.WEAPON_HOLD)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEReturnFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()or self:IsGround()or self:IsShip()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEReturnFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.RETURN_FIRE)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.RETURN_FIRE)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.RETURN_FIRE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEOpenFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()or self:IsGround()or self:IsShip()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEOpenFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE)
elseif self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.OPEN_FIRE)
elseif self:IsShip()then
Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.OPEN_FIRE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEOpenFireWeaponFreePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEOpenFireWeaponFree()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROEWeaponFreePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROEWeaponFree()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_FREE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTNoReactionPossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTNoReaction()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROT(ROTvalue)
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,ROTvalue)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTPassiveDefensePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTPassiveDefense()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTEvadeFirePossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTEvadeFire()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionROTVerticalPossible()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
if self:IsAir()then
return true
end
return false
end
return nil
end
function CONTROLLABLE:OptionROTVertical()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAlarmStateAuto()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.AUTO)
elseif self:IsShip()then
Controller:setOption(9,0)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAlarmStateGreen()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
elseif self:IsShip()then
Controller:setOption(9,1)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAlarmStateRed()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsGround()then
Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
elseif self:IsShip()then
Controller:setOption(9,2)
end
return self
end
return nil
end
function CONTROLLABLE:OptionRTBBingoFuel(RTB)
self:F2({self.ControllableName})
if RTB==nil then
RTB=true
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.RTB_ON_BINGO,RTB)
end
return self
end
return nil
end
function CONTROLLABLE:OptionRTBAmmo(WeaponsFlag)
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO,WeaponsFlag)
end
return self
end
return nil
end
function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,false)
end
return self
end
return nil
end
function CONTROLLABLE:OptionKeepWeaponsOnThreat()
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,true)
end
return self
end
return nil
end
function CONTROLLABLE:OptionProhibitAfterburner(Prohibit)
self:F2({self.ControllableName})
if Prohibit==nil then
Prohibit=true
end
if self:IsAir()then
self:SetOption(AI.Option.Air.id.PROHIBIT_AB,Prohibit)
end
return self
end
function CONTROLLABLE: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()
if DCSControllable then
local Controller=self:_GetController()
if self:IsAir()then
Controller:setOption(AI.Option.Air.id.ECM_USING,ECMvalue or 1)
end
end
return self
end
function CONTROLLABLE:OptionECM_Never()
self:F2({self.ControllableName})
self:OptionECM(0)
return self
end
function CONTROLLABLE:OptionECM_OnlyLockByRadar()
self:F2({self.ControllableName})
self:OptionECM(1)
return self
end
function CONTROLLABLE:OptionECM_DetectedLockByRadar()
self:F2({self.ControllableName})
self:OptionECM(2)
return self
end
function CONTROLLABLE:OptionECM_AlwaysOn()
self:F2({self.ControllableName})
self:OptionECM(3)
return self
end
function CONTROLLABLE:WayPointInitialize(WayPoints)
self:F({WayPoints})
if WayPoints then
self.WayPoints=WayPoints
else
self.WayPoints=self:GetTaskRoute()
end
return self
end
function CONTROLLABLE:GetWayPoints()
self:F()
if self.WayPoints then
return self.WayPoints
end
return nil
end
function CONTROLLABLE:WayPointFunction(WayPoint,WayPointIndex,WayPointFunction,...)
self:F2({WayPoint,WayPointIndex,WayPointFunction})
if not self.WayPoints then
self:WayPointInitialize()
end
table.insert(self.WayPoints[WayPoint].task.params.tasks,WayPointIndex)
self.WayPoints[WayPoint].task.params.tasks[WayPointIndex]=self:TaskFunction(WayPointFunction,arg)
return self
end
function CONTROLLABLE:WayPointExecute(WayPoint,WaitTime)
self:F({WayPoint,WaitTime})
if not WayPoint then
WayPoint=1
end
for TaskPointID=1,WayPoint-1 do
table.remove(self.WayPoints,1)
end
self:T3(self.WayPoints)
self:SetTask(self:TaskRoute(self.WayPoints),WaitTime)
return self
end
function CONTROLLABLE:IsAirPlane()
self:F2()
local DCSObject=self:GetDCSObject()
if DCSObject then
local Category=DCSObject:getDesc().category
return Category==Unit.Category.AIRPLANE
end
return nil
end
function CONTROLLABLE:IsHelicopter()
self:F2()
local DCSObject=self:GetDCSObject()
if DCSObject then
local Category=DCSObject:getDesc().category
return Category==Unit.Category.HELICOPTER
end
return nil
end
function CONTROLLABLE:OptionRestrictBurner(RestrictBurner)
self:F2({self.ControllableName})
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if RestrictBurner==true then
if self:IsAir()then
Controller:setOption(16,true)
end
else
if self:IsAir()then
Controller:setOption(16,false)
end
end
end
end
end
function CONTROLLABLE:OptionAAAttackRange(range)
self:F2({self.ControllableName})
local range=range or 3
if range<0 or range>4 then
range=3
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsAir()then
self:SetOption(AI.Option.Air.id.MISSILE_ATTACK,range)
end
end
return self
end
return nil
end
function CONTROLLABLE:OptionEngageRange(EngageRange)
self:F2({self.ControllableName})
EngageRange=EngageRange or 100
if EngageRange<0 or EngageRange>100 then
EngageRange=100
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsGround()then
self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,EngageRange)
end
end
return self
end
return nil
end
function CONTROLLABLE:SetOptionRadarUsing(Option)
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.RADAR_USING,Option)
end
return self
end
function CONTROLLABLE:SetOptionRadarUsingNever()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.RADAR_USING,0)
end
return self
end
function CONTROLLABLE:SetOptionRadarUsingForAttackOnly()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.RADAR_USING,1)
end
return self
end
function CONTROLLABLE:SetOptionRadarUsingForSearchIfRequired()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.RADAR_USING,2)
end
return self
end
function CONTROLLABLE:SetOptionRadarUsingForContinousSearch()
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.RADAR_USING,3)
end
return self
end
function CONTROLLABLE:SetOptionWaypointPassReport(OnOff)
self:F2({self.ControllableName})
local onoff=(OnOff==nil or OnOff==true)and false or true
if self:IsAir()then
self:SetOption(AI.Option.Air.id.PROHIBIT_WP_PASS_REPORT,onoff)
end
return self
end
function CONTROLLABLE:SetOptionRadioSilence(OnOff)
local onoff=(OnOff==true or OnOff==nil)and true or false
self:F2({self.ControllableName})
if self:IsAir()then
self:SetOption(AI.Option.Air.id.SILENCE,onoff)
end
return self
end
function CONTROLLABLE:SetOptionRadioContact(Objects)
self:F2({self.ControllableName})
if not Objects then Objects={"Air"}end
if type(Objects)~="table"then Objects={Objects}end
if self:IsAir()then
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_CONTACT,Objects)
end
return self
end
function CONTROLLABLE:SetOptionRadioEngage(Objects)
self:F2({self.ControllableName})
if not Objects then Objects={"Air"}end
if type(Objects)~="table"then Objects={Objects}end
if self:IsAir()then
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_ENGAGE,Objects)
end
return self
end
function CONTROLLABLE:SetOptionRadioKill(Objects)
self:F2({self.ControllableName})
if not Objects then Objects={"Air"}end
if type(Objects)~="table"then Objects={Objects}end
if self:IsAir()then
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_KILL,Objects)
end
return self
end
function CONTROLLABLE:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut,formation,onland)
self:F2({self.ControllableName})
local _coord=self:GetCoordinate()
if not _coord then
return self
end
local _radius=radius or 500
local _speed=speed or 20
local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100)
if onland then
for i=1,50 do
local island=_tocoord:GetSurfaceType()==land.SurfaceType.LAND and true or false
if island then break end
_tocoord=_coord:GetRandomCoordinateInRadius(_radius,100)
end
end
local _onroad=onroad or true
local _grptsk={}
local _candoroad=false
local _shortcut=shortcut or false
local _formation=formation or"Off Road"
if onroad then
_grptsk,_candoroad=self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut)
self:Route(_grptsk,5)
else
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation)
end
return self
end
function CONTROLLABLE:OptionDisperseOnAttack(Seconds)
self:F2({self.ControllableName})
local seconds=Seconds or 0
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsGround()then
self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK,seconds)
end
end
return self
end
return nil
end
function CONTROLLABLE:IsSubmarine()
self:F2()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
if UnitDescriptor.attributes["Submarines"]==true then
return true
else
return false
end
end
return nil
end
function CONTROLLABLE:SetSpeed(Speed,Keep)
self:F2({self.ControllableName})
local speed=Speed or 5
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
Controller:setSpeed(speed,Keep)
end
end
return self
end
function CONTROLLABLE:SetAltitude(Altitude,Keep,AltType)
self:F2({self.ControllableName})
local altitude=Altitude or 1000
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=self:_GetController()
if Controller then
if self:IsAir()then
Controller:setAltitude(altitude,Keep,AltType)
end
end
end
return self
end
function CONTROLLABLE:TaskAerobatics()
local DCSTaskAerobatics={
id="Aerobatics",
params={
["maneuversSequency"]={},
},
["enabled"]=true,
["auto"]=false,
}
return DCSTaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsCandle(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local CandleTask={
["name"]="CANDLE",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
}
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],CandleTask)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsEdgeFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime,Side)
local maxrepeats=10
local maxflight=200
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local flighttime=FlightTime or 10
if flighttime>200 then maxflight=flighttime end
local EdgeTask={
["name"]="EDGE_FLIGHT",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["FlightTime"]={
["max_v"]=maxflight,
["min_v"]=1,
["order"]=6,
["step"]=0.1,
["value"]=flighttime or 10,
},
["SIDE"]={
["order"]=7,
["value"]=Side or 0,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],EdgeTask)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsWingoverFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime)
local maxrepeats=10
local maxflight=200
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local flighttime=FlightTime or 10
if flighttime>200 then maxflight=flighttime end
local WingoverTask={
["name"]="WINGOVER_FLIGHT",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["FlightTime"]={
["max_v"]=maxflight,
["min_v"]=1,
["order"]=6,
["step"]=0.1,
["value"]=flighttime or 10,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],WingoverTask)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local LoopTask={
["name"]="LOOP",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
}
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsHorizontalEight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local LoopTask={
["name"]="HORIZONTAL_EIGHT",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["SIDE"]={
["order"]=6,
["value"]=Side or 0,
},
["ROLL1"]={
["order"]=7,
["value"]=RollDeg or 60,
},
["ROLL2"]={
["order"]=8,
["value"]=RollDeg or 60,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsHammerhead(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="HUMMERHEAD",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["SIDE"]={
["order"]=6,
["value"]=Side or 0,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsSkewedLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="SKEWED_LOOP",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["ROLL"]={
["order"]=6,
["value"]=RollDeg or 60,
},
["SIDE"]={
["order"]=7,
["value"]=Side or 0,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg,Pull,Angle)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="TURN",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["Ny_req"]={
["order"]=6,
["value"]=Pull or 2,
},
["ROLL"]={
["order"]=7,
["value"]=RollDeg or 60,
},
["SECTOR"]={
["order"]=8,
["value"]=Angle or 180,
},
["SIDE"]={
["order"]=9,
["value"]=Side or 0,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsDive(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude)
local maxrepeats=10
local angle=Angle
if angle<15 then angle=15 elseif angle>90 then angle=90 end
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="DIVE",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 5000,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["Angle"]={
["max_v"]=90,
["min_v"]=15,
["order"]=6,
["step"]=5,
["value"]=angle or 45,
},
["FinalAltitude"]={
["order"]=7,
["value"]=FinalAltitude or 1000,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsMilitaryTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="MILITARY_TURN",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
}
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsImmelmann(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="IMMELMAN",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
}
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsStraightFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local maxflight=200
if Repeats>maxrepeats then maxrepeats=Repeats end
local flighttime=FlightTime or 10
if flighttime>200 then maxflight=flighttime end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="STRAIGHT_FLIGHT",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["FlightTime"]={
["max_v"]=maxflight,
["min_v"]=1,
["order"]=6,
["step"]=0.1,
["value"]=flighttime or 10,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsClimb(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="CLIMB",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["Angle"]={
["max_v"]=90,
["min_v"]=15,
["order"]=6,
["step"]=5,
["value"]=Angle or 45,
},
["FinalAltitude"]={
["order"]=7,
["value"]=FinalAltitude or 5000,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsSpiral(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Roll,Side,UpDown,Angle)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local updown=UpDown and 1 or 0
local side=Side and 1 or 0
local Task={
["name"]="SPIRAL",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["SECTOR"]={
["order"]=6,
["value"]=TurnAngle or 360,
},
["ROLL"]={
["order"]=7,
["value"]=Roll or 60,
},
["SIDE"]={
["order"]=8,
["value"]=side or 0,
},
["UPDOWN"]={
["order"]=9,
["value"]=updown or 0,
},
["Angle"]={
["max_v"]=90,
["min_v"]=15,
["order"]=10,
["step"]=5,
["value"]=Angle or 45,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsSplitS(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FinalSpeed)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local maxflight=200
if Repeats>maxrepeats then maxrepeats=Repeats end
local finalspeed=FinalSpeed or 500
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="SPLIT_S",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["FinalSpeed"]={
["order"]=6,
["value"]=finalspeed,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsAileronRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle,FixAngle)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local maxflight=200
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="AILERON_ROLL",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["SIDE"]={
["order"]=6,
["value"]=Side or 0,
},
["RollRate"]={
["max_v"]=450,
["min_v"]=15,
["order"]=7,
["step"]=5,
["value"]=RollRate or 90,
},
["SECTOR"]={
["order"]=8,
["value"]=TurnAngle or 360,
},
["FIXSECTOR"]={
["max_v"]=180,
["min_v"]=0,
["order"]=9,
["step"]=5,
["value"]=FixAngle or 0,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsForcedTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Side,FlightTime,MinSpeed)
local maxrepeats=10
local flighttime=FlightTime or 30
local maxtime=200
if flighttime>200 then maxtime=flighttime end
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="FORCED_TURN",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["SECTOR"]={
["order"]=6,
["value"]=TurnAngle or 360,
},
["SIDE"]={
["order"]=7,
["value"]=Side or 0,
},
["FlightTime"]={
["max_v"]=maxtime or 200,
["min_v"]=0,
["order"]=8,
["step"]=0.1,
["value"]=flighttime or 30,
},
["MinSpeed"]={
["max_v"]=3000,
["min_v"]=30,
["order"]=9,
["step"]=10,
["value"]=MinSpeed or 250,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:TaskAerobaticsBarrelRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle)
local maxrepeats=10
if Repeats>maxrepeats then maxrepeats=Repeats end
local usesmoke=UseSmoke and 1 or 0
local startimmediately=StartImmediately and 1 or 0
local Task={
["name"]="BARREL_ROLL",
["params"]={
["RepeatQty"]={
["max_v"]=maxrepeats,
["min_v"]=1,
["order"]=1,
["value"]=Repeats or 1,
},
["InitAltitude"]={
["order"]=2,
["value"]=InitAltitude or 0,
},
["InitSpeed"]={
["order"]=3,
["value"]=InitSpeed or 0,
},
["UseSmoke"]={
["order"]=4,
["value"]=usesmoke,
},
["StartImmediatly"]={
["order"]=5,
["value"]=startimmediately,
},
["SIDE"]={
["order"]=6,
["value"]=Side or 0,
},
["RollRate"]={
["max_v"]=450,
["min_v"]=15,
["order"]=7,
["step"]=5,
["value"]=RollRate or 90,
},
["SECTOR"]={
["order"]=8,
["value"]=TurnAngle or 360,
},
}
}
table.insert(TaskAerobatics.params["maneuversSequency"],Task)
return TaskAerobatics
end
function CONTROLLABLE:PatrolRaceTrack(Point1,Point2,Altitude,Speed,Formation,AGL,Delay)
local PatrolGroup=self
if not self:IsInstanceOf("GROUP")then
PatrolGroup=self:GetGroup()
end
local delay=Delay or 1
self:F({PatrolGroup=PatrolGroup:GetName()})
if PatrolGroup:IsAir()then
if Formation then
PatrolGroup:SetOption(AI.Option.Air.id.FORMATION,Formation)
end
local FromCoord=PatrolGroup:GetCoordinate()
local ToCoord=Point1:GetCoordinate()
if Altitude then
local asl=true
if AGL then asl=false end
FromCoord:SetAltitude(Altitude,asl)
ToCoord:SetAltitude(Altitude,asl)
end
local Route={}
Route[#Route+1]=FromCoord:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description,timeReFuAr)
Route[#Route+1]=ToCoord:WaypointAir(AltType,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,description,timeReFuAr)
local TaskRouteToZone=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRaceTrack",Point2,Point1,Altitude,Speed,Formation,Delay)
PatrolGroup:SetTaskWaypoint(Route[#Route],TaskRouteToZone)
PatrolGroup:Route(Route,Delay)
end
return self
end
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",
}
GROUP.Takeoff={
Air=1,
Runway=2,
Hot=3,
Cold=4,
}
GROUPTEMPLATE={}
GROUPTEMPLATE.Takeoff={
[GROUP.Takeoff.Air]={"Turning Point","Turning Point"},
[GROUP.Takeoff.Runway]={"TakeOff","From Runway"},
[GROUP.Takeoff.Hot]={"TakeOffParkingHot","From Parking Area Hot"},
[GROUP.Takeoff.Cold]={"TakeOffParking","From Parking Area"}
}
GROUP.Attribute={
AIR_TRANSPORTPLANE="Air_TransportPlane",
AIR_AWACS="Air_AWACS",
AIR_FIGHTER="Air_Fighter",
AIR_BOMBER="Air_Bomber",
AIR_TANKER="Air_Tanker",
AIR_TRANSPORTHELO="Air_TransportHelo",
AIR_ATTACKHELO="Air_AttackHelo",
AIR_UAV="Air_UAV",
AIR_OTHER="Air_OtherAir",
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
GROUND_EWR="Ground_EWR",
GROUND_AAA="Ground_AAA",
GROUND_SAM="Ground_SAM",
GROUND_OTHER="Ground_OtherGround",
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
NAVAL_WARSHIP="Naval_WarShip",
NAVAL_ARMEDSHIP="Naval_ArmedShip",
NAVAL_UNARMEDSHIP="Naval_UnarmedShip",
NAVAL_OTHER="Naval_OtherNaval",
OTHER_UNKNOWN="Other_Unknown",
}
function GROUP:NewTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID)
local GroupName=GroupTemplate.name
_DATABASE:_RegisterGroupTemplate(GroupTemplate,CoalitionSide,CategoryID,CountryID,GroupName)
local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName))
self.GroupName=GroupName
if not _DATABASE.GROUPS[GroupName]then
_DATABASE.GROUPS[GroupName]=self
end
self:SetEventPriority(4)
return self
end
function GROUP:Register(GroupName)
local self=BASE:Inherit(self,CONTROLLABLE:New(GroupName))
self.GroupName=GroupName
self:SetEventPriority(4)
return self
end
function GROUP:Find(DCSGroup)
local GroupName=DCSGroup:getName()
local GroupFound=_DATABASE:FindGroup(GroupName)
return GroupFound
end
function GROUP:FindByName(GroupName)
local GroupFound=_DATABASE:FindGroup(GroupName)
return GroupFound
end
function GROUP:FindByMatching(Pattern)
local GroupFound=nil
for name,group in pairs(_DATABASE.GROUPS)do
if string.match(name,Pattern)then
GroupFound=group
break
end
end
return GroupFound
end
function GROUP:FindAllByMatching(Pattern)
local GroupsFound={}
for name,group in pairs(_DATABASE.GROUPS)do
if string.match(name,Pattern)then
GroupsFound[#GroupsFound+1]=group
end
end
return GroupsFound
end
function GROUP:GetDCSObject()
local DCSGroup=Group.getByName(self.GroupName)
if DCSGroup then
self.LastCallDCSObject=timer.getTime()
self.DCSObject=DCSGroup
return DCSGroup
end
return nil
end
function GROUP:GetPositionVec3()
local DCSPositionable=self:GetDCSObject()
if DCSPositionable then
local unit=DCSPositionable:getUnits()[1]
if unit then
local PositionablePosition=unit:getPosition().p
return PositionablePosition
end
end
return nil
end
function GROUP:IsAlive()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
if DCSGroup:isExist()then
local DCSUnit=DCSGroup:getUnit(1)
if DCSUnit then
local GroupIsAlive=DCSUnit:isActive()
return GroupIsAlive
end
end
end
return nil
end
function GROUP:IsActive()
local DCSGroup=self:GetDCSObject()
if DCSGroup and DCSGroup:isExist()then
local unit=DCSGroup:getUnit(1)
if unit then
local GroupIsActive=unit:isActive()
return GroupIsActive
end
end
return nil
end
function GROUP:Destroy(GenerateEvent,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,GROUP.Destroy,self,GenerateEvent)
else
local DCSGroup=Group.getByName(self.GroupName)
if DCSGroup then
for Index,UnitData in pairs(DCSGroup:getUnits())do
if GenerateEvent and GenerateEvent==true then
if self:IsAir()then
self:CreateEventCrash(timer.getTime(),UnitData)
else
self:CreateEventDead(timer.getTime(),UnitData)
end
elseif GenerateEvent==false then
else
self:CreateEventRemoveUnit(timer.getTime(),UnitData)
end
end
USERFLAG:New(self:GetName()):Set(100)
DCSGroup:destroy()
DCSGroup=nil
end
end
return nil
end
function GROUP:GetCategory()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
return GroupCategory
end
return nil
end
function GROUP:GetCategoryName()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local CategoryNames={
[Group.Category.AIRPLANE]="Airplane",
[Group.Category.HELICOPTER]="Helicopter",
[Group.Category.GROUND]="Ground Unit",
[Group.Category.SHIP]="Ship",
[Group.Category.TRAIN]="Train",
}
local GroupCategory=DCSGroup:getCategory()
return CategoryNames[GroupCategory]
end
return nil
end
function GROUP:GetCoalition()
if self.GroupCoalition~=nil then
return self.GroupCoalition
else
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCoalition=DCSGroup:getCoalition()
self.GroupCoalition=GroupCoalition
return GroupCoalition
end
end
return nil
end
function GROUP:GetCountry()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCountry=DCSGroup:getUnit(1):getCountry()
return GroupCountry
end
return nil
end
function GROUP:HasAttribute(attribute,all)
local _units=self:GetUnits()
if _units then
local _allhave=true
local _onehas=false
for _,_unit in pairs(_units)do
local _unit=_unit
if _unit then
local _hastit=_unit:HasAttribute(attribute)
if _hastit==true then
_onehas=true
else
_allhave=false
end
end
end
if all==true then
return _allhave
else
return _onehas
end
end
return nil
end
function GROUP:GetSpeedMax()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local Units=self:GetUnits()
local speedmax=nil
for _,unit in pairs(Units)do
local unit=unit
local speed=unit:GetSpeedMax()
if speedmax==nil or speed<speedmax then
speedmax=speed
end
end
return speedmax
end
return nil
end
function GROUP:GetRange()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local Units=self:GetUnits()
local Rangemin=nil
for _,unit in pairs(Units)do
local unit=unit
local range=unit:GetRange()
if range then
if Rangemin==nil then
Rangemin=range
elseif range<Rangemin then
Rangemin=range
end
end
end
return Rangemin
end
return nil
end
function GROUP:GetUnits()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnits=DCSGroup:getUnits()or{}
local Units={}
for Index,UnitData in pairs(DCSUnits)do
local unit=UNIT:Find(UnitData)
if unit then
Units[#Units+1]=UNIT:Find(UnitData)
else
local UnitName=UnitData:getName()
unit=_DATABASE:AddUnit(UnitName)
Units[#Units+1]=unit
end
end
return Units
end
return nil
end
function GROUP:GetPlayerUnits()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnits=DCSGroup:getUnits()
local Units={}
for Index,UnitData in pairs(DCSUnits)do
local PlayerUnit=UNIT:Find(UnitData)
if PlayerUnit:GetPlayerName()then
Units[#Units+1]=PlayerUnit
end
end
return Units
end
return nil
end
function GROUP:IsPlayer()
local unit=self:GetUnit(1)
if unit then
return unit:IsPlayer()
end
return false
end
function GROUP:GetUnit(UnitNumber)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local UnitFound=nil
local units=DCSGroup:getUnits()or{}
if units[UnitNumber]then
local UnitFound=UNIT:Find(units[UnitNumber])
if UnitFound then
return UnitFound
end
else
for _,_unit in pairs(units)do
local UnitFound=UNIT:Find(_unit)
if UnitFound then
return UnitFound
end
end
end
end
return nil
end
function GROUP:GetDCSUnit(UnitNumber)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
if DCSGroup.getUnit and DCSGroup:getUnit(UnitNumber)then
return DCSGroup:getUnit(UnitNumber)
else
local units=DCSGroup:getUnits()or{}
for _,_unit in pairs(units)do
if _unit and _unit:isExist()then
return _unit
end
end
end
end
return nil
end
function GROUP:GetSize()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupSize=DCSGroup:getSize()
if GroupSize then
return GroupSize
else
return 0
end
end
return nil
end
function GROUP:CountAliveUnits()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
local n=0
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
n=n+1
end
end
return n
end
return 0
end
function GROUP:GetFirstUnitAlive()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
return unit
end
end
end
return nil
end
function GROUP:GetFirstUnit()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
return units[1]
end
return nil
end
function GROUP:GetVelocityVec3()
local DCSGroup=self:GetDCSObject()
if DCSGroup and DCSGroup:isExist()then
local GroupUnits=DCSGroup:getUnits()
local GroupCount=#GroupUnits
local VelocityVec3={x=0,y=0,z=0}
for _,DCSUnit in pairs(GroupUnits)do
local UnitVelocityVec3=DCSUnit:getVelocity()
VelocityVec3.x=VelocityVec3.x+UnitVelocityVec3.x
VelocityVec3.y=VelocityVec3.y+UnitVelocityVec3.y
VelocityVec3.z=VelocityVec3.z+UnitVelocityVec3.z
end
VelocityVec3.x=VelocityVec3.x/GroupCount
VelocityVec3.y=VelocityVec3.y/GroupCount
VelocityVec3.z=VelocityVec3.z/GroupCount
return VelocityVec3
end
BASE:E({"Cannot GetVelocityVec3",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetAltitude(FromGround)
return self:GetHeight(FromGround)
end
function GROUP:GetHeight(FromGround)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupUnits=DCSGroup:getUnits()
local GroupCount=#GroupUnits
local GroupHeight=0
for _,DCSUnit in pairs(GroupUnits)do
local GroupPosition=DCSUnit:getPosition()
if FromGround==true then
local LandHeight=land.getHeight({x=GroupPosition.p.x,y=GroupPosition.p.z})
GroupHeight=GroupHeight+(GroupPosition.p.y-LandHeight)
else
GroupHeight=GroupHeight+GroupPosition.p.y
end
end
return GroupHeight/GroupCount
end
return nil
end
function GROUP:GetInitialSize()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupInitialSize=DCSGroup:getInitialSize()
return GroupInitialSize
end
return nil
end
function GROUP:GetDCSUnits()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnits=DCSGroup:getUnits()
return DCSUnits
end
return nil
end
function GROUP:Activate(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,GROUP.Activate,self)
else
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()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupTypeName=DCSGroup:getUnit(1):getTypeName()
return(GroupTypeName)
end
return nil
end
function GROUP:GetNatoReportingName()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupTypeName=DCSGroup:getUnit(1):getTypeName()
return UTILS.GetReportingName(GroupTypeName)
end
return"Bogey"
end
function GROUP:GetPlayerName()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local PlayerName=DCSGroup:getUnit(1):getPlayerName()
return(PlayerName)
end
return nil
end
function GROUP:GetCallsign()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCallSign=DCSGroup:getUnit(1):getCallsign()
return GroupCallSign
end
BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetVec2()
local Unit=self:GetUnit(1)
if Unit then
local vec2=Unit:GetVec2()
return vec2
end
end
function GROUP:GetVec3()
local unit=self:GetUnit(1)
if unit then
local vec3=unit:GetVec3()
return vec3
end
self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName))
return nil
end
function GROUP:GetAverageVec3()
local units=self:GetUnits()or{}
local x=0;local y=0;local z=0;local n=0
for _,unit in pairs(units)do
local vec3=nil
if unit and unit:IsAlive()then
vec3=unit:GetVec3()
end
if vec3 then
x=x+vec3.x
y=y+vec3.y
z=z+vec3.z
n=n+1
end
end
if n>0 then
local Vec3={x=x/n,y=y/n,z=z/n}
return Vec3
else
return self:GetVec3()
end
end
function GROUP:GetPointVec2()
local FirstUnit=self:GetUnit(1)
if FirstUnit then
local FirstUnitPointVec2=FirstUnit:GetPointVec2()
return FirstUnitPointVec2
end
BASE:E({"Cannot get COORDINATE",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetAverageCoordinate()
local vec3=self:GetAverageVec3()
if vec3 then
local coord=COORDINATE:NewFromVec3(vec3)
local Heading=self:GetHeading()
coord.Heading=Heading
return coord
else
local coord=self:GetCoordinate()
if coord then
return coord
else
BASE:E({"Cannot GetAverageCoordinate",Group=self,Alive=self:IsAlive()})
return nil
end
end
end
function GROUP:GetCoordinate()
local 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()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
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)
local FirstUnit=self:GetUnit(1)
if FirstUnit then
local FirstUnitRandomPointVec3=FirstUnit:GetRandomVec3(Radius)
return FirstUnitRandomPointVec3
end
BASE:E({"Cannot GetRandomVec3",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetHeading()
local GroupSize=self:GetSize()
local HeadingAccumulator=0
local n=0
local Units=self:GetUnits()
if GroupSize then
for _,unit in pairs(Units)do
if unit and unit:IsAlive()then
HeadingAccumulator=HeadingAccumulator+unit:GetHeading()
n=n+1
end
end
return math.floor(HeadingAccumulator/n)
end
BASE:E({"Cannot GetHeading",Group=self,Alive=self:IsAlive()})
return nil
end
function GROUP:GetFuelMin()
if not self:GetDCSObject()then
BASE:E({"Cannot GetFuel",Group=self,Alive=self:IsAlive()})
return 0
end
local min=65535
local unit=nil
local tmp=nil
for UnitID,UnitData in pairs(self:GetUnits())do
if UnitData and UnitData:IsAlive()then
tmp=UnitData:GetFuel()
if tmp<min then
min=tmp
unit=UnitData
end
end
end
return min,unit
end
function GROUP:GetFuelAvg()
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local GroupSize=self:GetSize()
local TotalFuel=0
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
local UnitFuel=Unit:GetFuel()or 0
TotalFuel=TotalFuel+UnitFuel
end
local GroupFuel=TotalFuel/GroupSize
return GroupFuel
end
BASE:E({"Cannot GetFuel",Group=self,Alive=self:IsAlive()})
return 0
end
function GROUP:GetFuel()
return self:GetFuelAvg()
end
function GROUP:GetAmmunition()
local DCSControllable=self:GetDCSObject()
local Ntot=0
local Nshells=0
local Nrockets=0
local Nmissiles=0
local Nbombs=0
local Narti=0
if DCSControllable then
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
local ntot,nshells,nrockets,nbombs,nmissiles,narti=Unit:GetAmmunition()
Ntot=Ntot+ntot
Nshells=Nshells+nshells
Nrockets=Nrockets+nrockets
Nmissiles=Nmissiles+nmissiles
Nbombs=Nbombs+nbombs
Narti=Narti+narti
end
end
return Ntot,Nshells,Nrockets,Nbombs,Nmissiles,Narti
end
do
function GROUP:IsInZone(Zone)
if self:IsAlive()then
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
local vec2=nil
if Unit then
vec2=Unit:GetVec2()
end
if vec2 and Zone:IsVec2InZone(vec2)then
return true
end
end
return false
end
return nil
end
function GROUP:IsCompletelyInZone(Zone)
if not self:IsAlive()then return false end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
else
return false
end
end
return true
end
function GROUP:IsPartlyInZone(Zone)
local IsOneUnitInZone=false
local IsOneUnitOutsideZone=false
if not self:IsAlive()then return false end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
IsOneUnitInZone=true
else
IsOneUnitOutsideZone=true
end
end
if IsOneUnitInZone and IsOneUnitOutsideZone then
return true
else
return false
end
end
function GROUP:IsPartlyOrCompletelyInZone(Zone)
return self:IsPartlyInZone(Zone)or self:IsCompletelyInZone(Zone)
end
function GROUP:IsNotInZone(Zone)
if not self:IsAlive()then return true end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
return false
end
end
return true
end
function GROUP:IsAnyInZone(Zone)
if not self:IsAlive()then return false end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
return true
end
end
return false
end
function GROUP:CountInZone(Zone)
local Count=0
if not self:IsAlive()then return Count end
for UnitID,UnitData in pairs(self:GetUnits())do
local Unit=UnitData
if Zone:IsVec3InZone(Unit:GetVec3())then
Count=Count+1
end
end
return Count
end
function GROUP:IsAir()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local IsAirResult=DCSGroup:getCategory()==Group.Category.AIRPLANE or DCSGroup:getCategory()==Group.Category.HELICOPTER
return IsAirResult
end
return nil
end
function GROUP:IsHelicopter()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
return GroupCategory==Group.Category.HELICOPTER
end
return nil
end
function GROUP:IsAirPlane()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
return GroupCategory==Group.Category.AIRPLANE
end
return nil
end
function GROUP:IsGround()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
return GroupCategory==Group.Category.GROUND
end
return nil
end
function GROUP:IsShip()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupCategory=DCSGroup:getCategory()
return GroupCategory==Group.Category.SHIP
end
return nil
end
function GROUP:AllOnGround()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local AllOnGroundResult=true
for Index,UnitData in pairs(DCSGroup:getUnits())do
if UnitData:inAir()then
AllOnGroundResult=false
end
end
return AllOnGroundResult
end
return nil
end
end
do
function GROUP:SetAIOnOff(AIOnOff)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSController=DCSGroup:getController()
if DCSController then
DCSController:setOnOff(AIOnOff)
return self
end
end
return nil
end
function GROUP:SetAIOn()
return self:SetAIOnOff(true)
end
function GROUP:SetAIOff()
return self:SetAIOnOff(false)
end
end
function GROUP:GetMaxVelocity()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupVelocityMax=0
for Index,UnitData in pairs(DCSGroup:getUnits())do
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
return GroupVelocityMax
end
return nil
end
function GROUP:GetMinHeight()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupHeightMin=999999999
for Index,UnitData in pairs(DCSGroup:getUnits())do
local UnitData=UnitData
local UnitHeight=UnitData:getPoint()
if UnitHeight<GroupHeightMin then
GroupHeightMin=UnitHeight
end
end
return GroupHeightMin
end
return nil
end
function GROUP:GetMaxHeight()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local GroupHeightMax=-999999999
for Index,UnitData in pairs(DCSGroup:getUnits())do
local UnitData=UnitData
local UnitHeight=UnitData:getPoint().p.y
if UnitHeight>GroupHeightMax then
GroupHeightMax=UnitHeight
end
end
return GroupHeightMax
end
return nil
end
function GROUP:GetTemplate()
local GroupName=self:GetName()
local template=_DATABASE:GetGroupTemplate(GroupName)
if template then
return UTILS.DeepCopy(template)
end
return nil
end
function GROUP:GetTemplateRoutePoints()
local GroupName=self:GetName()
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
return Template
end
function GROUP:SetTemplateCountry(Template,CountryID)
Template.CountryID=CountryID
return Template
end
function GROUP:SetTemplateCoalition(Template,CoalitionID)
Template.CoalitionID=CoalitionID
return Template
end
function GROUP:InitHeading(Heading)
self.InitRespawnHeading=Heading
return self
end
function GROUP:InitHeight(Height)
self.InitRespawnHeight=Height
return self
end
function GROUP:InitZone(Zone)
self.InitRespawnZone=Zone
return self
end
function GROUP:InitRandomizePositionZone(PositionZone)
self.InitRespawnRandomizePositionZone=PositionZone
self.InitRespawnRandomizePositionInner=nil
self.InitRespawnRandomizePositionOuter=nil
return self
end
function GROUP:InitRandomizePositionRadius(OuterRadius,InnerRadius)
self.InitRespawnRandomizePositionZone=nil
self.InitRespawnRandomizePositionOuter=OuterRadius
self.InitRespawnRandomizePositionInner=InnerRadius
return self
end
function GROUP:InitCoordinate(coordinate)
self.InitCoord=coordinate
return self
end
function GROUP:InitRadioCommsOnOff(switch)
if switch==true or switch==nil then
self.InitRespawnRadio=true
else
self.InitRespawnRadio=false
end
return self
end
function GROUP:InitRadioFrequency(frequency)
self.InitRespawnFreq=frequency
return self
end
function GROUP:InitRadioModulation(modulation)
if modulation and modulation:lower()=="fm"then
self.InitRespawnModu=radio.modulation.FM
else
self.InitRespawnModu=radio.modulation.AM
end
return self
end
function GROUP:InitModex(modex)
if modex then
self.InitRespawnModex=tonumber(modex)
end
return self
end
function GROUP:Respawn(Template,Reset)
Template=Template or self:GetTemplate()
local function _Heading(course)
local h
if course<=180 then
h=math.rad(course)
else
h=-math.rad(360-course)
end
return h
end
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
local NewPos={x=Vec3.x,y=Vec3.z}
if Reset==true then
for UnitID,UnitData in pairs(self:GetUnits())do
local GroupUnit=UnitData
if GroupUnit:IsAlive()then
local GroupUnitVec3=GroupUnit:GetVec3()
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3=Zone:GetRandomVec3()
else
if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then
GroupUnitVec3=COORDINATE:NewFromVec3(From):GetRandomVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
else
GroupUnitVec3=Zone:GetVec3()
end
end
end
if self.InitCoord then
GroupUnitVec3=self.InitCoord:GetVec3()
end
Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
if Zone then
Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x
Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z
else
Template.units[UnitID].x=GroupUnitVec3.x
Template.units[UnitID].y=GroupUnitVec3.z
end
Template.units[UnitID].heading=_Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading())
Template.units[UnitID].psi=-Template.units[UnitID].heading
end
end
Template=TransFormRoute(Template,OldPos,NewPos)
elseif Reset==false then
for UnitID,TemplateUnitData in pairs(Template.units)do
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=COORDINATE:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner)
else
GroupUnitVec3=Zone:GetVec3()
end
end
end
if self.InitCoord then
GroupUnitVec3=self.InitCoord:GetVec3()
end
Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y
Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x
Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z
Template.units[UnitID].heading=self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading
end
Template=TransFormRoute(Template,OldPos,NewPos)
else
local units=self:GetUnits()
for UnitID,Unit in pairs(Template.units)do
for _,_unit in pairs(units)do
local unit=_unit
if unit:GetName()==Unit.name then
local coord=unit:GetCoordinate()
local heading=unit:GetHeading()
Unit.x=coord.x
Unit.y=coord.z
Unit.alt=coord.y
Unit.heading=math.rad(heading)
Unit.psi=-Unit.heading
end
end
end
end
end
if self.InitRespawnModex then
for UnitID=1,#Template.units do
Template.units[UnitID].onboard_num=string.format("%03d",self.InitRespawnModex+(UnitID-1))
end
end
if self.InitRespawnRadio then
Template.communication=self.InitRespawnRadio
end
if self.InitRespawnFreq then
Template.frequency=self.InitRespawnFreq
end
if self.InitRespawnModu then
Template.modulation=self.InitRespawnModu
end
self:Destroy(false)
self: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)
if self and self:IsAlive()then
local airbase=self:GetCoordinate():GetClosestAirbase()
if airbase then
else
self:E("ERROR: could not find closest airbase!")
return nil
end
Takeoff=Takeoff or SPAWN.Takeoff.Hot
local AirbaseCoord=airbase:GetCoordinate()
SpawnTemplate=SpawnTemplate or self:GetTemplate()
if SpawnTemplate then
local SpawnPoint=SpawnTemplate.route.points[1]
SpawnPoint.linkUnit=nil
SpawnPoint.helipadId=nil
SpawnPoint.airdromeId=nil
local AirbaseID=airbase:GetID()
local AirbaseCategory=airbase:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
SpawnPoint.airdromeId=AirbaseID
end
SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1]
SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2]
local units=self:GetUnits()
local x
local y
for UnitID=1,#units do
local unit=units[UnitID]
local Parkingspot,TermialID,Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase)
local uc=unit:GetCoordinate()
SpawnTemplate.units[UnitID].x=uc.x
SpawnTemplate.units[UnitID].y=uc.z
SpawnTemplate.units[UnitID].alt=uc.y
SpawnTemplate.units[UnitID].parking=TermialID
SpawnTemplate.units[UnitID].parking_id=nil
end
SpawnPoint.x=SpawnTemplate.units[1].x
SpawnPoint.y=SpawnTemplate.units[1].y
SpawnPoint.alt=SpawnTemplate.units[1].alt
SpawnTemplate.x=SpawnTemplate.units[1].x
SpawnTemplate.y=SpawnTemplate.units[1].y
SpawnTemplate.uncontrolled=Uncontrolled
if self.InitRespawnRadio then
SpawnTemplate.communication=self.InitRespawnRadio
end
if self.InitRespawnFreq then
SpawnTemplate.frequency=self.InitRespawnFreq
end
if self.InitRespawnModu then
SpawnTemplate.modulation=self.InitRespawnModu
end
self:Destroy(false)
_DATABASE:Spawn(SpawnTemplate)
self:ResetEvents()
return self
end
else
self:E("WARNING: GROUP is not alive!")
end
return nil
end
function GROUP:GetTaskMission()
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
return UTILS.DeepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points)
else
return{}
end
end
function GROUP:CopyRoute(Begin,End,Randomize,Radius)
local Points={}
local GroupName=string.match(self:GetName(),".*#")
if GroupName then
GroupName=GroupName:sub(1,-2)
else
GroupName=self:GetName()
end
local Template=_DATABASE.Templates.Groups[GroupName].Template
if Template then
if not Begin then
Begin=0
end
if not End then
End=0
end
for TPointID=Begin+1,#Template.route.points-End do
if Template.route.points[TPointID]then
Points[#Points+1]=UTILS.DeepCopy(Template.route.points[TPointID])
if Randomize then
if not Radius then
Radius=500
end
Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius)
Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius)
end
end
end
return Points
else
error("Template not found for Group : "..GroupName)
end
return nil
end
function GROUP:CalculateThreatLevelA2G()
local MaxThreatLevelA2G=0
for UnitName,UnitData in pairs(self:GetUnits())do
local ThreatUnit=UnitData
local ThreatLevelA2G=ThreatUnit:GetThreatLevel()
if ThreatLevelA2G>MaxThreatLevelA2G then
MaxThreatLevelA2G=ThreatLevelA2G
end
end
return MaxThreatLevelA2G
end
function GROUP:GetThreatLevel()
local threatlevelMax=0
for UnitName,UnitData in pairs(self:GetUnits())do
local ThreatUnit=UnitData
local threatlevel=ThreatUnit:GetThreatLevel()
if threatlevel>threatlevelMax then
threatlevelMax=threatlevel
end
end
return threatlevelMax
end
function GROUP:InAir()
local DCSGroup=self:GetDCSObject()
if DCSGroup then
local DCSUnit=DCSGroup:getUnit(1)
if DCSUnit then
local GroupInAir=DCSGroup:getUnit(1):inAir()
return GroupInAir
end
end
return nil
end
function GROUP:IsAirborne(AllUnits)
local units=self:GetUnits()
if units then
if AllUnits then
for _,_unit in pairs(units)do
local unit=_unit
if unit then
local inair=unit:InAir()
if not inair then
return false
end
end
end
return true
else
for _,_unit in pairs(units)do
local unit=_unit
if unit then
local inair=unit:InAir()
if inair then
return true
end
end
return false
end
end
end
return nil
end
function GROUP:GetDCSDesc(n)
n=n or 1
local unit=self:GetUnit(n)
if unit and unit:IsAlive()~=nil then
local desc=unit:GetDesc()
return desc
end
return nil
end
function GROUP:GetAttribute()
local attribute=GROUP.Attribute.OTHER_UNKNOWN
if self then
local transportplane=self:HasAttribute("Transports")and self:HasAttribute("Planes")
local awacs=self:HasAttribute("AWACS")
local fighter=self:HasAttribute("Fighters")or self:HasAttribute("Interceptors")or self:HasAttribute("Multirole fighters")or(self:HasAttribute("Bombers")and not self:HasAttribute("Strategic bombers"))
local bomber=self:HasAttribute("Strategic bombers")
local tanker=self:HasAttribute("Tankers")
local uav=self:HasAttribute("UAVs")
local transporthelo=self:HasAttribute("Transport helicopters")
local attackhelicopter=self:HasAttribute("Attack helicopters")
local apc=self:HasAttribute("APC")
local truck=self:HasAttribute("Trucks")and self:GetCategory()==Group.Category.GROUND
local infantry=self:HasAttribute("Infantry")
local artillery=self:HasAttribute("Artillery")
local tank=self:HasAttribute("Old Tanks")or self:HasAttribute("Modern Tanks")or self:HasAttribute("Tanks")
local aaa=self:HasAttribute("AAA")and(not self:HasAttribute("SAM elements"))
local ewr=self:HasAttribute("EWR")
local ifv=self:HasAttribute("IFV")
local sam=self:HasAttribute("SAM elements")or self:HasAttribute("Optical Tracker")
local train=self:GetCategory()==Group.Category.TRAIN
local aircraftcarrier=self:HasAttribute("Aircraft Carriers")
local warship=self:HasAttribute("Heavy armed ships")
local armedship=self:HasAttribute("Armed ships")
local unarmedship=self:HasAttribute("Unarmed ships")
if fighter then
attribute=GROUP.Attribute.AIR_FIGHTER
elseif bomber then
attribute=GROUP.Attribute.AIR_BOMBER
elseif awacs then
attribute=GROUP.Attribute.AIR_AWACS
elseif transportplane then
attribute=GROUP.Attribute.AIR_TRANSPORTPLANE
elseif tanker then
attribute=GROUP.Attribute.AIR_TANKER
elseif attackhelicopter then
attribute=GROUP.Attribute.AIR_ATTACKHELO
elseif transporthelo then
attribute=GROUP.Attribute.AIR_TRANSPORTHELO
elseif uav then
attribute=GROUP.Attribute.AIR_UAV
elseif ewr then
attribute=GROUP.Attribute.GROUND_EWR
elseif sam then
attribute=GROUP.Attribute.GROUND_SAM
elseif aaa then
attribute=GROUP.Attribute.GROUND_AAA
elseif artillery then
attribute=GROUP.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=GROUP.Attribute.GROUND_TANK
elseif ifv then
attribute=GROUP.Attribute.GROUND_IFV
elseif apc then
attribute=GROUP.Attribute.GROUND_APC
elseif infantry then
attribute=GROUP.Attribute.GROUND_INFANTRY
elseif truck then
attribute=GROUP.Attribute.GROUND_TRUCK
elseif train then
attribute=GROUP.Attribute.GROUND_TRAIN
elseif aircraftcarrier then
attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER
elseif warship then
attribute=GROUP.Attribute.NAVAL_WARSHIP
elseif armedship then
attribute=GROUP.Attribute.NAVAL_ARMEDSHIP
elseif unarmedship then
attribute=GROUP.Attribute.NAVAL_UNARMEDSHIP
else
if self:IsGround()then
attribute=GROUP.Attribute.GROUND_OTHER
elseif self:IsShip()then
attribute=GROUP.Attribute.NAVAL_OTHER
elseif self:IsAir()then
attribute=GROUP.Attribute.AIR_OTHER
else
attribute=GROUP.Attribute.OTHER_UNKNOWN
end
end
end
return attribute
end
do
function GROUP:RouteRTB(RTBAirbase,Speed)
local DCSGroup=self:GetDCSObject()
if DCSGroup then
if RTBAirbase then
local Speed=Speed or self:GetSpeedMax()*0.8
local coord=self:GetCoordinate()
local PointFrom=coord:WaypointAirTurningPoint(nil,Speed)
local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed,RTBAirbase)
local Points={PointFrom,PointLanding}
local Template=self:GetTemplate()
Template.route.points=Points
self:Respawn(Template,true)
self:Route(Points)
else
self:ClearTasks()
end
end
return self
end
end
function GROUP:OnReSpawn(ReSpawnFunction)
self.ReSpawnFunction=ReSpawnFunction
end
do
function GROUP:HandleEvent(Event,EventFunction,...)
self:EventDispatcher():OnEventForGroup(self:GetName(),EventFunction,self,Event,...)
return self
end
function GROUP:UnHandleEvent(Event)
self:EventDispatcher():RemoveEvent(self,Event)
return self
end
function GROUP:ResetEvents()
self:EventDispatcher():Reset(self)
for UnitID,UnitData in pairs(self:GetUnits()or{})do
UnitData:ResetEvents()
end
return self
end
end
do
function GROUP:GetPlayerNames()
local HasPlayers=false
local PlayerNames={}
local Units=self:GetUnits()
for UnitID,UnitData in pairs(Units or{})do
local Unit=UnitData
local PlayerName=Unit:GetPlayerName()
if PlayerName and PlayerName~=""then
PlayerNames=PlayerNames or{}
table.insert(PlayerNames,PlayerName)
HasPlayers=true
end
end
if HasPlayers==true then
return PlayerNames
end
return nil
end
function GROUP:GetPlayerCount()
local PlayerCount=0
local Units=self:GetUnits()
for UnitID,UnitData in pairs(Units or{})do
local Unit=UnitData
local PlayerName=Unit:GetPlayerName()
if PlayerName and PlayerName~=""then
PlayerCount=PlayerCount+1
end
end
return PlayerCount
end
end
function GROUP:EnableEmission(switch)
local switch=switch or false
local DCSUnit=self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(switch)
end
return self
end
function GROUP:SetCommandInvisible(switch)
return self:CommandSetInvisible(switch)
end
function GROUP:CommandSetInvisible(switch)
if switch==nil then
switch=false
end
local SetInvisible={id='SetInvisible',params={value=switch}}
self:SetCommand(SetInvisible)
return self
end
function GROUP:SetCommandImmortal(switch)
return self:CommandSetImmortal(switch)
end
function GROUP:CommandSetImmortal(switch)
if switch==nil then
switch=false
end
local SetImmortal={id='SetImmortal',params={value=switch}}
self:SetCommand(SetImmortal)
return self
end
function GROUP:GetSkill()
local unit=self:GetUnit(1)
local name=unit:GetName()
local skill=_DATABASE.Templates.Units[name].Template.skill or"Random"
return skill
end
function GROUP:GetHighestThreat()
local units=self:GetUnits()
if units then
local threat=nil;local maxtl=0
for _,_unit in pairs(units or{})do
local unit=_unit
if unit and unit:IsAlive()then
local tl=unit:GetThreatLevel()
if tl>maxtl then
maxtl=tl
threat=unit
end
end
end
return threat,maxtl
end
return nil,nil
end
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...)
self:T("GetCustomCallSign")
local callsign="Ghost 1"
if self:IsAlive()then
local IsPlayer=self:IsPlayer()
local shortcallsign=self:GetCallsign()or"unknown91"
local callsignroot=string.match(shortcallsign,'(%a+)')or"Ghost"
local groupname=self:GetName()
local callnumber=string.match(shortcallsign,"(%d+)$")or"91"
local callnumbermajor=string.char(string.byte(callnumber,1))
local callnumberminor=string.char(string.byte(callnumber,2))
local personalized=false
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
if Keepnumber then
shortcallsign=string.match(groupname,"#(.+)")or"Ghost 111"
else
shortcallsign=string.match(groupname,"#%s*([%a]+)")or"Ghost"
end
personalized=true
elseif IsPlayer and string.find(playername,"|")then
shortcallsign=string.match(playername,"|%s*([%a]+)")or string.match(self:GetPlayerName(),"|%s*([%d]+)")or"Ghost"
personalized=true
end
if personalized then
shortcallsign=string.gsub(shortcallsign,"^%s*","")
shortcallsign=string.gsub(shortcallsign,"%s*$","")
if Keepnumber then
return shortcallsign
elseif ShortCallsign then
callsign=shortcallsign.." "..callnumbermajor
else
callsign=shortcallsign.." "..callnumbermajor.." "..callnumberminor
end
return callsign
end
if ShortCallsign then
callsign=callsignroot.." "..callnumbermajor
else
callsign=callsignroot.." "..callnumbermajor.." "..callnumberminor
end
end
return callsign
end
function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,LastWaypoint)
local speed=ToKIAS==true and UTILS.KnotsToAltKIAS(Speed,Altitude)or Speed
speed=UTILS.KnotsToMps(speed)
local alt=UTILS.FeetToMeters(Altitude)
local delay=Delay or 1
local task=self:TaskRecoveryTanker(CarrierGroup,speed,alt,LastWaypoint)
self:SetTask(task,delay)
local tankertask=self:EnRouteTaskTanker()
self:PushTask(tankertask,delay+2)
return self
end
function GROUP:GetGroupSTN()
local tSTN={}
local units=self:GetUnits()
local gname=self:GetName()
gname=string.gsub(gname,"(#%d+)$","")
local report=REPORT:New()
report:Add("Link16 S/TN Report")
report:Add("Group: "..gname)
report:Add("==================")
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
local STN,VCL,VCN,Lead=unit:GetSTN()
local name=unit:GetName()
tSTN[name]={
STN=STN,
VCL=VCL,
VCN=VCN,
Lead=Lead,
}
local lead=Lead==true and"(*)"or""
report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead))
end
end
report:Add("==================")
local text=report:Text()
return tSTN,text
end
function GROUP:IsSAM()
local issam=false
local units=self:GetUnits()
for _,_unit in pairs(units or{})do
local unit=_unit
if unit:IsSAM()then
issam=true
break
end
end
return issam
end
function GROUP:IsAAA()
local isAAA=false
local units=self:GetUnits()
for _,_unit in pairs(units or{})do
local unit=_unit
if unit:IsAAA()then
isAAA=true
break
end
end
return isAAA
end
UNIT={
ClassName="UNIT",
UnitName=nil,
GroupName=nil,
DCSUnit=nil,
}
function UNIT:Register(UnitName)
local self=BASE:Inherit(self,CONTROLLABLE:New(UnitName))
self.UnitName=UnitName
local unit=Unit.getByName(self.UnitName)
if unit then
local group=unit:getGroup()
if group then
self.GroupName=group:getName()
self.groupId=group:getID()
end
self.DCSUnit=unit
end
self:SetEventPriority(3)
return self
end
function UNIT:Find(DCSUnit)
if DCSUnit then
local UnitName=DCSUnit:getName()
local UnitFound=_DATABASE:FindUnit(UnitName)
return UnitFound
end
return nil
end
function UNIT:FindByName(UnitName)
local UnitFound=_DATABASE:FindUnit(UnitName)
return UnitFound
end
function UNIT:FindByMatching(Pattern)
local GroupFound=nil
for name,group in pairs(_DATABASE.UNITS)do
if string.match(name,Pattern)then
GroupFound=group
break
end
end
return GroupFound
end
function UNIT:FindAllByMatching(Pattern)
local GroupsFound={}
for name,group in pairs(_DATABASE.UNITS)do
if string.match(name,Pattern)then
GroupsFound[#GroupsFound+1]=group
end
end
return GroupsFound
end
function UNIT:Name()
return self.UnitName
end
function UNIT:GetDCSObject()
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=self:GetDCSObject()
if DCSUnit then
local altitude=0
local point=DCSUnit:getPoint()
altitude=point.y
if FromGround then
local land=land.getHeight({x=point.x,y=point.z})or 0
altitude=altitude-land
end
return altitude
end
return nil
end
function UNIT:ReSpawnAt(Coordinate,Heading)
local SpawnGroupTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name()))
local SpawnGroup=self:GetGroup()
if SpawnGroup then
local Vec3=SpawnGroup:GetVec3()
SpawnGroupTemplate.x=Coordinate.x
SpawnGroupTemplate.y=Coordinate.z
for UnitID,UnitData in pairs(SpawnGroup:GetUnits()or{})do
local GroupUnit=UnitData
if GroupUnit:IsAlive()then
local GroupUnitVec3=GroupUnit:GetVec3()
local GroupUnitHeading=GroupUnit:GetHeading()
SpawnGroupTemplate.units[UnitID].alt=GroupUnitVec3.y
SpawnGroupTemplate.units[UnitID].x=GroupUnitVec3.x
SpawnGroupTemplate.units[UnitID].y=GroupUnitVec3.z
SpawnGroupTemplate.units[UnitID].heading=GroupUnitHeading
end
end
end
for UnitTemplateID,UnitTemplateData in pairs(SpawnGroupTemplate.units)do
SpawnGroupTemplate.units[UnitTemplateID].unitId=nil
if UnitTemplateData.name==self:Name()then
SpawnGroupTemplate.units[UnitTemplateID].alt=Coordinate.y
SpawnGroupTemplate.units[UnitTemplateID].x=Coordinate.x
SpawnGroupTemplate.units[UnitTemplateID].y=Coordinate.z
SpawnGroupTemplate.units[UnitTemplateID].heading=Heading
else
local GroupUnit=UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name)
if GroupUnit and GroupUnit:IsAlive()then
local GroupUnitVec3=GroupUnit:GetVec3()
local GroupUnitHeading=GroupUnit:GetHeading()
UnitTemplateData.alt=GroupUnitVec3.y
UnitTemplateData.x=GroupUnitVec3.x
UnitTemplateData.y=GroupUnitVec3.z
UnitTemplateData.heading=GroupUnitHeading
else
if SpawnGroupTemplate.units[UnitTemplateID].name~=self:Name()then
SpawnGroupTemplate.units[UnitTemplateID].delete=true
end
end
end
end
local i=1
while i<=#SpawnGroupTemplate.units do
local UnitTemplateData=SpawnGroupTemplate.units[i]
if UnitTemplateData.delete then
table.remove(SpawnGroupTemplate.units,i)
else
i=i+1
end
end
SpawnGroupTemplate.groupId=nil
_DATABASE:Spawn(SpawnGroupTemplate)
end
function UNIT:IsActive()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitIsActive=DCSUnit:isActive()
return UnitIsActive
end
return nil
end
function UNIT:IsExist()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local exists=DCSUnit:isExist()
return exists
end
return nil
end
function UNIT:IsAlive()
local DCSUnit=self:GetDCSObject()
if DCSUnit and DCSUnit:isExist()then
local UnitIsAlive=DCSUnit:isActive()
return UnitIsAlive
end
return nil
end
function UNIT:IsDead()
return not self:IsAlive()
end
function UNIT:GetCallsign()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitCallSign=DCSUnit:getCallsign()
if UnitCallSign==""then
UnitCallSign=DCSUnit:getName()
end
return UnitCallSign
end
return nil
end
function UNIT:IsPlayer()
local group=self:GetGroup()
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
end
end
return false
end
function UNIT:GetPlayerName()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local PlayerName=DCSUnit:getPlayerName()
return PlayerName
end
return nil
end
function UNIT:IsClient()
if _DATABASE.CLIENTS[self.UnitName]then
return true
end
return false
end
function UNIT:GetClient()
local client=_DATABASE.CLIENTS[self.UnitName]
if client then
return client
end
return nil
end
function UNIT:GetNatoReportingName()
local typename=self:GetTypeName()
return UTILS.GetReportingName(typename)
end
function UNIT:GetNumber()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitNumber=DCSUnit:getNumber()
return UnitNumber
end
return nil
end
function UNIT:GetSpeedMax()
local Desc=self:GetDesc()
if Desc then
local SpeedMax=Desc.speedMax or 0
return SpeedMax*3.6
end
return 0
end
function UNIT:GetRange()
local Desc=self:GetDesc()
if Desc then
local Range=Desc.range
if Range then
Range=Range*1000
else
Range=10000000
end
return Range
end
return nil
end
function UNIT:IsRefuelable()
local refuelable=self:HasAttribute("Refuelable")
local system=nil
local Desc=self:GetDesc()
if Desc and Desc.tankerType then
system=Desc.tankerType
end
return refuelable,system
end
function UNIT:IsTanker()
local tanker=self:HasAttribute("Tankers")
local system=nil
if tanker then
local Desc=self:GetDesc()
if Desc and Desc.tankerType then
system=Desc.tankerType
end
local typename=self:GetTypeName()
if typename=="IL-78M"then
system=1
elseif typename=="KC130"or typename=="KC130J"then
system=1
elseif typename=="KC135BDA"then
system=1
elseif typename=="KC135MPRS"then
system=1
elseif typename=="S-3B Tanker"then
system=1
elseif typename=="KC_10_Extender"then
system=1
elseif typename=="KC_10_Extender_D"then
system=0
end
end
return tanker,system
end
function UNIT:IsAmmoSupply()
local typename=self:GetTypeName()
if typename=="M 818"then
return true
elseif typename=="Ural-375"then
return true
elseif typename=="ZIL-135"then
return true
end
return false
end
function UNIT:IsFuelSupply()
local typename=self:GetTypeName()
if typename=="M978 HEMTT Tanker"then
return true
elseif typename=="ATMZ-5"then
return true
elseif typename=="ATMZ-10"then
return true
elseif typename=="ATZ-5"then
return true
end
return false
end
function UNIT:GetGroup()
local UnitGroup=GROUP:FindByName(self.GroupName)
if UnitGroup then
return UnitGroup
else
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local grp=DCSUnit:getGroup()
if grp then
local UnitGroup=GROUP:FindByName(grp:getName())
return UnitGroup
end
end
end
return nil
end
function UNIT:GetPrefix()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitPrefix=string.match(self.UnitName,".*#"):sub(1,-2)
return UnitPrefix
end
return nil
end
function UNIT:GetAmmo()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitAmmo=DCSUnit:getAmmo()
return UnitAmmo
end
return nil
end
function UNIT:SetUnitInternalCargo(mass)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
trigger.action.setUnitInternalCargo(DCSUnit:getName(),mass)
end
return self
end
function UNIT:GetAmmunition()
local nammo=0
local nshells=0
local nrockets=0
local nmissiles=0
local nbombs=0
local narti=0
local nAPshells=0
local nHEshells=0
local unit=self
local ammotable=unit:GetAmmo()
if ammotable then
local weapons=#ammotable
for w=1,weapons do
local Nammo=ammotable[w]["count"]
local Tammo=ammotable[w]["desc"]["typeName"]
local Category=ammotable[w].desc.category
local MissileCategory=nil
if Category==Weapon.Category.MISSILE then
MissileCategory=ammotable[w].desc.missileCategory
end
if Category==Weapon.Category.SHELL then
nshells=nshells+Nammo
if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass>0 then
narti=narti+Nammo
end
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
nbombs=nbombs+Nammo
elseif Category==Weapon.Category.MISSILE then
if MissileCategory==Weapon.MissileCategory.AAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.BM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.OTHER then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.SAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.CRUISE then
nmissiles=nmissiles+Nammo
end
end
end
end
nammo=nshells+nrockets+nmissiles+nbombs
return nammo,nshells,nrockets,nbombs,nmissiles,narti,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()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitSensors=DCSUnit:getSensors()
return UnitSensors
end
return nil
end
function UNIT:HasSensors(...)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local HasSensors=DCSUnit:hasSensors(unpack(arg))
return HasSensors
end
return nil
end
function UNIT:HasSEAD()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitSEADAttributes=DCSUnit:getDesc().attributes
local HasSEAD=false
if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]==true or
UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]==true or
UnitSEADAttributes["Optical Tracker"]and UnitSEADAttributes["Optical Tracker"]==true
then
HasSEAD=true
end
return HasSEAD
end
return nil
end
function UNIT:GetRadar()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitRadarOn,UnitRadarObject=DCSUnit:getRadar()
return UnitRadarOn,UnitRadarObject
end
return nil,nil
end
function UNIT:GetFuel()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitFuel=DCSUnit:getFuel()
return UnitFuel
end
return nil
end
function UNIT:GetUnits()
local DCSUnit=self:GetDCSObject()
local Units={}
if DCSUnit then
Units[1]=UNIT:Find(DCSUnit)
return Units
end
return nil
end
function UNIT:GetLife()
local DCSUnit=self:GetDCSObject()
if DCSUnit and DCSUnit:isExist()then
local UnitLife=DCSUnit:getLife()
return UnitLife
end
return-1
end
function UNIT:GetLife0()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitLife0=DCSUnit:getLife0()
return UnitLife0
end
return 0
end
function UNIT:GetLifeRelative()
if self and self:IsAlive()then
local life0=self:GetLife0()
local lifeN=self:GetLife()
return lifeN/life0
end
return-1
end
function UNIT:GetDamageRelative()
if self and self:IsAlive()then
return 1-self:GetLifeRelative()
end
return 1
end
function UNIT:GetDrawArgumentValue(AnimationArgument)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local value=DCSUnit:getDrawArgumentValue(AnimationArgument or 0)
return value
end
return 0
end
function UNIT:GetUnitCategory()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
return DCSUnit:getDesc().category
end
return nil
end
function UNIT:GetCategoryName()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local CategoryNames={
[Unit.Category.AIRPLANE]="Airplane",
[Unit.Category.HELICOPTER]="Helicopter",
[Unit.Category.GROUND_UNIT]="Ground Unit",
[Unit.Category.SHIP]="Ship",
[Unit.Category.STRUCTURE]="Structure",
}
local UnitCategory=DCSUnit:getDesc().category
return CategoryNames[UnitCategory]
end
return nil
end
function UNIT:GetThreatLevel()
local ThreatLevel=0
local ThreatText=""
local Descriptor=self:GetDesc()
if Descriptor then
local Attributes=Descriptor.attributes
if self:IsGround()then
local ThreatLevels={
[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
elseif Attributes["SR SAM"]and
not Attributes["IR Guided SAM"]then
ThreatLevel=8
elseif(Attributes["SR SAM"]or Attributes["MANPADS"])and
Attributes["IR Guided SAM"]then
ThreatLevel=7
elseif Attributes["AAA"]then
ThreatLevel=6
elseif Attributes["Modern Tanks"]then
ThreatLevel=5
elseif(Attributes["Tanks"]or Attributes["IFV"])and
Attributes["ATGM"]then
ThreatLevel=4
elseif(Attributes["Tanks"]or Attributes["IFV"])and
not Attributes["ATGM"]then
ThreatLevel=3
elseif Attributes["Old Tanks"]or Attributes["APC"]or Attributes["Artillery"]then
ThreatLevel=2
elseif Attributes["Infantry"]or Attributes["EWR"]then
ThreatLevel=1
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
if self:IsAir()then
local ThreatLevels={
[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
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
if self:IsShip()then
local ThreatLevels={
[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
end
ThreatText=ThreatLevels[ThreatLevel+1]
end
end
return ThreatLevel,ThreatText
end
function UNIT:Explode(power,delay)
power=power or 100
local DCSUnit=self:GetDCSObject()
if DCSUnit then
if delay and delay>0 then
SCHEDULER:New(nil,self.Explode,{self,power},delay)
else
self:GetCoordinate():Explosion(power)
end
return self
end
return nil
end
function UNIT:OtherUnitInRadius(AwaitUnit,Radius)
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
return true
else
return false
end
end
return nil
end
function UNIT:IsFriendly(FriendlyCoalition)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitCoalition=DCSUnit:getCoalition()
local IsFriendlyResult=(UnitCoalition==FriendlyCoalition)
return IsFriendlyResult
end
return nil
end
function UNIT:IsShip()
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitDescriptor=DCSUnit:getDesc()
local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP)
return IsShipResult
end
return nil
end
function UNIT:InAir(NoHeloCheck)
local DCSUnit=self:GetDCSObject()
if DCSUnit then
local UnitInAir=DCSUnit:inAir()
local UnitCategory=DCSUnit:getDesc().category
if UnitInAir==true and UnitCategory==Unit.Category.HELICOPTER and(not NoHeloCheck)then
local VelocityVec3=DCSUnit:getVelocity()
local Velocity=UTILS.VecNorm(VelocityVec3)
local Coordinate=DCSUnit:getPoint()
local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z})
local Height=Coordinate.y-LandHeight
if Velocity<1 and Height<=60 then
UnitInAir=false
end
end
return UnitInAir
end
return nil
end
do
function UNIT:HandleEvent(EventID,EventFunction)
self:EventDispatcher():OnEventForUnit(self:GetName(),EventFunction,self,EventID)
return self
end
function UNIT:UnHandleEvent(EventID)
self:EventDispatcher():RemoveEvent(self,EventID)
return self
end
function UNIT:ResetEvents()
self:EventDispatcher():Reset(self)
return self
end
end
do
function UNIT:IsDetected(TargetUnit)
local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=self:IsTargetDetected(TargetUnit:GetDCSObject())
return TargetIsDetected
end
function UNIT:IsLOS(TargetUnit)
local IsLOS=self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3())
return IsLOS
end
function UNIT:KnowUnit(TargetUnit,TypeKnown,DistanceKnown)
if TypeKnown~=false then
TypeKnown=true
end
if DistanceKnown~=false then
DistanceKnown=true
end
local DCSControllable=self:GetDCSObject()
if DCSControllable then
local Controller=DCSControllable:getController()
if Controller then
local object=TargetUnit:GetDCSObject()
if object then
self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s",self:GetName(),TargetUnit:GetName(),tostring(TypeKnown),tostring(DistanceKnown)))
Controller:knowTarget(object,TypeKnown,DistanceKnown)
end
end
end
end
end
function UNIT:GetTemplate()
local group=self:GetGroup()
local name=self:GetName()
if group then
local template=group:GetTemplate()
if template then
for _,unit in pairs(template.units)do
if unit.name==name then
return UTILS.DeepCopy(unit)
end
end
end
end
return nil
end
function UNIT:GetTemplatePayload()
local unit=self:GetTemplate()
if unit then
return unit.payload
end
return nil
end
function UNIT:GetTemplatePylons()
local payload=self:GetTemplatePayload()
if payload then
return payload.pylons
end
return nil
end
function UNIT:GetTemplateFuel()
local payload=self:GetTemplatePayload()
if payload then
return payload.fuel
end
return nil
end
function UNIT:EnableEmission(switch)
local switch=switch or false
local DCSUnit=self:GetDCSObject()
if DCSUnit then
DCSUnit:enableEmission(switch)
end
return self
end
function UNIT:GetSkill()
local name=self.UnitName
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()
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
elseif template.AddPropAircraft.SADL_TN then
STN=template.AddPropAircraft.SADL_TN
end
VCN=template.AddPropAircraft.VoiceCallsignNumber
VCL=template.AddPropAircraft.VoiceCallsignLabel
end
if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then
FGL=template.datalinks.Link16.settings.flightLead
end
if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then
FGL=template.datalinks.SADL.settings.flightLead
end
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
CLIENT={
ClassName="CLIENT",
ClientName=nil,
ClientAlive=false,
ClientTransport=false,
ClientBriefingShown=false,
_Menus={},
_Tasks={},
Messages={},
Players={},
}
function CLIENT:Find(DCSUnit,Error)
local ClientName=DCSUnit:getName()
local ClientFound=_DATABASE:FindClient(ClientName)
if ClientFound then
ClientFound:F(ClientName)
return ClientFound
end
if not Error then
error("CLIENT not found for: "..ClientName)
end
end
function CLIENT:FindByPlayerName(Name)
local foundclient=nil
_DATABASE:ForEachClient(
function(client)
if client:GetPlayerName()==Name then
foundclient=client
end
end
)
return foundclient
end
function CLIENT:FindByName(ClientName,ClientBriefing,Error)
local ClientFound=_DATABASE:FindClient(ClientName)
if ClientFound then
ClientFound:F({ClientName,ClientBriefing})
ClientFound:AddBriefing(ClientBriefing)
ClientFound.MessageSwitch=true
return ClientFound
end
if not Error then
error("CLIENT not found for: "..ClientName)
end
end
function CLIENT:Register(ClientName)
local self=BASE:Inherit(self,UNIT:Register(ClientName))
self.ClientName=ClientName
self.MessageSwitch=true
self.ClientAlive2=false
return self
end
function CLIENT:Transport()
self.ClientTransport=true
return self
end
function CLIENT:AddBriefing(ClientBriefing)
self.ClientBriefing=ClientBriefing
self.ClientBriefingShown=false
return self
end
function CLIENT:AddPlayer(PlayerName)
table.insert(self.Players,PlayerName)
return self
end
function CLIENT:CountPlayers()
return#self.Players or 0
end
function CLIENT:GetPlayers()
return self.Players
end
function CLIENT:GetPlayer()
if#self.Players>0 then
return self.Players[1]
end
return nil
end
function CLIENT:RemovePlayer(PlayerName)
for i,playername in pairs(self.Players)do
if PlayerName==playername then
table.remove(self.Players,i)
break
end
end
return self
end
function CLIENT:RemovePlayers()
self.Players={}
return self
end
function CLIENT:ShowBriefing()
if not self.ClientBriefingShown then
self.ClientBriefingShown=true
local Briefing=""
if self.ClientBriefing and self.ClientBriefing~=""then
Briefing=Briefing..self.ClientBriefing
self:Message(Briefing,60,"Briefing")
end
end
return self
end
function CLIENT:ShowMissionBriefing(MissionBriefing)
self:F({self.ClientName})
if MissionBriefing then
self:Message(MissionBriefing,60,"Mission Briefing")
end
return self
end
function CLIENT:Reset(ClientName)
self:F()
self._Menus={}
end
function CLIENT:IsMultiSeated()
self:F(self.ClientName)
local ClientMultiSeatedTypes={
["Mi-8MT"]="Mi-8MT",
["UH-1H"]="UH-1H",
["P-51B"]="P-51B"
}
if self:IsAlive()then
local ClientTypeName=self:GetClientGroupUnit():GetTypeName()
if ClientMultiSeatedTypes[ClientTypeName]then
return true
end
end
return false
end
function CLIENT:Alive(CallBackFunction,...)
self:F()
self.ClientCallBack=CallBackFunction
self.ClientParameters=arg
self.AliveCheckScheduler=SCHEDULER:New(self,self._AliveCheckScheduler,{"Client Alive "..self.ClientName},0.1,5,0.5)
self.AliveCheckScheduler:NoTrace()
return self
end
function CLIENT:_AliveCheckScheduler(SchedulerName)
self:T2({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack})
if self:IsAlive()then
if self.ClientAlive2==false then
self:ShowBriefing()
if self.ClientCallBack then
self:T("Calling Callback function")
self.ClientCallBack(self,unpack(self.ClientParameters))
end
self.ClientAlive2=true
end
else
if self.ClientAlive2==true then
self.ClientAlive2=false
end
end
return true
end
function CLIENT:GetDCSGroup()
self:F3()
local ClientUnit=Unit.getByName(self.ClientName)
local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE)}
for CoalitionId,CoalitionData in pairs(CoalitionsData)do
self:T3({"CoalitionData:",CoalitionData})
for UnitId,UnitData in pairs(CoalitionData)do
self:T3({"UnitData:",UnitData})
if UnitData and UnitData:isExist()then
if ClientUnit then
local ClientGroup=ClientUnit:getGroup()
if ClientGroup then
self:T3("ClientGroup = "..self.ClientName)
if ClientGroup:isExist()and UnitData:getGroup():isExist()then
if ClientGroup:getID()==UnitData:getGroup():getID()then
self:T3("Normal logic")
self:T3(self.ClientName.." : group found!")
self.ClientGroupID=ClientGroup:getID()
self.ClientGroupName=ClientGroup:getName()
return ClientGroup
end
else
self:T3("Bug 1.5 logic")
local ClientGroupTemplate=_DATABASE.Templates.Units[self.ClientName].GroupTemplate
self.ClientGroupID=ClientGroupTemplate.groupId
self.ClientGroupName=_DATABASE.Templates.Units[self.ClientName].GroupName
self:T3(self.ClientName.." : group found in bug 1.5 resolvement logic!")
return ClientGroup
end
end
else
end
end
end
end
if ClientUnit then
local ClientGroup=ClientUnit:getGroup()
if ClientGroup then
self:T3("ClientGroup = "..self.ClientName)
if ClientGroup:isExist()then
self:T3("Normal logic")
self:T3(self.ClientName.." : group found!")
return ClientGroup
end
end
end
self.ClientGroupID=nil
self.ClientGroupName=nil
return nil
end
function CLIENT:GetClientGroupID()
self:GetDCSGroup()
return self.ClientGroupID
end
function CLIENT:GetClientGroupName()
self:GetDCSGroup()
return self.ClientGroupName
end
function CLIENT:GetClientGroupUnit()
self:F2()
local ClientDCSUnit=Unit.getByName(self.ClientName)
self:T(self.ClientDCSUnit)
if ClientDCSUnit and ClientDCSUnit:isExist()then
local ClientUnit=_DATABASE:FindUnit(self.ClientName)
return ClientUnit
end
return nil
end
function CLIENT:GetClientGroupDCSUnit()
self:F2()
local ClientDCSUnit=Unit.getByName(self.ClientName)
if ClientDCSUnit and ClientDCSUnit:isExist()then
self:T2(ClientDCSUnit)
return ClientDCSUnit
end
end
function CLIENT:IsTransport()
self:F()
return self.ClientTransport
end
function CLIENT:ShowCargo()
self:F()
local CargoMsg=""
for CargoName,Cargo in pairs(CARGOS)do
if self==Cargo:IsLoadedInClient()then
CargoMsg=CargoMsg..Cargo.CargoName.." Type:"..Cargo.CargoType.." Weight: "..Cargo.CargoWeight.."\n"
end
end
if CargoMsg==""then
CargoMsg="empty"
end
self:Message(CargoMsg,15,"Co-Pilot: Cargo Status",30)
end
function CLIENT:Message(Message,MessageDuration,MessageCategory,MessageInterval,MessageID)
self:F({Message,MessageDuration,MessageCategory,MessageInterval})
if self.MessageSwitch==true then
if MessageCategory==nil then
MessageCategory="Messages"
end
if MessageID~=nil then
if self.Messages[MessageID]==nil then
self.Messages[MessageID]={}
self.Messages[MessageID].MessageId=MessageID
self.Messages[MessageID].MessageTime=timer.getTime()
self.Messages[MessageID].MessageDuration=MessageDuration
if MessageInterval==nil then
self.Messages[MessageID].MessageInterval=600
else
self.Messages[MessageID].MessageInterval=MessageInterval
end
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
else
if self:GetClientGroupDCSUnit()and not self:GetClientGroupDCSUnit():inAir()then
if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+10 then
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
self.Messages[MessageID].MessageTime=timer.getTime()
end
else
if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+self.Messages[MessageID].MessageInterval then
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
self.Messages[MessageID].MessageTime=timer.getTime()
end
end
end
else
MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self)
end
end
end
function CLIENT:GetUCID()
local PID=NET.GetPlayerIDByName(nil,self:GetPlayerName())
return net.get_player_info(tonumber(PID),'ucid')
end
function CLIENT:GetPlayerInfo(Attribute)
local PID=NET.GetPlayerIDByName(nil,self:GetPlayerName())
if PID then
return net.get_player_info(tonumber(PID),Attribute)
else
return nil
end
end
STATIC={
ClassName="STATIC",
}
function STATIC:Register(StaticName)
local self=BASE:Inherit(self,POSITIONABLE:New(StaticName))
self.StaticName=StaticName
local DCSStatic=StaticObject.getByName(self.StaticName)
if DCSStatic then
local Life0=DCSStatic:getLife()or 1
self.Life0=Life0
else
self:E(string.format("Static object %s does not exist!",tostring(self.StaticName)))
end
return self
end
function STATIC:GetLife0()
return self.Life0 or 1
end
function STATIC:GetLife()
local DCSStatic=StaticObject.getByName(self.StaticName)
if DCSStatic then
return DCSStatic:getLife()or 1
end
return nil
end
function STATIC:Find(DCSStatic)
local StaticName=DCSStatic:getName()
local StaticFound=_DATABASE:FindStatic(StaticName)
return StaticFound
end
function STATIC:FindByName(StaticName,RaiseError)
local StaticFound=_DATABASE:FindStatic(StaticName)
self.StaticName=StaticName
if StaticFound then
return StaticFound
end
if RaiseError==nil or RaiseError==true then
error("STATIC not found for: "..StaticName)
end
return nil
end
function STATIC:Destroy(GenerateEvent)
self:F2(self.ObjectName)
local DCSObject=self:GetDCSObject()
if DCSObject then
local StaticName=DCSObject:getName()
self:F({StaticName=StaticName})
if GenerateEvent and GenerateEvent==true then
if self:IsAir()then
self:CreateEventCrash(timer.getTime(),DCSObject)
else
self:CreateEventDead(timer.getTime(),DCSObject)
end
elseif GenerateEvent==false then
else
self:CreateEventRemoveUnit(timer.getTime(),DCSObject)
end
DCSObject:destroy()
return true
end
return nil
end
function STATIC:GetDCSObject()
local DCSStatic=StaticObject.getByName(self.StaticName)
if DCSStatic then
return DCSStatic
end
return nil
end
function STATIC:GetUnits()
self:F2({self.StaticName})
local DCSStatic=self:GetDCSObject()
local Statics={}
if DCSStatic then
Statics[1]=STATIC:Find(DCSStatic)
self:T3(Statics)
return Statics
end
return nil
end
function STATIC:GetThreatLevel()
return 1,"Static"
end
function STATIC:SpawnAt(Coordinate,Heading,Delay)
Heading=Heading or 0
if Delay and Delay>0 then
SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay)
else
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName)
SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName)
end
return self
end
function STATIC:ReSpawn(CountryID,Delay)
if Delay and Delay>0 then
SCHEDULER:New(nil,self.ReSpawn,{self,CountryID},Delay)
else
CountryID=CountryID or self:GetCountry()
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID)
SpawnStatic:Spawn(nil,self.StaticName)
end
return self
end
function STATIC:ReSpawnAt(Coordinate,Heading,Delay)
if Delay and Delay>0 then
SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay)
else
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry())
SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName)
end
return self
end
function STATIC:FindByMatching(Pattern)
local GroupFound=nil
for name,static in pairs(_DATABASE.STATICS)do
if string.match(name,Pattern)then
GroupFound=static
break
end
end
return GroupFound
end
function STATIC:FindAllByMatching(Pattern)
local GroupsFound={}
for name,static in pairs(_DATABASE.STATICS)do
if string.match(name,Pattern)then
GroupsFound[#GroupsFound+1]=static
end
end
return GroupsFound
end
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={
[Airbase.Category.AIRDROME]="Airdrome",
[Airbase.Category.HELIPAD]="Helipad",
[Airbase.Category.SHIP]="Ship",
},
activerwyno=nil,
}
AIRBASE.Caucasus={
["Anapa_Vityazevo"]="Anapa-Vityazevo",
["Batumi"]="Batumi",
["Beslan"]="Beslan",
["Gelendzhik"]="Gelendzhik",
["Gudauta"]="Gudauta",
["Kobuleti"]="Kobuleti",
["Krasnodar_Center"]="Krasnodar-Center",
["Krasnodar_Pashkovsky"]="Krasnodar-Pashkovsky",
["Krymsk"]="Krymsk",
["Kutaisi"]="Kutaisi",
["Maykop_Khanskaya"]="Maykop-Khanskaya",
["Mineralnye_Vody"]="Mineralnye Vody",
["Mozdok"]="Mozdok",
["Nalchik"]="Nalchik",
["Novorossiysk"]="Novorossiysk",
["Senaki_Kolkhi"]="Senaki-Kolkhi",
["Sochi_Adler"]="Sochi-Adler",
["Soganlug"]="Soganlug",
["Sukhumi_Babushara"]="Sukhumi-Babushara",
["Tbilisi_Lochini"]="Tbilisi-Lochini",
["Vaziani"]="Vaziani",
}
AIRBASE.Nevada={
["Beatty"]="Beatty",
["Boulder_City"]="Boulder City",
["Creech"]="Creech",
["Echo_Bay"]="Echo Bay",
["Groom_Lake"]="Groom Lake",
["Henderson_Executive"]="Henderson Executive",
["Jean"]="Jean",
["Laughlin"]="Laughlin",
["Lincoln_County"]="Lincoln County",
["McCarran_International"]="McCarran International",
["Mesquite"]="Mesquite",
["Mina"]="Mina",
["Nellis"]="Nellis",
["North_Las_Vegas"]="North Las Vegas",
["Pahute_Mesa"]="Pahute Mesa",
["Tonopah"]="Tonopah",
["Tonopah_Test_Range"]="Tonopah Test Range",
}
AIRBASE.Normandy={
["Abbeville_Drucat"]="Abbeville Drucat",
["Amiens_Glisy"]="Amiens-Glisy",
["Argentan"]="Argentan",
["Avranches_Le_Val_Saint_Pere"]="Avranches Le Val-Saint-Pere",
["Azeville"]="Azeville",
["Barville"]="Barville",
["Bazenville"]="Bazenville",
["Beaumont_le_Roger"]="Beaumont-le-Roger",
["Beauvais_Tille"]="Beauvais-Tille",
["Beny_sur_Mer"]="Beny-sur-Mer",
["Bernay_Saint_Martin"]="Bernay Saint Martin",
["Beuzeville"]="Beuzeville",
["Biggin_Hill"]="Biggin Hill",
["Biniville"]="Biniville",
["Broglie"]="Broglie",
["Brucheville"]="Brucheville",
["Cardonville"]="Cardonville",
["Carpiquet"]="Carpiquet",
["Chailey"]="Chailey",
["Chippelle"]="Chippelle",
["Conches"]="Conches",
["Cormeilles_en_Vexin"]="Cormeilles-en-Vexin",
["Creil"]="Creil",
["Cretteville"]="Cretteville",
["Cricqueville_en_Bessin"]="Cricqueville-en-Bessin",
["Deanland"]="Deanland",
["Deauville"]="Deauville",
["Detling"]="Detling",
["Deux_Jumeaux"]="Deux Jumeaux",
["Dinan_Trelivan"]="Dinan-Trelivan",
["Dunkirk_Mardyck"]="Dunkirk-Mardyck",
["Essay"]="Essay",
["Evreux"]="Evreux",
["Farnborough"]="Farnborough",
["Fecamp_Benouville"]="Fecamp-Benouville",
["Flers"]="Flers",
["Ford"]="Ford",
["Friston"]="Friston",
["Funtington"]="Funtington",
["Goulet"]="Goulet",
["Gravesend"]="Gravesend",
["Guyancourt"]="Guyancourt",
["Hauterive"]="Hauterive",
["Heathrow"]="Heathrow",
["High_Halden"]="High Halden",
["Kenley"]="Kenley",
["Lantheuil"]="Lantheuil",
["Le_Molay"]="Le Molay",
["Lessay"]="Lessay",
["Lignerolles"]="Lignerolles",
["Longues_sur_Mer"]="Longues-sur-Mer",
["Lonrai"]="Lonrai",
["Lymington"]="Lymington",
["Lympne"]="Lympne",
["Manston"]="Manston",
["Maupertus"]="Maupertus",
["Meautis"]="Meautis",
["Merville_Calonne"]="Merville Calonne",
["Needs_Oar_Point"]="Needs Oar Point",
["Odiham"]="Odiham",
["Orly"]="Orly",
["Picauville"]="Picauville",
["Poix"]="Poix",
["Ronai"]="Ronai",
["Rouen_Boos"]="Rouen-Boos",
["Rucqueville"]="Rucqueville",
["Saint_Andre_de_lEure"]="Saint-Andre-de-lEure",
["Saint_Aubin"]="Saint-Aubin",
["Saint_Omer_Wizernes"]="Saint-Omer Wizernes",
["Saint_Pierre_du_Mont"]="Saint Pierre du Mont",
["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer",
["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer",
["Sommervieu"]="Sommervieu",
["Stoney_Cross"]="Stoney Cross",
["Tangmere"]="Tangmere",
["Triqueville"]="Triqueville",
["Villacoublay"]="Villacoublay",
["Vrigny"]="Vrigny",
["West_Malling"]="West Malling",
["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",
["Bandar_Abbas_Intl"]="Bandar Abbas Intl",
["Bandar_Lengeh"]="Bandar Lengeh",
["Bandar_e_Jask"]="Bandar-e-Jask",
["Dubai_Intl"]="Dubai Intl",
["Fujairah_Intl"]="Fujairah Intl",
["Havadarya"]="Havadarya",
["Jiroft"]="Jiroft",
["Kerman"]="Kerman",
["Khasab"]="Khasab",
["Kish_Intl"]="Kish Intl",
["Lar"]="Lar",
["Lavan_Island"]="Lavan Island",
["Liwa_AFB"]="Liwa AFB",
["Qeshm_Island"]="Qeshm Island",
["Quasoura_airport"]="Quasoura_airport",
["Ras_Al_Khaimah_Intl"]="Ras Al Khaimah Intl",
["Sas_Al_Nakheel"]="Sas Al Nakheel",
["Sharjah_Intl"]="Sharjah Intl",
["Shiraz_Intl"]="Shiraz Intl",
["Sir_Abu_Nuayr"]="Sir Abu Nuayr",
["Sirri_Island"]="Sirri Island",
["Tunb_Island_AFB"]="Tunb Island AFB",
["Tunb_Kochak"]="Tunb Kochak",
}
AIRBASE.TheChannel={
["Abbeville_Drucat"]="Abbeville Drucat",
["Biggin_Hill"]="Biggin Hill",
["Detling"]="Detling",
["Dunkirk_Mardyck"]="Dunkirk Mardyck",
["Eastchurch"]="Eastchurch",
["Hawkinge"]="Hawkinge",
["Headcorn"]="Headcorn",
["High_Halden"]="High Halden",
["Lympne"]="Lympne",
["Manston"]="Manston",
["Merville_Calonne"]="Merville Calonne",
["Saint_Omer_Longuenesse"]="Saint Omer Longuenesse",
}
AIRBASE.Syria={
["Abu_al_Duhur"]="Abu al-Duhur",
["Adana_Sakirpasa"]="Adana Sakirpasa",
["Akrotiri"]="Akrotiri",
["Al_Dumayr"]="Al-Dumayr",
["Al_Qusayr"]="Al Qusayr",
["Aleppo"]="Aleppo",
["Amman"]="Amman",
["An_Nasiriyah"]="An Nasiriyah",
["At_Tanf"]="At Tanf",
["Bassel_Al_Assad"]="Bassel Al-Assad",
["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri",
["Damascus"]="Damascus",
["Deir_ez_Zor"]="Deir ez-Zor",
["Ercan"]="Ercan",
["Eyn_Shemer"]="Eyn Shemer",
["Gaziantep"]="Gaziantep",
["Gazipasa"]="Gazipasa",
["Gecitkale"]="Gecitkale",
["H"]="H",
["H3"]="H3",
["H3_Northwest"]="H3 Northwest",
["H3_Southwest"]="H3 Southwest",
["H4"]="H4",
["Haifa"]="Haifa",
["Hama"]="Hama",
["Hatay"]="Hatay",
["Herzliya"]="Herzliya",
["Incirlik"]="Incirlik",
["Jirah"]="Jirah",
["Khalkhalah"]="Khalkhalah",
["Kharab_Ishk"]="Kharab Ishk",
["King_Abdullah_II"]="King Abdullah II",
["King_Hussein_Air_College"]="King Hussein Air College",
["Kingsfield"]="Kingsfield",
["Kiryat_Shmona"]="Kiryat Shmona",
["Kuweires"]="Kuweires",
["Lakatamia"]="Lakatamia",
["Larnaca"]="Larnaca",
["Marj_Ruhayyil"]="Marj Ruhayyil",
["Marj_as_Sultan_North"]="Marj as Sultan North",
["Marj_as_Sultan_South"]="Marj as Sultan South",
["Megiddo"]="Megiddo",
["Mezzeh"]="Mezzeh",
["Minakh"]="Minakh",
["Muwaffaq_Salti"]="Muwaffaq Salti",
["Naqoura"]="Naqoura",
["Nicosia"]="Nicosia",
["Palmyra"]="Palmyra",
["Paphos"]="Paphos",
["Pinarbashi"]="Pinarbashi",
["Prince_Hassan"]="Prince Hassan",
["Qabr_as_Sitt"]="Qabr as Sitt",
["Ramat_David"]="Ramat David",
["Rayak"]="Rayak",
["Rene_Mouawad"]="Rene Mouawad",
["Rosh_Pina"]="Rosh Pina",
["Ruwayshid"]="Ruwayshid",
["Sanliurfa"]="Sanliurfa",
["Sayqal"]="Sayqal",
["Shayrat"]="Shayrat",
["Tabqa"]="Tabqa",
["Taftanaz"]="Taftanaz",
["Tal_Siman"]="Tal Siman",
["Tha_lah"]="Tha'lah",
["Tiyas"]="Tiyas",
["Wujah_Al_Hajar"]="Wujah Al Hajar",
["Ben_Gurion"]="Ben Gurion",
["Hatzor"]="Hatzor",
["Palmashim"]="Palmashim",
["Tel_Nof"]="Tel Nof",
}
AIRBASE.MarianaIslands={
["Andersen_AFB"]="Andersen AFB",
["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl",
["North_West_Field"]="North West Field",
["Olf_Orote"]="Olf Orote",
["Pagan_Airstrip"]="Pagan Airstrip",
["Rota_Intl"]="Rota Intl",
["Saipan_Intl"]="Saipan Intl",
["Tinian_Intl"]="Tinian Intl",
}
AIRBASE.SouthAtlantic={
["Almirante_Schroeders"]="Almirante Schroeders",
["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena",
["Cullen"]="Cullen",
["El_Calafate"]="El Calafate",
["Franco_Bianco"]="Franco Bianco",
["Gobernador_Gregores"]="Gobernador Gregores",
["Goose_Green"]="Goose Green",
["Gull_Point"]="Gull Point",
["Hipico_Flying_Club"]="Hipico Flying Club",
["Mount_Pleasant"]="Mount Pleasant",
["O_Higgins"]="O'Higgins",
["Pampa_Guanaco"]="Pampa Guanaco",
["Port_Stanley"]="Port Stanley",
["Porvenir"]="Porvenir",
["Puerto_Natales"]="Puerto Natales",
["Puerto_Santa_Cruz"]="Puerto Santa Cruz",
["Puerto_Williams"]="Puerto Williams",
["Punta_Arenas"]="Punta Arenas",
["Rio_Chico"]="Rio Chico",
["Rio_Gallegos"]="Rio Gallegos",
["Rio_Grande"]="Rio Grande",
["Rio_Turbio"]="Rio Turbio",
["San_Carlos_FOB"]="San Carlos FOB",
["San_Julian"]="San Julian",
["Tolhuin"]="Tolhuin",
["Ushuaia"]="Ushuaia",
["Ushuaia_Helo_Port"]="Ushuaia Helo Port",
}
AIRBASE.Sinai={
["Abu_Rudeis"]="Abu Rudeis",
["Abu_Suwayr"]="Abu Suwayr",
["Al_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",
["Borj_El_Arab_International_Airport"]="Borj El Arab International Airport",
["Cairo_International_Airport"]="Cairo International Airport",
["Cairo_West"]="Cairo West",
["Difarsuwar_Airfield"]="Difarsuwar Airfield",
["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",
["Kibrit_Air_Base"]="Kibrit Air Base",
["Kom_Awshim"]="Kom Awshim",
["Melez"]="Melez",
["Nevatim"]="Nevatim",
["Ovda"]="Ovda",
["Palmachim"]="Palmachim",
["Quwaysina"]="Quwaysina",
["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",
["Tel_Nof"]="Tel Nof",
["Wadi_Abu_Rish"]="Wadi Abu Rish",
["Wadi_al_Jandali"]="Wadi al Jandali",
}
AIRBASE.Kola={
["Banak"]="Banak",
["Bodo"]="Bodo",
["Ivalo"]="Ivalo",
["Jokkmokk"]="Jokkmokk",
["Kalixfors"]="Kalixfors",
["Kallax"]="Kallax",
["Kemi_Tornio"]="Kemi Tornio",
["Kirkenes"]="Kirkenes",
["Kiruna"]="Kiruna",
["Kuusamo"]="Kuusamo",
["Monchegorsk"]="Monchegorsk",
["Murmansk_International"]="Murmansk International",
["Olenya"]="Olenya",
["Rovaniemi"]="Rovaniemi",
["Severomorsk_1"]="Severomorsk-1",
["Severomorsk_3"]="Severomorsk-3",
["Vidsel"]="Vidsel",
["Vuojarvi"]="Vuojarvi",
["Andoya"]="Andoya",
["Alakourtti"]="Alakourtti",
["Kittila"]="Kittila",
["Bardufoss"]="Bardufoss",
["Alta"]="Alta",
["Sodankyla"]="Sodankyla",
["Enontekio"]="Enontekio",
["Evenes"]="Evenes",
["Hosio"]="Hosio",
}
AIRBASE.Afghanistan={
["Bagram"]="Bagram",
["Bamyan"]="Bamyan",
["Bost"]="Bost",
["Camp_Bastion"]="Camp Bastion",
["Camp_Bastion_Heliport"]="Camp Bastion Heliport",
["Chaghcharan"]="Chaghcharan",
["Dwyer"]="Dwyer",
["Farah"]="Farah",
["Gardez"]="Gardez",
["Ghazni_Heliport"]="Ghazni Heliport",
["Herat"]="Herat",
["Jalalabad"]="Jalalabad",
["Kabul"]="Kabul",
["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",
["Sharana"]="Sharana",
["Shindand"]="Shindand",
["Shindand_Heliport"]="Shindand Heliport",
["Tarinkot"]="Tarinkot",
["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_Salam_Airbase"]="Al-Salam Airbase",
["Balad_Airbase"]="Balad Airbase",
["Kirkuk_International_Airport"]="Kirkuk International Airport",
["Bashur_Airport"]="Bashur Airport",
["Al_Taquddum_Airport"]="Al-Taquddum Airport",
["Qayyarah_Airfield_West"]="Qayyarah Airfield West",
["K1_Base"]="K1 Base",
}
AIRBASE.GermanyCW={
["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",
["Braunschweig"]="Braunschweig",
["Bremen"]="Bremen",
["Briest"]="Briest",
["Buechel"]="Buchel",
["Bueckeburg"]="Buckeburg",
["Celle"]="Celle",
["Cochstedt"]="Cochstedt",
["Damgarten"]="Damgarten",
["Dedelow"]="Dedelow",
["Dessau"]="Dessau",
["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",
["Herrenteich"]="Herrenteich",
["Hildesheim"]="Hildesheim",
["Hockenheim"]="Hockenheim",
["Holzdorf"]="Holzdorf",
["Kammermark"]="Kammermark",
["Koethen"]="Kothen",
["Laage"]="Laage",
["Langenselbold"]="Langenselbold",
["Laerz"]="Larz",
["Leipzig_Halle"]="Leipzig Halle",
["Leipzig_Mockau"]="Leipzig Mockau",
["Luebeck"]="Lubeck",
["Lueneburg"]="Luneburg",
["Mahlwinkel"]="Mahlwinkel",
["Mendig"]="Mendig",
["Merseburg"]="Merseburg",
["Neubrandenburg"]="Neubrandenburg",
["Neuruppin"]="Neuruppin",
["Northeim"]="Northeim",
["Ober_Moerlen"]="Ober-Morlen",
["Obermehler_Schlotheim"]="Obermehler Schlotheim",
["Parchim"]="Parchim",
["Peenemuende"]="Peenemunde",
["Pferdsfeld"]="Pferdsfeld",
["Pinnow"]="Pinnow",
["Pottschutthoehe"]="Pottschutthohe",
["Ramstein"]="Ramstein",
["Rinteln"]="Rinteln",
["Schoenefeld"]="Schonefeld",
["Schweinfurt"]="Schweinfurt",
["Sembach"]="Sembach",
["Spangdahlem"]="Spangdahlem",
["Sperenberg"]="Sperenberg",
["Stendal"]="Stendal",
["Tegel"]="Tegel",
["Tempelhof"]="Tempelhof",
["Templin"]="Templin",
["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",
["Zweibruecken"]="Zweibrucken",
}
AIRBASE.TerminalType={
Runway=16,
HelicopterOnly=40,
Shelter=68,
OpenMed=72,
SmallSizeFighter=100,
OpenBig=104,
OpenMedOrBig=176,
HelicopterUsable=216,
FighterAircraft=244,
FighterAircraftSmall=344,
}
AIRBASE.SpotStatus={
FREE="Free",
OCCUPIED="Occupied",
RESERVED="Reserved",
}
function AIRBASE:Register(AirbaseName)
local self=BASE:Inherit(self,POSITIONABLE:New(AirbaseName))
self.AirbaseName=AirbaseName
self.AirbaseID=self:GetID(true)
self.descriptors=self:GetDesc()
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true
elseif self.category==Airbase.Category.HELIPAD 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
self.isHelipad=true
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
else
self:E("ERROR: Unknown airbase category!")
end
self:_InitRunways()
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)
if vec2 then
if self.isShip then
local unit=UNIT:FindByName(AirbaseName)
if unit then
self.AirbaseZone=ZONE_UNIT:New(AirbaseName,unit,2500)
end
else
self.AirbaseZone=ZONE_RADIUS:New(AirbaseName,vec2,2500)
end
else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s",AirbaseName))
end
self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName)))
return self
end
function AIRBASE:_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)
return AirbaseFound
end
function AIRBASE:FindByName(AirbaseName)
local AirbaseFound=_DATABASE:FindAirbase(AirbaseName)
return AirbaseFound
end
function AIRBASE:FindByID(id)
for name,_airbase in pairs(_DATABASE.AIRBASES)do
local airbase=_airbase
local aid=tonumber(airbase:GetID(true))
if aid==id then
return airbase
end
end
return nil
end
function AIRBASE:GetDCSObject()
local DCSAirbase=Airbase.getByName(self.AirbaseName)
if DCSAirbase then
return DCSAirbase
end
return nil
end
function AIRBASE:GetZone()
return self.AirbaseZone
end
function AIRBASE:GetWarehouse()
local warehouse=nil
local airbase=self:GetDCSObject()
if airbase and Airbase.getWarehouse then
warehouse=airbase:getWarehouse()
end
return warehouse
end
function AIRBASE:GetStorage()
return self.storage
end
function AIRBASE:SetAutoCapture(Switch)
local airbase=self:GetDCSObject()
if airbase then
airbase:autoCapture(Switch)
end
return self
end
function AIRBASE:SetAutoCaptureON()
self:SetAutoCapture(true)
return self
end
function AIRBASE:SetAutoCaptureOFF()
self:SetAutoCapture(false)
return self
end
function AIRBASE:IsAutoCapture()
local airbase=self:GetDCSObject()
local auto=nil
if airbase then
auto=airbase:autoCaptureIsOn()
end
return auto
end
function AIRBASE:SetCoalition(Coal)
local airbase=self:GetDCSObject()
if airbase then
airbase:setCoalition(Coal)
end
return self
end
function AIRBASE.GetAllAirbases(coalition,category)
local airbases={}
for _,_airbase in pairs(_DATABASE.AIRBASES)do
local airbase=_airbase
if coalition==nil or airbase:GetCoalition()==coalition then
if category==nil or category==airbase:GetAirbaseCategory()then
table.insert(airbases,airbase)
end
end
end
return airbases
end
function AIRBASE.GetAllAirbaseNames(coalition,category)
local airbases={}
for airbasename,_airbase in pairs(_DATABASE.AIRBASES)do
local airbase=_airbase
if coalition==nil or airbase:GetCoalition()==coalition then
if category==nil or category==airbase:GetAirbaseCategory()then
table.insert(airbases,airbasename)
end
end
end
return airbases
end
function AIRBASE:GetID(unique)
if self.AirbaseID then
return unique and self.AirbaseID or math.abs(self.AirbaseID)
else
for DCSAirbaseId,DCSAirbase in ipairs(world.getAirbases())do
local AirbaseName=DCSAirbase:getName()
local airbaseID=tonumber(DCSAirbase:getID())
local airbaseCategory=self:GetAirbaseCategory()
if AirbaseName==self.AirbaseName then
if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then
return unique and-airbaseID or airbaseID
else
return airbaseID
end
end
end
end
return nil
end
function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist)
if TerminalIdWhitelist==nil then
self.parkingWhitelist={}
return self
end
if type(TerminalIdWhitelist)~="table"then
TerminalIdWhitelist={TerminalIdWhitelist}
end
self.parkingWhitelist=TerminalIdWhitelist
return self
end
function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist)
if TerminalIdBlacklist==nil then
self.parkingBlacklist={}
return self
end
if type(TerminalIdBlacklist)~="table"then
TerminalIdBlacklist={TerminalIdBlacklist}
end
self.parkingBlacklist=TerminalIdBlacklist
return self
end
function AIRBASE:SetRadioSilentMode(Silent)
local airbase=self:GetDCSObject()
if airbase then
airbase:setRadioSilentMode(Silent)
end
return self
end
function AIRBASE:GetRadioSilentMode()
local silent=nil
local airbase=self:GetDCSObject()
if airbase then
silent=airbase:getRadioSilentMode()
end
return silent
end
function AIRBASE:GetAirbaseCategory()
return self.category
end
function AIRBASE:IsAirdrome()
return self.isAirdrome
end
function AIRBASE:IsHelipad()
return self.isHelipad
end
function AIRBASE:IsShip()
return self.isShip
end
function AIRBASE:GetParkingData(available)
self:F2(available)
local DCSAirbase=self:GetDCSObject()
local parkingdata=nil
if DCSAirbase then
parkingdata=DCSAirbase:getParking(available)
end
self:T2({parkingdata=parkingdata})
return parkingdata
end
function AIRBASE:GetParkingSpotsNumber(termtype)
local parkingdata=self:GetParkingData(false)
local nspots=0
for _,parkingspot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
nspots=nspots+1
end
end
return nspots
end
function AIRBASE:GetFreeParkingSpotsNumber(termtype,allowTOAC)
local parkingdata=self:GetParkingData(true)
local nfree=0
for _,parkingspot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then
nfree=nfree+1
end
end
end
return nfree
end
function AIRBASE:GetFreeParkingSpotsCoordinates(termtype,allowTOAC)
local parkingdata=self:GetParkingData(true)
local spots={}
for _,parkingspot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
if(allowTOAC and allowTOAC==true)or parkingspot.TO_AC==false then
table.insert(spots,COORDINATE:NewFromVec3(parkingspot.vTerminalPos))
end
end
end
return spots
end
function AIRBASE:GetParkingSpotsCoordinates(termtype)
local parkingdata=self:GetParkingData(false)
local spots={}
for _,parkingspot in ipairs(parkingdata)do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type,termtype)then
local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos)
table.insert(spots,_coord)
end
end
return spots
end
function AIRBASE:_InitParkingSpots()
local parkingdata=self:GetParkingData(false)
self.parking={}
self.parkingByID={}
self.NparkingTotal=0
self.NparkingTerminal={}
for _,terminalType in pairs(AIRBASE.TerminalType)do
self.NparkingTerminal[terminalType]=0
end
local function isClient(coord)
local clients=_DATABASE.CLIENTS
for clientname,_client in pairs(clients)do
local client=_client
if client and client.SpawnCoord then
local dist=client.SpawnCoord:Get2DDistance(coord)
if dist<2 then
return true,clientname
end
end
end
return false,nil
end
for _,spot in pairs(parkingdata)do
local park={}
park.Vec3=spot.vTerminalPos
park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos)
park.DistToRwy=spot.fDistToRW
park.Free=nil
park.TerminalID=spot.Term_Index
park.TerminalID0=spot.Term_Index_0
park.TerminalType=spot.Term_Type
park.TOAC=spot.TO_AC
park.ClientSpot,park.ClientName=isClient(park.Coordinate)
park.AirbaseName=self.AirbaseName
self.NparkingTotal=self.NparkingTotal+1
for _,terminalType in pairs(AIRBASE.TerminalType)do
if self._CheckTerminalType(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)
return self.parkingByID[TerminalID]
end
function AIRBASE:GetParkingSpotsTable(termtype)
local parkingdata=self:GetParkingData(false)
local parkingfree=self:GetParkingData(true)
local function _isfree(_tocheck)
for _,_spot in pairs(parkingfree)do
if _spot.Term_Index==_tocheck.Term_Index then
return true
end
end
return false
end
local spots={}
for _,_spot in pairs(parkingdata)do
if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)then
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
if spot then
spot.Free=_isfree(_spot)
spot.TOAC=_spot.TO_AC
spot.AirbaseName=self.AirbaseName
table.insert(spots,spot)
else
self:E(string.format("ERROR: Parking spot %s is nil!",tostring(_spot.Term_Index)))
end
end
end
return spots
end
function AIRBASE:GetFreeParkingSpotsTable(termtype,allowTOAC)
local parkingfree=self:GetParkingData(true)
local freespots={}
for _,_spot in pairs(parkingfree)do
if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)then
if(allowTOAC and allowTOAC==true)or _spot.TO_AC==false then
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
spot.Free=true
spot.TOAC=_spot.TO_AC
spot.AirbaseName=self.AirbaseName
table.insert(freespots,spot)
end
end
end
return freespots
end
function AIRBASE:GetParkingSpotData(TerminalID)
local parkingdata=self:GetParkingSpotsTable()
for _,_spot in pairs(parkingdata)do
local spot=_spot
self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType})
if TerminalID==spot.TerminalID then
return spot
end
end
self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID))
return nil
end
function AIRBASE:MarkParkingSpots(termtype,mark)
if mark==nil then
mark=true
end
local parkingdata=self:GetParkingSpotsTable(termtype)
local airbasename=self:GetName()
self:E(string.format("Parking spots at %s for terminal type %s:",airbasename,tostring(termtype)))
for _,_spot in pairs(parkingdata)do
local _text=string.format("Term Index=%d, Term Type=%d, Free=%s, TOAC=%s, Term ID0=%d, Dist2Rwy=%.1f m",
_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)
if mark then
_spot.Coordinate:MarkToAll(_text)
end
local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m",
airbasename,_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)
self:E(_text)
end
end
function AIRBASE:FindFreeParkingSpotForAircraft(group,terminaltype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nspots,parkingdata)
scanradius=scanradius or 50
if scanunits==nil then
scanunits=true
end
if scanstatics==nil then
scanstatics=true
end
if scanscenery==nil then
scanscenery=false
end
if verysafe==nil then
verysafe=false
end
local function _overlap(object1,object2,dist)
local pos1=object1
local pos2=object2
local r1=pos1:GetBoundingRadius()
local r2=pos2:GetBoundingRadius()
if r1 and r2 then
local safedist=(r1+r2)*1.1
local safe=(dist>safedist)
self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s",r1,r2,safedist,dist,tostring(safe)))
return safe
else
return true
end
end
local airport=self:GetName()
parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype)
local aircraft=nil
local _aircraftsize=23
local ax=23
local ay=7
local az=17
if group and group.ClassName=="GROUP"then
aircraft=group:GetUnit(1)
if aircraft then
_aircraftsize,ax,ay,az=aircraft:GetObjectSize()
end
end
local _nspots=nspots or group:GetSize()
self:T(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.",airport,_nspots,_aircraftsize,ax,ay,az,tostring(terminaltype)))
local validspots={}
local nvalid=0
local _test=false
if _test then
return validspots
end
local markobstacles=false
for _,parkingspot in pairs(parkingdata)do
local _spot=parkingspot.Coordinate
local _termid=parkingspot.TerminalID
if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)and self:_CheckParkingLists(_termid)then
if verysafe and(parkingspot.Free==false or parkingspot.TOAC==true)then
self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.",airport,parkingspot.TerminalID,tostring(parkingspot.Free),tostring(parkingspot.TOAC)))
else
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery)
local occupied=false
for _,unit in pairs(_units)do
local _coord=unit:GetCoordinate()
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,unit,_dist)
if markobstacles then
local l,x,y,z=unit:GetObjectSize()
_coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",unit:GetName(),x,y,z,l,_dist,_termid,tostring(_safe)))
end
if scanunits and not _safe then
occupied=true
end
end
for _,static in pairs(_statics)do
local _static=STATIC:Find(static)
local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,_static,_dist)
if markobstacles then
local l,x,y,z=_static:GetObjectSize()
_coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",static:getName(),x,y,z,l,_dist,_termid,tostring(_safe)))
end
if scanstatics and not _safe then
occupied=true
end
end
for _,scenery in pairs(_sceneries)do
local _scenery=SCENERY:Register(scenery:getTypeName(),scenery)
local _vec3=scenery:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _dist=_coord:Get2DDistance(_spot)
local _safe=_overlap(aircraft,_scenery,_dist)
if markobstacles then
local l,x,y,z=scenery:GetObjectSize(scenery)
_coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s",scenery:getTypeName(),x,y,z,l,_dist,_termid,tostring(_safe)))
end
if scanscenery and not _safe then
occupied=true
end
end
for _,_takenspot in pairs(validspots)do
local _dist=_takenspot.Coordinate:Get2DDistance(_spot)
local _safe=_overlap(aircraft,aircraft,_dist)
if not _safe then
occupied=true
end
end
if occupied then
self:T(string.format("%s: Parking spot id %d occupied.",airport,_termid))
else
self:T(string.format("%s: Parking spot id %d free.",airport,_termid))
if nvalid<_nspots then
table.insert(validspots,{Coordinate=_spot,TerminalID=_termid})
end
nvalid=nvalid+1
self:T(string.format("%s: Parking spot id %d free. Nfree=%d/%d.",airport,_termid,nvalid,_nspots))
end
end
if nvalid>=_nspots then
return validspots
end
end
end
return validspots
end
function AIRBASE:_CheckParkingLists(TerminalID)
if self.parkingBlacklist and#self.parkingBlacklist>0 then
for _,terminalID in pairs(self.parkingBlacklist or{})do
if terminalID==TerminalID then
return false
end
end
end
if self.parkingWhitelist and#self.parkingWhitelist>0 then
for _,terminalID in pairs(self.parkingWhitelist or{})do
if terminalID==TerminalID then
return true
end
end
return false
end
return true
end
function AIRBASE._CheckTerminalType(Term_Type,termtype)
if Term_Type==nil then
return false
end
if termtype==nil then
if Term_Type==AIRBASE.TerminalType.Runway then
return false
else
return true
end
end
local match=false
if Term_Type==termtype then
match=true
end
if termtype==AIRBASE.TerminalType.OpenMedOrBig then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then
match=true
end
elseif termtype==AIRBASE.TerminalType.HelicopterUsable then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.HelicopterOnly then
match=true
end
elseif termtype==AIRBASE.TerminalType.FighterAircraft then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter 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
end
return match
end
function AIRBASE:GetRunways()
return self.runways or{}
end
function AIRBASE:GetRunwayByName(Name)
if Name==nil then
return
end
if Name then
for _,_runway in pairs(self.runways)do
local runway=_runway
local name=self:GetRunwayName(runway)
self:T("Check Runway Name: "..name)
if name==Name:upper()then
return runway
end
end
end
self:E("ERROR: Could not find runway with name "..tostring(Name))
return nil
end
function AIRBASE:_InitRunways(IncludeInverse)
if IncludeInverse==nil then
IncludeInverse=true
end
local Runways={}
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={}
local namefromheading=math.floor(heading/10)
if self.AirbaseName==AIRBASE.Syria.Beirut_Rafic_Hariri and math.abs(namefromheading-name)>1 then
runway.name=string.format("%02d",tonumber(namefromheading))
else
runway.name=string.format("%02d",tonumber(name))
end
runway.magheading=tonumber(runway.name)*10
runway.heading=heading
runway.width=width or 0
runway.length=length or 0
runway.center=COORDINATE:NewFromVec3(center)
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
if math.abs(runway.heading-runway.magheading)>60 then
self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f",runway.name,runway.heading,runway.magheading))
runway.heading=runway.heading-180
end
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
runway.position=runway.center:Translate(-runway.length/2,runway.heading)
runway.endpoint=runway.center:Translate(runway.length/2,runway.heading)
local init=runway.center:GetVec3()
local width=runway.width/2
local L2=runway.length/2
local offset1={x=init.x+(math.cos(bearing+math.pi)*L2),y=init.z+(math.sin(bearing+math.pi)*L2)}
local offset2={x=init.x-(math.cos(bearing+math.pi)*L2),y=init.z-(math.sin(bearing+math.pi)*L2)}
local points={}
points[1]={x=offset1.x+(math.cos(bearing+(math.pi/2))*width),y=offset1.y+(math.sin(bearing+(math.pi/2))*width)}
points[2]={x=offset1.x+(math.cos(bearing-(math.pi/2))*width),y=offset1.y+(math.sin(bearing-(math.pi/2))*width)}
points[3]={x=offset2.x+(math.cos(bearing-(math.pi/2))*width),y=offset2.y+(math.sin(bearing-(math.pi/2))*width)}
points[4]={x=offset2.x+(math.cos(bearing+(math.pi/2))*width),y=offset2.y+(math.sin(bearing+(math.pi/2))*width)}
runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s",self.AirbaseName,runway.name),points)
return runway
end
local airbase=self:GetDCSObject()
if airbase then
local runways=airbase:getRunways()
self:T2(runways)
if runways 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)
table.insert(Runways,runway)
if IncludeInverse then
local idx=tonumber(runway.name)
local name2=tostring(idx-18)
if idx<18 then
name2=tostring(idx+18)
end
local runway=_createRunway(name2,rwy.course-math.pi,rwy.width,rwy.length,rwy.position)
table.insert(Runways,runway)
end
end
else
self.runways={}
return{}
end
end
local rpairs={}
for i,_ri in pairs(Runways)do
local ri=_ri
for j,_rj in pairs(Runways)do
local rj=_rj
if i<j then
if ri.name==rj.name then
rpairs[i]=j
end
end
end
end
local function isLeft(a,b,c)
return((b.z-a.z)*(c.x-a.x)-(b.x-a.x)*(c.z-a.z))>0
end
for i,j in pairs(rpairs)do
local ri=Runways[i]
local rj=Runways[j]
local c0=ri.center
local a=UTILS.VecTranslate(c0,1000,ri.heading)
local b=UTILS.VecSubstract(rj.center,ri.center)
b=UTILS.VecAdd(ri.center,b)
local left=isLeft(c0,a,b)
if left then
ri.isLeft=false
rj.isLeft=true
else
ri.isLeft=true
rj.isLeft=false
end
end
self.runways=Runways
return Runways
end
function AIRBASE:GetRunwayData(magvar,mark)
local runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
return{}
end
local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
if false then
for i,_coord in pairs(runwaycoords)do
local coord=_coord
coord:Translate(100,0):MarkToAll("Runway i="..i)
end
end
magvar=magvar or UTILS.GetMagneticDeclination()
local N=#runwaycoords
local N2=N/2
local exception=false
local name=self:GetName()
if name==AIRBASE.Nevada.Jean_Airport or
name==AIRBASE.Nevada.Creech_AFB or
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
name==AIRBASE.PersianGulf.Dubai_Intl or
name==AIRBASE.PersianGulf.Shiraz_International_Airport or
name==AIRBASE.PersianGulf.Kish_International_Airport or
name==AIRBASE.MarianaIslands.Andersen_AFB then
exception=1
elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and
name~=AIRBASE.Syria.Minakh and
name~=AIRBASE.Syria.Damascus and
name~=AIRBASE.Syria.Khalkhalah and
name~=AIRBASE.Syria.Marj_Ruhayyil and
name~=AIRBASE.Syria.Beirut_Rafic_Hariri then
exception=2
end
local function f(i)
local j
if exception==1 then
j=N-(i-1)
elseif exception==2 then
if i<=N2 then
j=i+N2
else
j=i-N2
end
else
if i%2==0 then
j=i-1
else
j=i+1
end
end
if name==AIRBASE.Syria.Beirut_Rafic_Hariri then
if i==1 then
j=3
elseif i==2 then
j=6
elseif i==3 then
j=1
elseif i==4 then
j=5
elseif i==5 then
j=4
elseif i==6 then
j=2
end
end
if name==AIRBASE.Syria.Ramat_David then
if i==1 then
j=4
elseif i==2 then
j=6
elseif i==3 then
j=5
elseif i==4 then
j=1
elseif i==5 then
j=3
elseif i==6 then
j=2
end
end
return j
end
for i=1,N do
local j=f(i)
local c1=runwaycoords[i]
local c2=runwaycoords[j]
local hdg=c1:HeadingTo(c2)
local idx=string.format("%02d",UTILS.Round((hdg-magvar)/10,0))
local runway={}
runway.heading=hdg
runway.idx=idx
runway.length=c1:Get2DDistance(c2)
runway.position=c1
runway.endpoint=c2
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
return runways
end
function AIRBASE:SetActiveRunway(Name,PreferLeft)
self:SetActiveRunwayTakeoff(Name,PreferLeft)
self:SetActiveRunwayLanding(Name,PreferLeft)
end
function AIRBASE:SetActiveRunwayLanding(Name,PreferLeft)
local runway=self:GetRunwayByName(Name)
if not runway then
runway=self:GetRunwayIntoWind(PreferLeft)
end
if runway then
self:T(string.format("%s: Setting active runway for landing as %s",self.AirbaseName,self:GetRunwayName(runway)))
else
self:E("ERROR: Could not set the runway for landing!")
end
self.runwayLanding=runway
return runway
end
function AIRBASE:GetActiveRunway()
return self.runwayLanding,self.runwayTakeoff
end
function AIRBASE:GetActiveRunwayLanding()
return self.runwayLanding
end
function AIRBASE:GetActiveRunwayTakeoff()
return self.runwayTakeoff
end
function AIRBASE:SetActiveRunwayTakeoff(Name,PreferLeft)
local runway=self:GetRunwayByName(Name)
if not runway then
runway=self:GetRunwayIntoWind(PreferLeft)
end
if runway then
self:T(string.format("%s: Setting active runway for takeoff as %s",self.AirbaseName,self:GetRunwayName(runway)))
else
self:E("ERROR: Could not set the runway for takeoff!")
end
self.runwayTakeoff=runway
return runway
end
function AIRBASE:GetRunwayIntoWind(PreferLeft)
local runways=self:GetRunways()
local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3()
local norm=UTILS.VecNorm(Vwind)
local iact=1
if norm>0 then
Vwind.x=Vwind.x/norm
Vwind.y=0
Vwind.z=Vwind.z/norm
local dotmin=nil
for i,_runway in pairs(runways)do
local runway=_runway
if PreferLeft==nil or PreferLeft==runway.isLeft then
local alpha=math.rad(runway.heading)
local Vrunway={x=math.cos(alpha),y=0,z=math.sin(alpha)}
local dot=UTILS.VecDot(Vwind,Vrunway)
if dotmin==nil or dot<dotmin then
dotmin=dot
iact=i
end
end
end
else
self:E("WARNING: Norm of wind is zero! Cannot determine runway based on wind direction")
end
return runways[iact]
end
function AIRBASE:GetRunwayName(Runway,LongLeftRight)
Runway=Runway or self:GetActiveRunway()
local name="XX"
if Runway then
name=Runway.name
if Runway.isLeft==true then
if LongLeftRight then
name=name.." Left"
else
name=name.."L"
end
elseif Runway.isLeft==false then
if LongLeftRight then
name=name.." Right"
else
name=name.."R"
end
end
end
return name
end
function AIRBASE:CheckOnRunWay(group,radius,despawn)
radius=radius or 50
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
return false
end
if group and group:IsAlive()then
self:T(string.format("%s, checking if group %s is on runway?",self:GetName(),group:GetName()))
local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway)
local units=group:GetUnits()
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()and not unit:InAir()then
self:T(string.format("%s, checking if unit %s is on runway?",self:GetName(),unit:GetName()))
for _i,_coord in pairs(runwaypoints)do
local dist=unit:GetCoordinate():Get2DDistance(_coord)
if dist<radius then
self:E(string.format("%s, unit %s of group %s was spawned on runway #%d. Distance %.1f < radius %.1f m. Despawn = %s.",self:GetName(),unit:GetName(),group:GetName(),_i,dist,radius,tostring(despawn)))
if despawn then
group:Destroy(true)
end
return true
else
self:T(string.format("%s, unit %s of group %s was NOT spawned on runway #%d. Distance %.1f > radius %.1f m. Despawn = %s.",self:GetName(),unit:GetName(),group:GetName(),_i,dist,radius,tostring(despawn)))
end
end
else
self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(),unit:GetName(),group:GetName()))
end
end
else
self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(),group:GetName()))
end
return false
end
function AIRBASE:GetCategory()
return self.category
end
function AIRBASE:GetCategoryName()
return AIRBASE.CategoryName[self.category]
end
SCENERY={
ClassName="SCENERY",
}
function SCENERY:Register(SceneryName,SceneryObject)
local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName))
self.SceneryName=tostring(SceneryName)
self.SceneryObject=SceneryObject
if self.SceneryObject and self.SceneryObject.getLife then
self.Life0=self.SceneryObject:getLife()or 0
else
self.Life0=0
end
self.Properties={}
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
function SCENERY:SetProperty(PropertyName,PropertyValue)
self.Properties[PropertyName]=PropertyValue
return self
end
function SCENERY:GetName()
return self.SceneryName
end
function SCENERY:GetDCSObject()
return self.SceneryObject
end
function SCENERY:GetLife()
local life=0
if self.SceneryObject and self.SceneryObject.getLife then
life=self.SceneryObject:getLife()
if life>self.Life0 then
self.Life0=math.floor(life*1.2)
end
end
return life
end
function SCENERY:GetLife0()
return self.Life0 or 0
end
function SCENERY:IsAlive(Threshold)
if not Threshold then
return self:GetLife()>=1 and true or false
else
return self:GetRelativeLife()>Threshold and true or false
end
end
function SCENERY:IsDead(Threshold)
if not Threshold then
return self:GetLife()<1 and true or false
else
return self:GetRelativeLife()<=Threshold and true or false
end
end
function SCENERY:GetRelativeLife()
local life=self:GetLife()
local life0=self:GetLife0()
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)
local radius=Radius or 100
local name=Name or"unknown"
local scenery=nil
local function SceneryScan(scoordinate,sradius,sname)
if scoordinate~=nil then
local Vec2=scoordinate:GetVec2()
local scanzone=ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius,true)
scanzone:Scan({Object.Category.SCENERY})
local scanned=scanzone:GetScannedSceneryObjects()
local rscenery=nil
for _,_scenery in pairs(scanned)do
local scenery=_scenery
if tostring(scenery.SceneryName)==tostring(sname)then
rscenery=scenery
if Role then rscenery:SetProperty("ROLE",Role)end
break
end
end
return rscenery
end
return nil
end
if Coordinate then
scenery=SceneryScan(Coordinate,radius,name)
end
return scenery
end
function SCENERY:FindByNameInZone(Name,Zone,Radius)
local radius=Radius or 100
local name=Name or"unknown"
if type(Zone)=="string"then
Zone=ZONE:FindByName(Zone)
end
local coordinate=Zone:GetCoordinate()
return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"))
end
function SCENERY:FindByZoneName(ZoneName)
local zone=ZoneName
if type(ZoneName)=="string"then
zone=ZONE:FindByName(ZoneName)
end
local _id=zone:GetProperty('OBJECT ID')
if not _id then
BASE:E("**** Zone without object ID: "..ZoneName.." | Type: "..tostring(zone.ClassName))
if string.find(zone.ClassName,"POLYGON")then
zone:Scan({Object.Category.SCENERY})
local scanned=zone:GetScannedSceneryObjects()
for _,_scenery in(scanned)do
local scenery=_scenery
if scenery:IsAlive()then
local role=zone:GetProperty("ROLE")
if role then scenery:SetProperty("ROLE",role)end
return scenery
end
end
return nil
else
return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE"))
end
else
return self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE"))
end
end
function SCENERY:FindAllByZoneName(ZoneName)
local zone=ZoneName
if type(ZoneName)=="string"then
zone=ZONE:FindByName(ZoneName)
end
local _id=zone:GetProperty('OBJECT ID')
if not _id then
zone:Scan({Object.Category.SCENERY})
local scanned=zone:GetScannedSceneryObjects()
if#scanned>0 then
return scanned
else
return nil
end
else
local obj=self:FindByName(_id,zone:GetCoordinate(),nil,zone:GetProperty("ROLE"))
if obj then
return{obj}
else
return nil
end
end
end
function SCENERY:Destroy()
return self
end
MARKER={
ClassName="MARKER",
Debug=false,
lid=nil,
mid=nil,
coordinate=nil,
text=nil,
message=nil,
readonly=nil,
coalition=nil,
}
_MARKERID=0
MARKER.version="0.1.1"
function MARKER:New(Coordinate,Text)
local self=BASE:Inherit(self,FSM:New())
self.coordinate=UTILS.DeepCopy(Coordinate)
self.text=Text
self.readonly=false
self.message=""
_MARKERID=_MARKERID+1
self.myid=_MARKERID
self.lid=string.format("Marker #%d | ",self.myid)
self:SetStartState("Invisible")
self:AddTransition("Invisible","Added","Visible")
self:AddTransition("Visible","Removed","Invisible")
self:AddTransition("*","Changed","*")
self:AddTransition("*","TextUpdate","*")
self:AddTransition("*","CoordUpdate","*")
self:HandleEvent(EVENTS.MarkAdded)
self:HandleEvent(EVENTS.MarkRemoved)
self:HandleEvent(EVENTS.MarkChange)
return self
end
function MARKER:ReadOnly()
self.readonly=true
return self
end
function MARKER:ReadWrite()
self.readonly=false
return self
end
function MARKER:Message(Text)
self.message=Text or""
return self
end
function MARKER:ToAll(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.ToAll,self)
else
self.toall=true
self.tocoalition=nil
self.coalition=nil
self.togroup=nil
self.groupname=nil
self.groupid=nil
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
trigger.action.markToAll(self.mid,self.text,self.coordinate:GetVec3(),self.readonly,self.message)
end
return self
end
function MARKER:ToCoalition(Coalition,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.ToCoalition,self,Coalition)
else
self.coalition=Coalition
self.tocoalition=true
self.toall=false
self.togroup=false
self.groupname=nil
self.groupid=nil
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
trigger.action.markToCoalition(self.mid,self.text,self.coordinate:GetVec3(),self.coalition,self.readonly,self.message)
end
return self
end
function MARKER:ToBlue(Delay)
self:ToCoalition(coalition.side.BLUE,Delay)
return self
end
function MARKER:ToRed(Delay)
self:ToCoalition(coalition.side.RED,Delay)
return self
end
function MARKER:ToNeutral(Delay)
self:ToCoalition(coalition.side.NEUTRAL,Delay)
return self
end
function MARKER:ToGroup(Group,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.ToGroup,self,Group)
else
if Group and Group:IsAlive()~=nil then
self.groupid=Group:GetID()
if self.groupid then
self.groupname=Group:GetName()
self.togroup=true
self.tocoalition=nil
self.coalition=nil
self.toall=nil
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
trigger.action.markToGroup(self.mid,self.text,self.coordinate:GetVec3(),self.groupid,self.readonly,self.message)
end
else
end
end
return self
end
function MARKER:UpdateText(Text,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.UpdateText,self,Text)
else
self.text=tostring(Text)
self:Refresh()
self:TextUpdate(tostring(Text))
end
return self
end
function MARKER:UpdateCoordinate(Coordinate,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.UpdateCoordinate,self,Coordinate)
else
self.coordinate=Coordinate
self:Refresh()
self:CoordUpdate(Coordinate)
end
return self
end
function MARKER:Refresh(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.Refresh,self)
else
if self.toall then
self:ToAll()
elseif self.tocoalition then
self:ToCoalition(self.coalition)
elseif self.togroup then
local group=GROUP:FindByName(self.groupname)
self:ToGroup(group)
else
self:E(self.lid.."ERROR: unknown To in :Refresh()!")
end
end
return self
end
function MARKER:Remove(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MARKER.Remove,self)
else
if self.shown then
trigger.action.removeMark(self.mid)
end
end
return self
end
function MARKER:GetCoordinate()
return self.coordinate
end
function MARKER:GetText()
return self.text
end
function MARKER:SetText(Text)
self.text=Text and tostring(Text)or""
return self
end
function MARKER:IsVisible()
return self:Is("Visible")
end
function MARKER:IsInvisible()
return self:Is("Invisible")
end
function MARKER:OnEventMarkAdded(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
self.shown=true
self:Added(EventData)
end
end
end
function MARKER:OnEventMarkRemoved(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
self.shown=false
self:Removed(EventData)
end
end
end
function MARKER:OnEventMarkChange(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s",tostring(MarkID)))
if MarkID==self.mid then
self.text=tostring(EventData.MarkText)
self:Changed(EventData)
end
end
end
end
function MARKER:onafterAdded(From,Event,To,EventData)
local text=string.format("Captured event MarkAdded for myself:\n")
text=text..string.format("Marker ID  = %s\n",tostring(EventData.MarkID))
text=text..string.format("Coalition  = %s\n",tostring(EventData.MarkCoalition))
text=text..string.format("Group  ID  = %s\n",tostring(EventData.MarkGroupID))
text=text..string.format("Initiator  = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody")
text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere")
text=text..string.format("Text:          \n%s",tostring(EventData.MarkText))
self:T2(self.lid..text)
end
function MARKER:onafterRemoved(From,Event,To,EventData)
local text=string.format("Captured event MarkRemoved for myself:\n")
text=text..string.format("Marker ID  = %s\n",tostring(EventData.MarkID))
text=text..string.format("Coalition  = %s\n",tostring(EventData.MarkCoalition))
text=text..string.format("Group  ID  = %s\n",tostring(EventData.MarkGroupID))
text=text..string.format("Initiator  = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody")
text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere")
text=text..string.format("Text:          \n%s",tostring(EventData.MarkText))
self:T2(self.lid..text)
end
function MARKER:onafterChanged(From,Event,To,EventData)
local text=string.format("Captured event MarkChange for myself:\n")
text=text..string.format("Marker ID  = %s\n",tostring(EventData.MarkID))
text=text..string.format("Coalition  = %s\n",tostring(EventData.MarkCoalition))
text=text..string.format("Group  ID  = %s\n",tostring(EventData.MarkGroupID))
text=text..string.format("Initiator  = %s\n",EventData.IniUnit and EventData.IniUnit:GetName()or"Nobody")
text=text..string.format("Coordinate = %s\n",EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS()or"Nowhere")
text=text..string.format("Text:          \n%s",tostring(EventData.MarkText))
self:T2(self.lid..text)
end
function MARKER:onafterTextUpdate(From,Event,To,Text)
self:T(self.lid..string.format("New Marker Text:\n%s",Text))
end
function MARKER:onafterCoordUpdate(From,Event,To,Coordinate)
self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s",Coordinate:ToStringLLDMS()))
end
WEAPON={
ClassName="WEAPON",
verbose=0,
}
WEAPON.version="0.1.0"
function WEAPON:New(WeaponObject)
if WeaponObject==nil then
env.error("ERROR: Weapon object does NOT exist")
return nil
end
local self=BASE:Inherit(self,POSITIONABLE:New("Weapon"))
self.weapon=WeaponObject
self.desc=WeaponObject:getDesc()
self.category=self.desc.category
if self:IsMissile()and self.desc.missileCategory then
self.categoryMissile=self.desc.missileCategory
if self.desc.guidance then
self.guidance=self.desc.guidance
end
end
self.typeName=WeaponObject:getTypeName()or"Unknown Type"
self.name=WeaponObject:getName()
self.coalition=WeaponObject:getCoalition()
self.country=WeaponObject:getCountry()
self.launcher=WeaponObject:getLauncher()
self.launcherName="Unknown Launcher"
if self.launcher then
self.launcherName=self.launcher:getName()
self.launcherUnit=UNIT:Find(self.launcher)
end
self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint())
self.lid=string.format("[%s] %s | ",self.typeName,self.name)
if self.launcherUnit then
self.releaseHeading=self.launcherUnit:GetHeading()
self.releaseAltitudeASL=self.launcherUnit:GetAltitude()
self.releaseAltitudeAGL=self.launcherUnit:GetAltitude(true)
self.releaseCoordinate=self.launcherUnit:GetCoordinate()
self.releasePitch=self.launcherUnit:GetPitch()
end
self:SetTimeStepTrack()
self:SetDistanceInterceptPoint()
local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s",
self.version,self.name,self.typeName,self.category,self.coalition,self.country,self.launcherName)
self:T(self.lid..text)
self:T2(self.desc)
return self
end
function WEAPON:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function WEAPON:SetTimeStepTrack(TimeStep)
self.dtTrack=TimeStep or 0.01
return self
end
function WEAPON:SetDistanceInterceptPoint(Distance)
self.distIP=Distance or 50
return self
end
function WEAPON:SetMarkImpact(Switch)
if Switch==false then
self.impactMark=false
else
self.impactMark=true
end
return self
end
function WEAPON:SetSmokeImpact(Switch,SmokeColor)
if Switch==false then
self.impactSmoke=false
else
self.impactSmoke=true
end
self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red
return self
end
function WEAPON:SetFuncTrack(FuncTrack,...)
self.trackFunc=FuncTrack
self.trackArg=arg or{}
return self
end
function WEAPON:SetFuncImpact(FuncImpact,...)
self.impactFunc=FuncImpact
self.impactArg=arg or{}
return self
end
function WEAPON:GetLauncher()
return self.launcherUnit
end
function WEAPON:GetTarget()
local target=nil
if self.weapon then
local object=self.weapon:getTarget()
if object then
local category=Object.getCategory(object)
local name=object:getName()
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
target=STATIC:FindByName(name,false)
elseif category==Object.Category.SCENERY then
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
else
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!",category))
end
end
end
end
return target
end
function WEAPON:GetTargetDistance(ConversionFunction)
local target=self:GetTarget()
local distance=nil
if target then
local tv3=target:GetVec3()
local wv3=self:GetVec3()
if tv3 and wv3 then
distance=UTILS.VecDist3D(tv3,wv3)
if ConversionFunction then
distance=ConversionFunction(distance)
end
end
end
return distance
end
function WEAPON:GetTargetName()
local target=self:GetTarget()
local name="None"
if target then
name=target:GetName()
end
return name
end
function WEAPON:GetVelocityVec3()
local Vvec3=nil
if self.weapon then
Vvec3=self.weapon:getVelocity()
end
return Vvec3
end
function WEAPON:GetSpeed(ConversionFunction)
local speed=nil
if self.weapon then
local v=self:GetVelocityVec3()
speed=UTILS.VecNorm(v)
if ConversionFunction then
speed=ConversionFunction(speed)
end
end
return speed
end
function WEAPON:GetVec3()
local vec3=nil
if self.weapon then
vec3=self.weapon:getPoint()
end
return vec3
end
function WEAPON:GetVec2()
local vec3=self:GetVec3()
if vec3 then
local vec2={x=vec3.x,y=vec3.z}
return vec2
end
return nil
end
function WEAPON:GetTypeName()
return self.typeName
end
function WEAPON:GetCoalition()
return self.coalition
end
function WEAPON:GetCountry()
return self.country
end
function WEAPON:GetDCSObject()
return self.weapon
end
function WEAPON:GetImpactVec3()
return self.impactVec3
end
function WEAPON:GetImpactCoordinate()
return self.impactCoord
end
function WEAPON:GetReleaseHeading(AccountForMagneticInclination)
AccountForMagneticInclination=AccountForMagneticInclination or true
if AccountForMagneticInclination then return UTILS.ClampAngle(self.releaseHeading-UTILS.GetMagneticDeclination())else return UTILS.ClampAngle(self.releaseHeading)end
end
function WEAPON:GetReleaseAltitudeASL()
return self.releaseAltitudeASL
end
function WEAPON:GetReleaseAltitudeAGL()
return self.releaseAltitudeAGL
end
function WEAPON:GetReleaseCoordinate()
return self.releaseCoordinate
end
function WEAPON:GetReleasePitch()
return self.releasePitch
end
function WEAPON:GetImpactHeading(AccountForMagneticInclination)
AccountForMagneticInclination=AccountForMagneticInclination or true
if AccountForMagneticInclination then return UTILS.ClampAngle(self.impactHeading-UTILS.GetMagneticDeclination())else return self.impactHeading end
end
function WEAPON:InAir()
local inAir=nil
if self.weapon then
inAir=self.weapon:inAir()
end
return inAir
end
function WEAPON:IsExist()
local isExist=nil
if self.weapon then
isExist=self.weapon:isExist()
end
return isExist
end
function WEAPON:IsBomb()
return self.category==Weapon.Category.BOMB
end
function WEAPON:IsMissile()
return self.category==Weapon.Category.MISSILE
end
function WEAPON:IsRocket()
return self.category==Weapon.Category.ROCKET
end
function WEAPON:IsShell()
return self.category==Weapon.Category.SHELL
end
function WEAPON:IsTorpedo()
return self.category==Weapon.Category.TORPEDO
end
function WEAPON:IsFoxOne()
return self.guidance==Weapon.GuidanceType.RADAR_SEMI_ACTIVE
end
function WEAPON:IsFoxTwo()
return self.guidance==Weapon.GuidanceType.IR
end
function WEAPON:IsFoxThree()
return self.guidance==Weapon.GuidanceType.RADAR_ACTIVE
end
function WEAPON:Destroy(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,WEAPON.Destroy,self,0)
else
if self.weapon then
self:T(self.lid.."Destroying Weapon NOW!")
self:StopTrack()
self.weapon:destroy()
end
end
return self
end
function WEAPON:StartTrack(Delay)
Delay=math.max(Delay or 0.001,0.001)
self:T(self.lid..string.format("Start tracking weapon in %.4f sec",Delay))
self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon,self,timer.getTime()+Delay)
return self
end
function WEAPON:StopTrack(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,WEAPON.StopTrack,self,0)
else
if self.trackScheduleID then
timer.removeFunction(self.trackScheduleID)
end
end
return self
end
function WEAPON:_TrackWeapon(time)
if self.verbose>=20 then
self:I(self.lid..string.format("Tracking at T=%.5f",time))
end
local status,pos3=pcall(
function()
local point=self.weapon:getPosition()
return point
end
)
if status then
self.pos3=pos3
self.vec3=UTILS.DeepCopy(self.pos3.p)
self.coordinate:UpdateFromVec3(self.vec3)
self.last_velocity=self.weapon:getVelocity()
self.tracking=true
if self.trackFunc then
self.trackFunc(self,unpack(self.trackArg))
end
if self.verbose>=5 then
local vec2={x=self.vec3.x,y=self.vec3.z}
local height=land.getHeight(vec2)
local agl=self.vec3.y-height
local ip=self:_GetIP(self.distIP)
local d=0
if ip then
d=UTILS.VecDist3D(self.vec3,ip)
end
self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f",time,height,agl,d))
end
else
local ip=self:_GetIP(self.distIP)
if self.verbose>=10 and ip then
self:I(self.lid.."Got intercept point!")
local coord=COORDINATE:NewFromVec3(ip)
coord:MarkToAll("Intercept point")
coord:SmokeBlue()
local d=UTILS.VecDist3D(ip,self.vec3)
self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters",d))
end
self.impactVec3=ip or self.vec3
self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
self.impactHeading=UTILS.VecHdg(self.last_velocity)
if self.impactMark then
self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s",self.name,self.typeName,self.launcherName))
end
if self.impactSmoke then
self.impactCoord:Smoke(self.impactSmokeColor)
end
if self.impactFunc then
self.impactFunc(self,unpack(self.impactArg or{}))
end
self.tracking=false
end
if self.tracking then
if self.dtTrack and self.dtTrack>=0.00001 then
return time+self.dtTrack
else
return nil
end
end
return nil
end
function WEAPON:_GetIP(Distance)
Distance=Distance or 50
local ip=nil
if Distance>0 and self.pos3 then
ip=land.getIP(self.pos3.p,self.pos3.x,Distance or 20)
end
return ip
end
do
NET={
ClassName="NET",
Version="0.1.4",
BlockTime=600,
BlockedPilots={},
BlockedUCIDs={},
BlockedSides={},
BlockedSlots={},
KnownPilots={},
BlockMessage=nil,
UnblockMessage=nil,
lid=nil,
}
function NET:New()
local self=BASE:Inherit(self,FSM:New())
self.BlockTime=600
self.BlockedPilots={}
self.KnownPilots={}
self:SetBlockMessage()
self:SetUnblockMessage()
self.BlockedSides={}
self.BlockedSides[1]=false
self.BlockedSides[2]=false
self:SetStartState("Stopped")
self:AddTransition("Stopped","Run","Running")
self:AddTransition("*","PlayerJoined","*")
self:AddTransition("*","PlayerLeft","*")
self:AddTransition("*","PlayerDied","*")
self:AddTransition("*","PlayerEjected","*")
self:AddTransition("*","PlayerBlocked","*")
self:AddTransition("*","PlayerUnblocked","*")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
self.lid=string.format("NET %s | ",self.Version)
self:Run()
return self
end
function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot})
local blocked=false
local TNow=timer.getTime()
if UCID and self.BlockedUCIDs[UCID]and TNow<self.BlockedUCIDs[UCID]then
blocked=true
end
if PlayerID and not Name then
Name=self:GetPlayerIDByName(Name)
end
if Name and self.BlockedPilots[Name]and TNow<self.BlockedPilots[Name]then
blocked=true
end
self:T({time=self.BlockedSides[PlayerSide]})
if PlayerSide and type(self.BlockedSides[PlayerSide])=="number"and TNow<self.BlockedSides[PlayerSide]then
blocked=true
end
if PlayerSlot and self.BlockedSlots[PlayerSlot]and TNow<self.BlockedSlots[PlayerSlot]then
blocked=true
end
self:T("IsAnyBlocked: "..tostring(blocked))
return blocked
end
function NET:_EventHandler(EventData)
self:T(self.lid.." _EventHandler")
self:T2({Event=EventData.id})
local data=EventData
if data.id and data.IniUnit and(data.IniPlayerName or data.IniUnit:GetPlayerName())then
local name=data.IniPlayerName and data.IniPlayerName or data.IniUnit:GetPlayerName()
local ucid=self:GetPlayerUCID(nil,name)or"none"
local PlayerID=self:GetPlayerIDByName(name)or"none"
local PlayerSide,PlayerSlot=self:GetSlot(data.IniUnit)
if not PlayerSide then PlayerSide=EventData.IniCoalition end
if not PlayerSlot then PlayerSlot=EventData.IniUnit:GetID()or-1 end
local TNow=timer.getTime()
if data.id==EVENTS.PlayerEnterUnit or data.id==EVENTS.PlayerEnterAircraft then
self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id)
local blocked=self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot)
if blocked and PlayerID then
self:T("Player blocked")
local outcome=net.force_player_slot(tonumber(PlayerID),PlayerSide,data.IniUnit:GetID())
self:T({Blocked_worked=outcome})
if outcome==false then
local unit=data.IniUnit
local sched=TIMER:New(unit.Destroy,unit,3):Start(3)
self:__PlayerBlocked(5,unit,name,1)
end
else
local client=CLIENT:FindByPlayerName(name)or data.IniUnit
if not self.KnownPilots[name]or(self.KnownPilots[name]and TNow-self.KnownPilots[name].timestamp>3)then
self:__PlayerJoined(1,client,name)
self.KnownPilots[name]={
name=name,
ucid=ucid,
id=PlayerID,
side=PlayerSide,
slot=PlayerSlot,
timestamp=TNow,
}
end
return self
end
end
if data.id==EVENTS.PlayerLeaveUnit and self.KnownPilots[name]then
self:T(self.lid.."Pilot Leaving: "..name.." | UCID: "..ucid)
self:__PlayerLeft(1,data.IniUnit,name)
self.KnownPilots[name]=false
return self
end
if data.id==EVENTS.Ejection and self.KnownPilots[name]then
self:T(self.lid.."Pilot Ejecting: "..name.." | UCID: "..ucid)
self:__PlayerEjected(1,data.IniUnit,name)
self.KnownPilots[name]=false
return self
end
if(data.id==EVENTS.PilotDead or data.id==EVENTS.SelfKillPilot or data.id==EVENTS.Crash)and self.KnownPilots[name]then
self:T(self.lid.."Pilot Dead: "..name.." | UCID: "..ucid)
self:__PlayerDied(1,data.IniUnit,name)
self.KnownPilots[name]=false
return self
end
end
return self
end
function NET:BlockPlayer(Client,PlayerName,Seconds,Message)
self:T({PlayerName,Seconds,Message})
local name=PlayerName
if Client and(not PlayerName)then
name=Client:GetPlayerName()
elseif PlayerName then
name=PlayerName
else
self:F(self.lid.."Block: No Client or PlayerName given or nothing found!")
return self
end
local ucid=self:GetPlayerUCID(Client,name)
local addon=Seconds or self.BlockTime
self.BlockedPilots[name]=timer.getTime()+addon
self.BlockedUCIDs[ucid]=timer.getTime()+addon
local message=Message or self.BlockMessage
if name then
self:SendChatToPlayer(message,name)
else
self:SendChat(name..": "..message)
end
self:__PlayerBlocked(1,Client,name,Seconds)
local PlayerID=self:GetPlayerIDByName(name)
if PlayerID and tonumber(PlayerID)~=1 then
local outcome=net.force_player_slot(tonumber(PlayerID),0,'')
end
return self
end
function NET:BlockPlayerSet(PlayerSet,Seconds,Message)
self:T({PlayerSet.Set,Seconds,Message})
local addon=Seconds or self.BlockTime
local message=Message or self.BlockMessage
for _,_client in pairs(PlayerSet.Set)do
local name=_client:GetPlayerName()
self:BlockPlayer(_client,name,addon,message)
end
return self
end
function NET:UnblockPlayerSet(PlayerSet,Message)
self:T({PlayerSet.Set,Seconds,Message})
local message=Message or self.UnblockMessage
for _,_client in pairs(PlayerSet.Set)do
local name=_client:GetPlayerName()
self:UnblockPlayer(_client,name,message)
end
return self
end
function NET:BlockUCID(ucid,Seconds)
self:T({ucid,Seconds})
local addon=Seconds or self.BlockTime
self.BlockedUCIDs[ucid]=timer.getTime()+addon
return self
end
function NET:UnblockUCID(ucid)
self:T({ucid})
self.BlockedUCIDs[ucid]=nil
return self
end
function NET:BlockSide(Side,Seconds)
local addon=Seconds or self.BlockTime
if Side==1 or Side==2 then
self.BlockedSides[Side]=timer.getTime()+addon
end
return self
end
function NET:UnblockSide(Side,Seconds)
local addon=Seconds or self.BlockTime
if Side==1 or Side==2 then
self.BlockedSides[Side]=false
end
return self
end
function NET:BlockSlot(Slot,Seconds)
self:T({Slot,Seconds})
local addon=Seconds or self.BlockTime
self.BlockedSlots[Slot]=timer.getTime()+addon
return self
end
function NET:UnblockSlot(Slot)
self:T({Slot})
self.BlockedSlots[Slot]=nil
return self
end
function NET:UnblockPlayer(Client,PlayerName,Message)
local name=PlayerName
if Client then
name=Client:GetPlayerName()
elseif PlayerName then
name=PlayerName
else
self:F(self.lid.."Unblock: No PlayerName given or not found!")
return self
end
local ucid=self:GetPlayerUCID(Client,name)
self.BlockedPilots[name]=nil
self.BlockedUCIDs[ucid]=nil
local message=Message or self.UnblockMessage
if name then
self:SendChatToPlayer(message,name)
else
self:SendChat(name..": "..message)
end
self:__PlayerUnblocked(1,Client,name)
return self
end
function NET:SetBlockMessage(Text)
self.BlockMessage=Text or"You are blocked from joining. Wait time is: "..self.BlockTime.." seconds!"
return self
end
function NET:SetBlockTime(Seconds)
self.BlockTime=Seconds or 600
return self
end
function NET:SetUnblockMessage(Text)
self.UnblockMessage=Text or"You are unblocked now and can join again."
return self
end
function NET:SendChat(Message,ToAll)
if Message then
net.send_chat(Message,ToAll)
end
return self
end
function NET:GetPlayerIDByName(Name)
if not Name then return nil end
local playerList=net.get_player_list()
for i=1,#playerList do
local playerName=net.get_name(i)
if playerName==Name then
return playerList[i]
end
end
return nil
end
function NET:GetPlayerIDFromClient(Client)
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
return nil
end
end
function NET:SendChatToClient(Message,ToClient,FromClient)
local PlayerId=self:GetPlayerIDFromClient(ToClient)
local FromId=self:GetPlayerIDFromClient(FromClient)
if Message and PlayerId and FromId then
net.send_chat_to(Message,tonumber(PlayerId),tonumber(FromId))
elseif Message and PlayerId then
net.send_chat_to(Message,tonumber(PlayerId))
end
return self
end
function NET:SendChatToPlayer(Message,ToPlayer,FromPlayer)
local PlayerId=self:GetPlayerIDByName(ToPlayer)
local FromId=self:GetPlayerIDByName(FromPlayer)
if Message and PlayerId and FromId then
net.send_chat_to(Message,tonumber(PlayerId),tonumber(FromId))
elseif Message and PlayerId then
net.send_chat_to(Message,tonumber(PlayerId))
end
return self
end
function NET:GetPlayerList()
local plist=nil
plist=net.get_player_list()
return plist
end
function NET:GetMyPlayerID()
return net.get_my_player_id()
end
function NET:GetServerID()
return net.get_server_id()
end
function NET:GetPlayerInfo(Client,Attribute)
local PlayerID=self:GetPlayerIDFromClient(Client)
if PlayerID then
return net.get_player_info(tonumber(PlayerID),Attribute)
else
return nil
end
end
function NET:GetPlayerUCID(Client,Name)
local PlayerID=nil
if Client then
PlayerID=self:GetPlayerIDFromClient(Client)
elseif Name then
PlayerID=self:GetPlayerIDByName(Name)
else
self:E(self.lid.."Neither client nor name provided!")
end
local ucid=net.get_player_info(tonumber(PlayerID),'ucid')
return ucid
end
function NET:Kick(Client,Message)
local PlayerID=self:GetPlayerIDFromClient(Client)
if PlayerID and tonumber(PlayerID)~=1 then
return net.kick(tonumber(PlayerID),Message)
else
return false
end
end
function NET:GetPlayerStatistic(Client,StatisticID)
local PlayerID=self:GetPlayerIDFromClient(Client)
local stats=StatisticID or 0
if stats>7 or stats<0 then stats=0 end
if PlayerID then
return net.get_stat(tonumber(PlayerID),stats)
else
return nil
end
end
function NET:GetName(Client)
local PlayerID=self:GetPlayerIDFromClient(Client)
if PlayerID then
return net.get_name(tonumber(PlayerID))
else
return nil
end
end
function NET:GetSlot(Client)
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
end
end
function NET:ForceSlot(Client,SideID,SlotID)
local PlayerID=self:GetPlayerIDFromClient(Client)
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)
return net.lua2json(Lua)
end
function NET.Json2Lua(Json)
return net.json2lua(Json)
end
function NET:DoStringIn(State,DoString)
return net.dostring_in(State,DoString)
end
function NET:Log(Message)
net.log(Message)
return self
end
function NET:GetKnownPilotData(Client,Name)
local name=Name
if Client and not Name then
name=Client:GetPlayerName()
end
if name then
return self.KnownPilots[name]
else
return nil
end
end
function NET:onafterStatus(From,Event,To)
self:T({From,Event,To})
local function HouseHold(tavolo)
local TNow=timer.getTime()
for _,entry in pairs(tavolo)do
if type(entry)=="number"and entry>=TNow then entry=false end
end
end
HouseHold(self.BlockedPilots)
HouseHold(self.BlockedSides)
HouseHold(self.BlockedSlots)
HouseHold(self.BlockedUCIDs)
if self:Is("Running")then
self:__Status(-60)
end
return self
end
function NET:onafterRun(From,Event,To)
self:T({From,Event,To})
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.SelfKillPilot,self._EventHandler)
self:__Status(-10)
end
function NET:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.SelfKillPilot)
return self
end
end
STORAGE={
ClassName="STORAGE",
verbose=0,
}
STORAGE.Liquid={
JETFUEL=0,
GASOLINE=1,
MW50=2,
DIESEL=3,
}
STORAGE.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 and self.airbase then
self.warehouse=self.airbase:getWarehouse()
end
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)
local storage=_DATABASE:FindStorage(AirbaseName)
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)
self:T(self.lid..string.format("Adding %d items of %s",Amount,UTILS.OneLineSerialize(Name)))
self.warehouse:addItem(Name,Amount)
return self
end
function STORAGE:SetItem(Name,Amount)
self:T(self.lid..string.format("Setting item %s to N=%d",UTILS.OneLineSerialize(Name),Amount))
self.warehouse:setItem(Name,Amount)
return self
end
function STORAGE:GetItemAmount(Name)
local N=self.warehouse:getItemCount(Name)
return N
end
function STORAGE:RemoveItem(Name,Amount)
self:T(self.lid..string.format("Removing N=%d of item %s",Amount,Name))
self.warehouse:removeItem(Name,Amount)
return self
end
function STORAGE:AddLiquid(Type,Amount)
self:T(self.lid..string.format("Adding %d liquids of %s",Amount,self:GetLiquidName(Type)))
self.warehouse:addLiquid(Type,Amount)
return self
end
function STORAGE:SetLiquid(Type,Amount)
self:T(self.lid..string.format("Setting liquid %s to N=%d",self:GetLiquidName(Type),Amount))
self.warehouse:setLiquidAmount(Type,Amount)
return self
end
function STORAGE:RemoveLiquid(Type,Amount)
self:T(self.lid..string.format("Removing N=%d of liquid %s",Amount,self:GetLiquidName(Type)))
self.warehouse:removeLiquid(Type,Amount)
return self
end
function STORAGE:GetLiquidAmount(Type)
local N=self.warehouse:getLiquidAmount(Type)
return N
end
function STORAGE:GetLiquidName(Type)
local name="Unknown"
if Type==STORAGE.Liquid.JETFUEL then
name="Jet fuel"
elseif Type==STORAGE.Liquid.GASOLINE then
name="Aircraft gasoline"
elseif Type==STORAGE.Liquid.MW50 then
name="MW 50"
elseif Type==STORAGE.Liquid.DIESEL then
name="Diesel"
else
self:E(self.lid..string.format("ERROR: Unknown liquid type %s",tostring(Type)))
end
return name
end
function STORAGE:AddAmount(Type,Amount)
if type(Type)=="number"then
self:AddLiquid(Type,Amount)
else
self:AddItem(Type,Amount)
end
return self
end
function STORAGE:RemoveAmount(Type,Amount)
if type(Type)=="number"then
self:RemoveLiquid(Type,Amount)
else
self:RemoveItem(Type,Amount)
end
return self
end
function STORAGE:SetAmount(Type,Amount)
if type(Type)=="number"then
self:SetLiquid(Type,Amount)
else
self:SetItem(Type,Amount)
end
return self
end
function STORAGE:GetAmount(Type)
local N=0
if type(Type)=="number"then
N=self:GetLiquidAmount(Type)
else
N=self:GetItemAmount(Type)
end
return N
end
function STORAGE:IsUnlimited(Type)
local N=self:GetAmount(Type)
local unlimited=false
if N>0 then
self:RemoveAmount(Type,1)
local n=self:GetAmount(Type)
unlimited=unlimited or n>2^29 or n==N
if not unlimited then
self:AddAmount(Type,1)
end
self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)",tostring(Type),tostring(unlimited),N,n))
end
return unlimited
end
function STORAGE:IsLimited(Type)
local limited=not self:IsUnlimited(Type)
return limited
end
function STORAGE:IsUnlimitedAircraft()
local unlimited=self:IsUnlimited("A-10C")
return unlimited
end
function STORAGE:IsUnlimitedLiquids()
local unlimited=self:IsUnlimited(STORAGE.Liquid.DIESEL)
return unlimited
end
function STORAGE:IsUnlimitedWeapons()
local unlimited=self:IsUnlimited(ENUMS.Storage.weapons.bombs.Mk_82)
return unlimited
end
function STORAGE:IsLimitedAircraft()
local limited=self:IsLimited("A-10C")
return limited
end
function STORAGE:IsLimitedLiquids()
local limited=self:IsLimited(STORAGE.Liquid.DIESEL)
return limited
end
function STORAGE:IsLimitedWeapons()
local limited=self:IsLimited(ENUMS.Storage.weapons.bombs.Mk_82)
return limited
end
function STORAGE:GetInventory(Item)
local inventory=self.warehouse:getInventory(Item)
return inventory.aircraft,inventory.liquids,inventory.weapon
end
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",
}
DYNAMICCARGO.AircraftDimensions={
["CH-47Fbl1"]={
["width"]=4,
["height"]=6,
["length"]=11,
["ropelength"]=30,
},
}
DYNAMICCARGO.version="0.0.7"
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((delta2D<dimensions.length and delta2D<dimensions.width and helolanded)or(delta3D==dimensions.ropelength and helo:InAir()))then
success=true
Helo=helo
Playername=name
end
end
end
return success,Helo,Playername
end
function DYNAMICCARGO:_UpdatePosition()
self:T(self.lid.." _UpdatePositionAndState")
if self:IsAlive()then
local pos=self:GetCoordinate()
if self.testing then
self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z))
self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z))
end
if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2)>0.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()end
self:T(self.lid.." dead! "..self.CargoState.."-> REMOVED")
self.CargoState=DYNAMICCARGO.State.REMOVED
_DATABASE:CreateEventDynamicCargoRemoved(self)
end
return self
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
SCORING={
ClassName="SCORING",
ClassID=0,
Players={},
AutoSave=true,
version="1.18.4"
}
local _SCORINGCoalition={
[1]="Red",
[2]="Blue",
}
local _SCORINGCategory={
[Unit.Category.AIRPLANE]="Plane",
[Unit.Category.HELICOPTER]="Helicopter",
[Unit.Category.GROUND_UNIT]="Vehicle",
[Unit.Category.SHIP]="Ship",
[Unit.Category.STRUCTURE]="Structure",
}
function SCORING:New(GameName,SavePath,AutoSave)
local self=BASE:Inherit(self,BASE:New())
if GameName then
self.GameName=GameName
else
error("A game name must be given to register the scoring results")
end
self.ScoringObjects={}
self.ScoringZones={}
self:SetMessagesToAll()
self:SetMessagesHit(false)
self:SetMessagesDestroy(true)
self:SetMessagesScore(true)
self:SetMessagesZone(true)
self:SetScaleDestroyScore(10)
self:SetScaleDestroyPenalty(30)
self:SetScoreIncrementOnHit(0)
self:SetFratricide(self.ScaleDestroyPenalty*3)
self.penaltyonfratricide=true
self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty)
self.penaltyoncoalitionchange=true
self:SetDisplayMessagePrefix()
self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash)
self:HandleEvent(EVENTS.Hit,self._EventOnHit)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.PlayerLeaveUnit)
self.ScoringPlayerScan=BASE:ScheduleOnce(1,function()
for PlayerName,PlayerUnit in pairs(_DATABASE:GetPlayerUnits())do
self:_AddPlayerFromUnit(PlayerUnit)
self:SetScoringMenu(PlayerUnit:GetGroup())
end
end)
self.AutoSavePath=SavePath
self.AutoSave=AutoSave or true
self:OpenCSV(GameName)
return self
end
function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix)
self.DisplayMessagePrefix=DisplayMessagePrefix or""
return self
end
function SCORING:SetScaleDestroyScore(Scale)
self.ScaleDestroyScore=Scale
return self
end
function SCORING:SetScaleDestroyPenalty(Scale)
self.ScaleDestroyPenalty=Scale
return self
end
function SCORING:AddUnitScore(ScoreUnit,Score)
local UnitName=ScoreUnit:GetName()
self.ScoringObjects[UnitName]=Score
return self
end
function SCORING:RemoveUnitScore(ScoreUnit)
local UnitName=ScoreUnit:GetName()
self.ScoringObjects[UnitName]=nil
return self
end
function SCORING:AddStaticScore(ScoreStatic,Score)
local StaticName=ScoreStatic:GetName()
self.ScoringObjects[StaticName]=Score
return self
end
function SCORING:RemoveStaticScore(ScoreStatic)
local StaticName=ScoreStatic:GetName()
self.ScoringObjects[StaticName]=nil
return self
end
function SCORING:AddScoreGroup(ScoreGroup,Score)
local ScoreUnits=ScoreGroup:GetUnits()
for ScoreUnitID,ScoreUnit in pairs(ScoreUnits)do
local UnitName=ScoreUnit:GetName()
self.ScoringObjects[UnitName]=Score
end
return self
end
function SCORING:AddScoreSetGroup(Set,Score)
local set=Set:GetSetObjects()
for _,_group in pairs(set)do
if _group and _group:IsAlive()then
self:AddScoreGroup(_group,Score)
end
end
local function AddScore(group)
self:AddScoreGroup(group,Score)
end
function Set:OnAfterAdded(From,Event,To,ObjectName,Object)
AddScore(Object)
end
return self
end
function SCORING:AddZoneScore(ScoreZone,Score)
local ZoneName=ScoreZone:GetName()
self.ScoringZones[ZoneName]={}
self.ScoringZones[ZoneName].ScoreZone=ScoreZone
self.ScoringZones[ZoneName].Score=Score
return self
end
function SCORING:RemoveZoneScore(ScoreZone)
local ZoneName=ScoreZone:GetName()
self.ScoringZones[ZoneName]=nil
return self
end
function SCORING:SetMessagesHit(OnOff)
self.MessagesHit=OnOff
return self
end
function SCORING:SetScoreIncrementOnHit(score)
self.ScoreIncrementOnHit=score
return self
end
function SCORING:IfMessagesHit()
return self.MessagesHit
end
function SCORING:SetMessagesDestroy(OnOff)
self.MessagesDestroy=OnOff
return self
end
function SCORING:IfMessagesDestroy()
return self.MessagesDestroy
end
function SCORING:SetMessagesScore(OnOff)
self.MessagesScore=OnOff
return self
end
function SCORING:IfMessagesScore()
return self.MessagesScore
end
function SCORING:SetMessagesZone(OnOff)
self.MessagesZone=OnOff
return self
end
function SCORING:IfMessagesZone()
return self.MessagesZone
end
function SCORING:SetMessagesToAll()
self.MessagesAudience=1
return self
end
function SCORING:IfMessagesToAll()
return self.MessagesAudience==1
end
function SCORING:SetMessagesToCoalition()
self.MessagesAudience=2
return self
end
function SCORING:IfMessagesToCoalition()
return self.MessagesAudience==2
end
function SCORING:SetFratricide(Fratricide)
self.Fratricide=Fratricide
return self
end
function SCORING:SwitchFratricide(OnOff)
self.penaltyonfratricide=OnOff
return self
end
function SCORING:SwitchTreason(OnOff)
self.penaltyoncoalitionchange=OnOff
return self
end
function SCORING:SetCoalitionChangePenalty(CoalitionChangePenalty)
self.CoalitionChangePenalty=CoalitionChangePenalty
return self
end
function SCORING:SetScoringMenu(ScoringGroup)
local Menu=MENU_GROUP:New(ScoringGroup,'Scoring and Statistics')
local ReportGroupSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report players in group',Menu,SCORING.ReportScoreGroupSummary,self,ScoringGroup)
local ReportGroupDetailed=MENU_GROUP_COMMAND:New(ScoringGroup,'Detailed report players in group',Menu,SCORING.ReportScoreGroupDetailed,self,ScoringGroup)
local ReportToAllSummary=MENU_GROUP_COMMAND:New(ScoringGroup,'Summary report all players',Menu,SCORING.ReportScoreAllSummary,self,ScoringGroup)
self:SetState(ScoringGroup,"ScoringMenu",Menu)
return self
end
function SCORING:_AddPlayerFromUnit(UnitData)
self:F(UnitData)
if UnitData:IsAlive()then
local UnitName=UnitData:GetName()
local PlayerName=UnitData:GetPlayerName()
local UnitDesc=UnitData:GetDesc()
local UnitCategory=UnitDesc.category
local UnitCoalition=UnitData:GetCoalition()
local UnitTypeName=UnitData:GetTypeName()
local UnitThreatLevel,UnitThreatType=UnitData:GetThreatLevel()
self:T({PlayerName,UnitName,UnitCategory,UnitCoalition,UnitTypeName})
if self.Players[PlayerName]==nil then
self.Players[PlayerName]={}
self.Players[PlayerName].Hit={}
self.Players[PlayerName].Destroy={}
self.Players[PlayerName].Goals={}
self.Players[PlayerName].Mission={}
self.Players[PlayerName].HitPlayers={}
self.Players[PlayerName].Score=0
self.Players[PlayerName].Penalty=0
self.Players[PlayerName].PenaltyCoalition=0
self.Players[PlayerName].PenaltyWarning=0
end
if not self.Players[PlayerName].UnitCoalition then
self.Players[PlayerName].UnitCoalition=UnitCoalition
else
if self.Players[PlayerName].UnitCoalition~=UnitCoalition and self.penaltyoncoalitionchange then
self.Players[PlayerName].Penalty=self.Players[PlayerName].Penalty+self.CoalitionChangePenalty or 50
self.Players[PlayerName].PenaltyCoalition=self.Players[PlayerName].PenaltyCoalition+1
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' changed coalition from ".._SCORINGCoalition[self.Players[PlayerName].UnitCoalition].." to ".._SCORINGCoalition[UnitCoalition]..
"(changed "..self.Players[PlayerName].PenaltyCoalition.." times the coalition). "..self.CoalitionChangePenalty.." penalty points added.",
MESSAGE.Type.Information
):ToAll()
self:ScoreCSV(PlayerName,"","COALITION_PENALTY",1,-1*self.CoalitionChangePenalty,self.Players[PlayerName].UnitName,_SCORINGCoalition[self.Players[PlayerName].UnitCoalition],_SCORINGCategory[self.Players[PlayerName].UnitCategory],self.Players[PlayerName].UnitType,
UnitName,_SCORINGCoalition[UnitCoalition],_SCORINGCategory[UnitCategory],UnitData:GetTypeName())
end
end
self.Players[PlayerName].UnitName=UnitName
self.Players[PlayerName].UnitCoalition=UnitCoalition
self.Players[PlayerName].UnitCategory=UnitCategory
self.Players[PlayerName].UnitType=UnitTypeName
self.Players[PlayerName].UNIT=UnitData
self.Players[PlayerName].ThreatLevel=UnitThreatLevel
self.Players[PlayerName].ThreatType=UnitThreatType
if self.Players[PlayerName].Penalty>self.Fratricide*0.50 and self.penaltyonfratricide then
if self.Players[PlayerName].PenaltyWarning<1 then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than "..self.Fratricide..", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: "..self.Players[PlayerName].Penalty,
MESSAGE.Type.Information)
:ToAll()
self.Players[PlayerName].PenaltyWarning=self.Players[PlayerName].PenaltyWarning+1
end
end
if self.Players[PlayerName].Penalty>self.Fratricide and self.penaltyonfratricide then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
MESSAGE.Type.Information)
:ToAll()
UnitData:GetGroup():Destroy()
end
end
end
function SCORING:AddGoalScorePlayer(PlayerName,GoalTag,Text,Score)
self:F({PlayerName,PlayerName,GoalTag,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0}
PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score
PlayerData.Score=PlayerData.Score+Score
if Text then
MESSAGE:NewType(self.DisplayMessagePrefix..Text,
MESSAGE.Type.Information)
:ToAll()
end
self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,nil)
end
end
function SCORING:AddGoalScore(PlayerUnit,GoalTag,Text,Score)
local PlayerName=PlayerUnit:GetPlayerName()
self:T2({PlayerUnit.UnitName,PlayerName,GoalTag,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
PlayerData.Goals[GoalTag]=PlayerData.Goals[GoalTag]or{Score=0}
PlayerData.Goals[GoalTag].Score=PlayerData.Goals[GoalTag].Score+Score
PlayerData.Score=PlayerData.Score+Score
if Text then
MESSAGE:NewType(self.DisplayMessagePrefix..Text,
MESSAGE.Type.Information)
:ToAll()
end
self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,PlayerUnit:GetName())
end
end
function SCORING:_AddMissionTaskScore(Mission,PlayerUnit,Text,Score)
local PlayerName=PlayerUnit:GetPlayerName()
local MissionName=Mission:GetName()
self:F({Mission:GetName(),PlayerUnit.UnitName,PlayerName,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
if not PlayerData.Mission[MissionName]then
PlayerData.Mission[MissionName]={}
PlayerData.Mission[MissionName].ScoreTask=0
PlayerData.Mission[MissionName].ScoreMission=0
end
self:T(PlayerName)
self:T(PlayerData.Mission[MissionName])
PlayerData.Score=self.Players[PlayerName].Score+Score
PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score
if Text then
MESSAGE:NewType(self.DisplayMessagePrefix..Mission:GetText().." : "..Text.." Score: "..Score,
MESSAGE.Type.Information)
:ToAll()
end
self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score,PlayerUnit:GetName())
end
end
function SCORING:_AddMissionGoalScore(Mission,PlayerName,Text,Score)
local MissionName=Mission:GetName()
self:F({Mission:GetName(),PlayerName,Text,Score})
if PlayerName then
local PlayerData=self.Players[PlayerName]
if not PlayerData.Mission[MissionName]then
PlayerData.Mission[MissionName]={}
PlayerData.Mission[MissionName].ScoreTask=0
PlayerData.Mission[MissionName].ScoreMission=0
end
self:T(PlayerName)
self:T(PlayerData.Mission[MissionName])
PlayerData.Score=self.Players[PlayerName].Score+Score
PlayerData.Mission[MissionName].ScoreTask=self.Players[PlayerName].Mission[MissionName].ScoreTask+Score
if Text then
MESSAGE:NewType(string.format("%s%s: %s! Player %s receives %d score!",self.DisplayMessagePrefix,Mission:GetText(),Text,PlayerName,Score),MESSAGE.Type.Information):ToAll()
end
self:ScoreCSV(PlayerName,"","TASK_"..MissionName:gsub(' ','_'),1,Score)
end
end
function SCORING:_AddMissionScore(Mission,Text,Score)
local MissionName=Mission:GetName()
self:F({Mission,Text,Score})
self:F(self.Players)
for PlayerName,PlayerData in pairs(self.Players)do
self:F(PlayerData)
if PlayerData.Mission[MissionName]then
PlayerData.Score=PlayerData.Score+Score
PlayerData.Mission[MissionName].ScoreMission=PlayerData.Mission[MissionName].ScoreMission+Score
if Text then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' has "..Text.." in "..Mission:GetText()..". "..Score.." mission score!",
MESSAGE.Type.Information)
:ToAll()
end
self:ScoreCSV(PlayerName,"","MISSION_"..MissionName:gsub(' ','_'),1,Score)
end
end
end
function SCORING:OnEventBirth(Event)
if Event.IniUnit then
Event.IniUnit.ThreatLevel,Event.IniUnit.ThreatType=Event.IniUnit:GetThreatLevel()
if Event.IniObjectCategory==1 then
local PlayerName=Event.IniUnit:GetPlayerName()
Event.IniUnit.BirthTime=timer.getTime()
if PlayerName then
self:_AddPlayerFromUnit(Event.IniUnit)
self.Players[PlayerName].PlayerKills=0
self:SetScoringMenu(Event.IniGroup)
end
end
end
end
function SCORING:OnEventPlayerLeaveUnit(Event)
if Event.IniUnit then
local Menu=self:GetState(Event.IniUnit:GetGroup(),"ScoringMenu")
if Menu then
end
end
end
function SCORING:_EventOnHit(Event)
self:F({Event})
local InitUnit=nil
local InitUNIT=nil
local InitUnitName=""
local InitGroup=nil
local InitGroupName=""
local InitPlayerName=nil
local InitCoalition=nil
local InitCategory=nil
local InitType=nil
local InitUnitCoalition=nil
local InitUnitCategory=nil
local InitUnitType=nil
local TargetUnit=nil
local TargetUNIT=nil
local TargetUnitName=""
local TargetGroup=nil
local TargetGroupName=""
local TargetPlayerName=nil
local TargetCoalition=nil
local TargetCategory=nil
local TargetType=nil
local TargetUnitCoalition=nil
local TargetUnitCategory=nil
local TargetUnitType=nil
if Event.IniDCSUnit then
InitUnit=Event.IniDCSUnit
InitUNIT=Event.IniUnit
InitUnitName=Event.IniDCSUnitName
InitGroup=Event.IniDCSGroup
InitGroupName=Event.IniDCSGroupName
InitPlayerName=Event.IniPlayerName
InitCoalition=Event.IniCoalition
InitCategory=Event.IniCategory
InitType=Event.IniTypeName
InitUnitCoalition=_SCORINGCoalition[InitCoalition]
InitUnitCategory=_SCORINGCategory[InitCategory]
InitUnitType=InitType
self:T({InitUnitName,InitGroupName,InitPlayerName,InitCoalition,InitCategory,InitType,InitUnitCoalition,InitUnitCategory,InitUnitType})
end
if Event.TgtDCSUnit then
TargetUnit=Event.TgtDCSUnit
TargetUNIT=Event.TgtUnit
TargetUnitName=Event.TgtDCSUnitName
TargetGroup=Event.TgtDCSGroup
TargetGroupName=Event.TgtDCSGroupName
TargetPlayerName=Event.TgtPlayerName
TargetCoalition=Event.TgtCoalition
TargetCategory=Event.TgtCategory
TargetType=Event.TgtTypeName
TargetUnitCoalition=_SCORINGCoalition[TargetCoalition]
TargetUnitCategory=_SCORINGCategory[TargetCategory]
TargetUnitType=TargetType
self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType,TargetUnitCoalition,TargetUnitCategory,TargetUnitType})
end
if InitPlayerName~=nil then
self:_AddPlayerFromUnit(InitUNIT)
if self.Players[InitPlayerName]then
if TargetPlayerName~=nil then
self:_AddPlayerFromUnit(TargetUNIT)
end
self:T("Hitting Something")
if TargetCategory then
local Player=self.Players[InitPlayerName]
Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{}
Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{}
local PlayerHit=Player.Hit[TargetCategory][TargetUnitName]
PlayerHit.Score=PlayerHit.Score or 0
PlayerHit.Penalty=PlayerHit.Penalty or 0
PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0
PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT
if PlayerHit.UNIT.ThreatType==nil then
PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel()
if PlayerHit.ThreatType==nil or PlayerHit.ThreatType==""then
PlayerHit.ThreatLevel=1
PlayerHit.ThreatType="Unknown"
end
else
PlayerHit.ThreatLevel=PlayerHit.UNIT.ThreatLevel
PlayerHit.ThreatType=PlayerHit.UNIT.ThreatType
end
if timer.getTime()-PlayerHit.TimeStamp>1 then
PlayerHit.TimeStamp=timer.getTime()
if TargetPlayerName~=nil then
Player.HitPlayers[TargetPlayerName]=true
end
local Score=0
if InitCoalition then
if InitCoalition==TargetCoalition then
local Penalty=10
Player.Penalty=Player.Penalty+Penalty
PlayerHit.Penalty=PlayerHit.Penalty+Penalty
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1
if TargetPlayerName~=nil then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. "..
"Penalty: -"..Penalty..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit friendly target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.PenaltyHit.." times. "..
"Penalty: -"..Penalty..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
end
self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
else
Player.Score=Player.Score+self.ScoreIncrementOnHit
PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit
PlayerHit.ScoreHit=PlayerHit.ScoreHit+1
if TargetPlayerName~=nil then
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. "..
"Score: "..PlayerHit.Score..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) "..PlayerHit.ScoreHit.." times. "..
"Score: "..PlayerHit.Score..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
end
self:ScoreCSV(InitPlayerName,TargetPlayerName,"HIT_SCORE",1,1,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
end
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..InitPlayerName.."' hit scenery object.",
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(InitPlayerName,"","HIT_SCORE",1,0,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType)
end
end
end
end
elseif InitPlayerName==nil then
end
if Event.WeaponPlayerName~=nil then
self:_AddPlayerFromUnit(Event.WeaponUNIT)
if self.Players[Event.WeaponPlayerName]then
if TargetPlayerName~=nil then
self:_AddPlayerFromUnit(TargetUNIT)
end
self:T("Hitting Scenery")
if TargetCategory then
local Player=self.Players[Event.WeaponPlayerName]
Player.Hit[TargetCategory]=Player.Hit[TargetCategory]or{}
Player.Hit[TargetCategory][TargetUnitName]=Player.Hit[TargetCategory][TargetUnitName]or{}
local PlayerHit=Player.Hit[TargetCategory][TargetUnitName]
PlayerHit.Score=PlayerHit.Score or 0
PlayerHit.Penalty=PlayerHit.Penalty or 0
PlayerHit.ScoreHit=PlayerHit.ScoreHit or 0
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp=PlayerHit.TimeStamp or 0
PlayerHit.UNIT=PlayerHit.UNIT or TargetUNIT
if PlayerHit.UNIT.ThreatType==nil then
PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel()
if PlayerHit.ThreatType==nil then
PlayerHit.ThreatLevel=1
PlayerHit.ThreatType="Unknown"
end
else
PlayerHit.ThreatLevel=PlayerHit.UNIT.ThreatLevel
PlayerHit.ThreatType=PlayerHit.UNIT.ThreatType
end
if timer.getTime()-PlayerHit.TimeStamp>1 then
PlayerHit.TimeStamp=timer.getTime()
local Score=0
if InitCoalition then
if InitCoalition==TargetCoalition then
local Penalty=10
Player.Penalty=Player.Penalty+Penalty
PlayerHit.Penalty=PlayerHit.Penalty+Penalty
PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1*self.ScaleDestroyPenalty
MESSAGE
:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit friendly target "..
TargetUnitCategory.." ( "..TargetType.." ) "..
"Penalty: -"..Penalty.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Update
)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_PENALTY",1,-10,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
else
Player.Score=Player.Score+self.ScoreIncrementOnHit
PlayerHit.Score=PlayerHit.Score+self.ScoreIncrementOnHit
PlayerHit.ScoreHit=PlayerHit.ScoreHit+1
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit enemy target "..TargetUnitCategory.." ( "..TargetType.." ) "..
"Score: "..PlayerHit.Score..".  Score Total:"..Player.Score-Player.Penalty,
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(Event.WeaponCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(Event.WeaponPlayerName,TargetPlayerName,"HIT_SCORE",1,1,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
end
else
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit scenery object.",
MESSAGE.Type.Update)
:ToAllIf(self:IfMessagesHit()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesHit()and self:IfMessagesToCoalition())
self:ScoreCSV(Event.WeaponPlayerName,"","HIT_SCORE",1,0,Event.WeaponName,Event.WeaponCoalition,Event.WeaponCategory,Event.WeaponTypeName,TargetUnitName,"","Scenery",TargetUnitType)
end
end
end
end
end
end
function SCORING:_EventOnDeadOrCrash(Event)
self:F({Event})
local TargetUnit=nil
local TargetGroup=nil
local TargetUnitName=""
local TargetGroupName=""
local TargetPlayerName=""
local TargetCoalition=nil
local TargetCategory=nil
local TargetType=nil
local TargetUnitCoalition=nil
local TargetUnitCategory=nil
local TargetUnitType=nil
if Event.IniDCSUnit then
TargetUnit=Event.IniUnit
TargetUnitName=Event.IniDCSUnitName
TargetGroup=Event.IniDCSGroup
TargetGroupName=Event.IniDCSGroupName
TargetPlayerName=Event.IniPlayerName
TargetCoalition=Event.IniCoalition
TargetCategory=Event.IniCategory
TargetType=Event.IniTypeName
TargetUnitCoalition=_SCORINGCoalition[TargetCoalition]
TargetUnitCategory=_SCORINGCategory[TargetCategory]
TargetUnitType=TargetType
self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType})
end
for PlayerName,Player in pairs(self.Players)do
if Player then
self:T("Something got destroyed")
local InitUnitName=Player.UnitName
local InitUnitType=Player.UnitType
local InitCoalition=Player.UnitCoalition
local InitCategory=Player.UnitCategory
local InitUnitCoalition=_SCORINGCoalition[InitCoalition]
local InitUnitCategory=_SCORINGCategory[InitCategory]
self:T({InitUnitName,InitUnitType,InitUnitCoalition,InitCoalition,InitUnitCategory,InitCategory})
local Destroyed=false
if Player and Player.Hit and Player.Hit[TargetCategory]and Player.Hit[TargetCategory][TargetUnitName]and Player.Hit[TargetCategory][TargetUnitName].TimeStamp~=0 and(TargetUnit.BirthTime==nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp>TargetUnit.BirthTime)then
local TargetThreatLevel=Player.Hit[TargetCategory][TargetUnitName].ThreatLevel
local TargetThreatType=Player.Hit[TargetCategory][TargetUnitName].ThreatType
Player.Destroy[TargetCategory]=Player.Destroy[TargetCategory]or{}
Player.Destroy[TargetCategory][TargetType]=Player.Destroy[TargetCategory][TargetType]or{}
local TargetDestroy=Player.Destroy[TargetCategory][TargetType]
TargetDestroy.Score=TargetDestroy.Score or 0
TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy or 0
TargetDestroy.Penalty=TargetDestroy.Penalty or 0
TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy or 0
if TargetCoalition then
if InitCoalition==TargetCoalition then
local ThreatLevelTarget=TargetThreatLevel
local ThreatTypeTarget=TargetThreatType
local ThreatLevelPlayer=Player.ThreatLevel/10+1
local ThreatPenalty=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyPenalty/10)
self:F({ThreatLevel=ThreatPenalty,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer})
Player.Penalty=Player.Penalty+ThreatPenalty
TargetDestroy.Penalty=TargetDestroy.Penalty+ThreatPenalty
TargetDestroy.PenaltyDestroy=TargetDestroy.PenaltyDestroy+1
if Player.HitPlayers[TargetPlayerName]then
self:OnKillPvP(PlayerName,TargetPlayerName,true)
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Penalty: -"..ThreatPenalty.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
else
self:OnKillPvE(PlayerName,TargetUnitName,true,TargetThreatLevel,Player.ThreatLevel,ThreatPenalty)
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly target "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Penalty: -"..ThreatPenalty.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
end
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_PENALTY",1,ThreatPenalty,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
else
local ThreatLevelTarget=TargetThreatLevel
local ThreatTypeTarget=TargetThreatType
local ThreatLevelPlayer=Player.ThreatLevel/10+1
local ThreatScore=math.ceil((ThreatLevelTarget/ThreatLevelPlayer)*self.ScaleDestroyScore/10)
self:F({ThreatLevel=ThreatScore,ThreatLevelTarget=ThreatLevelTarget,ThreatTypeTarget=ThreatTypeTarget,ThreatLevelPlayer=ThreatLevelPlayer})
Player.Score=Player.Score+ThreatScore
TargetDestroy.Score=TargetDestroy.Score+ThreatScore
TargetDestroy.ScoreDestroy=TargetDestroy.ScoreDestroy+1
if Player.HitPlayers[TargetPlayerName]then
if Player.PlayerKills~=nil then
Player.PlayerKills=Player.PlayerKills+1
else
Player.PlayerKills=1
end
self:OnKillPvP(PlayerName,TargetPlayerName,false,TargetThreatLevel,Player.ThreatLevel,ThreatScore)
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy player '"..TargetPlayerName.."' "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Score: +"..ThreatScore.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
else
self:OnKillPvE(PlayerName,TargetUnitName,false,TargetThreatLevel,Player.ThreatLevel,ThreatScore)
MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy "..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) "..
"Score: +"..ThreatScore.." = "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition())
end
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,ThreatScore,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
local UnitName=TargetUnit:GetName()
local Score=self.ScoringObjects[UnitName]
if Score then
Player.Score=Player.Score+Score
TargetDestroy.Score=TargetDestroy.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix.."Special target '"..TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".." destroyed! "..
"Player '"..PlayerName.."' receives an extra "..Score.." points! Total: "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesScore()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesScore()and self:IfMessagesToCoalition())
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
end
for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do
self:F({ScoringZone=ScoreZoneData})
local ScoreZone=ScoreZoneData.ScoreZone
local Score=ScoreZoneData.Score
if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then
Player.Score=Player.Score+Score
TargetDestroy.Score=TargetDestroy.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix.."Target destroyed in zone '"..ScoreZone:GetName().."'."..
"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition())
self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
Destroyed=true
end
end
end
else
for ZoneName,ScoreZoneData in pairs(self.ScoringZones)do
self:F({ScoringZone=ScoreZoneData})
local ScoreZone=ScoreZoneData.ScoreZone
local Score=ScoreZoneData.Score
if ScoreZone:IsVec2InZone(TargetUnit:GetVec2())then
Player.Score=Player.Score+Score
TargetDestroy.Score=TargetDestroy.Score+Score
MESSAGE:NewType(self.DisplayMessagePrefix.."Scenery destroyed in zone '"..ScoreZone:GetName().."'."..
"Player '"..PlayerName.."' receives an extra "..Score.." points! ".."Total: "..Player.Score-Player.Penalty,
MESSAGE.Type.Information)
:ToAllIf(self:IfMessagesZone()and self:IfMessagesToAll())
:ToCoalitionIf(InitCoalition,self:IfMessagesZone()and self:IfMessagesToCoalition())
self:ScoreCSV(PlayerName,"","DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType)
Destroyed=true
end
end
end
if Destroyed then
Player.Hit[TargetCategory][TargetUnitName].TimeStamp=0
end
end
end
end
end
function SCORING:ReportDetailedPlayerHits(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageHits=""
for CategoryID,CategoryName in pairs(_SCORINGCategory)do
self:T(CategoryName)
if PlayerData.Hit[CategoryID]then
self:T("Hit scores exist for player "..PlayerName)
local Score=0
local ScoreHit=0
local Penalty=0
local PenaltyHit=0
for UnitName,UnitData in pairs(PlayerData.Hit[CategoryID])do
Score=Score+UnitData.Score
ScoreHit=ScoreHit+UnitData.ScoreHit
Penalty=Penalty+UnitData.Penalty
PenaltyHit=UnitData.PenaltyHit
end
local ScoreMessageHit=string.format("%s: %d  ",CategoryName,Score-Penalty)
self:T(ScoreMessageHit)
ScoreMessageHits=ScoreMessageHits..ScoreMessageHit
PlayerScore=PlayerScore+Score
PlayerPenalty=PlayerPenalty+Penalty
else
end
end
if ScoreMessageHits~=""then
ScoreMessage="Hits: "..ScoreMessageHits
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerDestroys(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageDestroys=""
for CategoryID,CategoryName in pairs(_SCORINGCategory)do
if PlayerData.Destroy[CategoryID]then
self:T("Destroy scores exist for player "..PlayerName)
local Score=0
local ScoreDestroy=0
local Penalty=0
local PenaltyDestroy=0
for UnitName,UnitData in pairs(PlayerData.Destroy[CategoryID])do
self:F({UnitData=UnitData})
if UnitData~={}then
Score=Score+UnitData.Score
ScoreDestroy=ScoreDestroy+UnitData.ScoreDestroy
Penalty=Penalty+UnitData.Penalty
PenaltyDestroy=PenaltyDestroy+UnitData.PenaltyDestroy
end
end
local ScoreMessageDestroy=string.format("  %s: %d  ",CategoryName,Score-Penalty)
self:T(ScoreMessageDestroy)
ScoreMessageDestroys=ScoreMessageDestroys..ScoreMessageDestroy
PlayerScore=PlayerScore+Score
PlayerPenalty=PlayerPenalty+Penalty
else
end
end
if ScoreMessageDestroys~=""then
ScoreMessage="Destroys: "..ScoreMessageDestroys
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerCoalitionChanges(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageCoalitionChangePenalties=""
if PlayerData.PenaltyCoalition~=0 then
ScoreMessageCoalitionChangePenalties=ScoreMessageCoalitionChangePenalties..string.format(" -%d (%d changed)",PlayerData.Penalty,PlayerData.PenaltyCoalition)
PlayerPenalty=PlayerPenalty+PlayerData.Penalty
end
if ScoreMessageCoalitionChangePenalties~=""then
ScoreMessage=ScoreMessage.."Coalition Penalties: "..ScoreMessageCoalitionChangePenalties
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerGoals(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageGoal=""
local ScoreGoal=0
local ScoreTask=0
for GoalName,GoalData in pairs(PlayerData.Goals)do
ScoreGoal=ScoreGoal+GoalData.Score
ScoreMessageGoal=ScoreMessageGoal.."'"..GoalName.."':"..GoalData.Score.."; "
end
PlayerScore=PlayerScore+ScoreGoal
if ScoreMessageGoal~=""then
ScoreMessage="Goals: "..ScoreMessageGoal
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportDetailedPlayerMissions(PlayerName)
local ScoreMessage=""
local PlayerScore=0
local PlayerPenalty=0
local PlayerData=self.Players[PlayerName]
if PlayerData then
self:T("Score Player: "..PlayerName)
local InitUnitCoalition=_SCORINGCoalition[PlayerData.UnitCoalition]
local InitUnitCategory=_SCORINGCategory[PlayerData.UnitCategory]
local InitUnitType=PlayerData.UnitType
local InitUnitName=PlayerData.UnitName
local ScoreMessageMission=""
local ScoreMission=0
local ScoreTask=0
for MissionName,MissionData in pairs(PlayerData.Mission)do
ScoreMission=ScoreMission+MissionData.ScoreMission
ScoreTask=ScoreTask+MissionData.ScoreTask
ScoreMessageMission=ScoreMessageMission.."'"..MissionName.."'; "
end
PlayerScore=PlayerScore+ScoreMission+ScoreTask
if ScoreMessageMission~=""then
ScoreMessage="Tasks: "..ScoreTask.." Mission: "..ScoreMission.." ( "..ScoreMessageMission..")"
end
end
return ScoreMessage,PlayerScore,PlayerPenalty
end
function SCORING:ReportScoreGroupSummary(PlayerGroup)
local PlayerMessage=""
self:T("Report Score Group Summary")
local PlayerUnits=PlayerGroup:GetUnits()
for UnitID,PlayerUnit in pairs(PlayerUnits)do
local PlayerUnit=PlayerUnit
local PlayerName=PlayerUnit:GetPlayerName()
if PlayerName then
local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
self:F({ReportHits,ScoreHits,PenaltyHits})
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys})
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
self:F({ReportGoals,ScoreGoals,PenaltyGoals})
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
self:F({ReportMissions,ScoreMissions,PenaltyMissions})
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions
PlayerMessage=string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore-PlayerPenalty,
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup)
end
end
end
function SCORING:ReportScoreGroupDetailed(PlayerGroup)
local PlayerMessage=""
self:T("Report Score Group Detailed")
local PlayerUnits=PlayerGroup:GetUnits()
for UnitID,PlayerUnit in pairs(PlayerUnits)do
local PlayerUnit=PlayerUnit
local PlayerName=PlayerUnit:GetPlayerName()
if PlayerName then
local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
self:F({ReportHits,ScoreHits,PenaltyHits})
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys})
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
self:F({ReportGoals,ScoreGoals,PenaltyGoals})
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
self:F({ReportMissions,ScoreMissions,PenaltyMissions})
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions
PlayerMessage=
string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
PlayerName,
PlayerScore-PlayerPenalty,
PlayerScore,
PlayerPenalty,
ReportHits,
ReportDestroys,
ReportCoalitionChanges,
ReportGoals,
ReportMissions
)
MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Detailed):ToGroup(PlayerGroup)
end
end
end
function SCORING:ReportScoreAllSummary(PlayerGroup)
local PlayerMessage=""
self:T({"Summary Score Report of All Players",Players=self.Players})
for PlayerName,PlayerData in pairs(self.Players)do
self:T({PlayerName=PlayerName,PlayerGroup=PlayerGroup})
if PlayerName then
local ReportHits,ScoreHits,PenaltyHits=self:ReportDetailedPlayerHits(PlayerName)
ReportHits=ReportHits~=""and"\n- "..ReportHits or ReportHits
self:F({ReportHits,ScoreHits,PenaltyHits})
local ReportDestroys,ScoreDestroys,PenaltyDestroys=self:ReportDetailedPlayerDestroys(PlayerName)
ReportDestroys=ReportDestroys~=""and"\n- "..ReportDestroys or ReportDestroys
self:F({ReportDestroys,ScoreDestroys,PenaltyDestroys})
local ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges=self:ReportDetailedPlayerCoalitionChanges(PlayerName)
ReportCoalitionChanges=ReportCoalitionChanges~=""and"\n- "..ReportCoalitionChanges or ReportCoalitionChanges
self:F({ReportCoalitionChanges,ScoreCoalitionChanges,PenaltyCoalitionChanges})
local ReportGoals,ScoreGoals,PenaltyGoals=self:ReportDetailedPlayerGoals(PlayerName)
ReportGoals=ReportGoals~=""and"\n- "..ReportGoals or ReportGoals
self:F({ReportGoals,ScoreGoals,PenaltyGoals})
local ReportMissions,ScoreMissions,PenaltyMissions=self:ReportDetailedPlayerMissions(PlayerName)
ReportMissions=ReportMissions~=""and"\n- "..ReportMissions or ReportMissions
self:F({ReportMissions,ScoreMissions,PenaltyMissions})
local PlayerScore=ScoreHits+ScoreDestroys+ScoreCoalitionChanges+ScoreGoals+ScoreMissions
local PlayerPenalty=PenaltyHits+PenaltyDestroys+PenaltyCoalitionChanges+PenaltyGoals+PenaltyMissions
PlayerMessage=
string.format("Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore-PlayerPenalty,
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType(PlayerMessage,MESSAGE.Type.Overview):ToGroup(PlayerGroup)
end
end
end
function SCORING:SecondsToClock(sSeconds)
local nSeconds=sSeconds
if nSeconds==0 then
return"00:00:00";
else
local nHours=string.format("%02.f",math.floor(nSeconds/3600));
local nMins=string.format("%02.f",math.floor(nSeconds/60-(nHours*60)));
local nSecs=string.format("%02.f",math.floor(nSeconds-nHours*3600-nMins*60));
return nHours..":"..nMins..":"..nSecs
end
end
function SCORING:OpenCSV(ScoringCSV)
self:F(ScoringCSV)
if lfs and io and os and self.AutoSave==true then
if ScoringCSV then
self.ScoringCSV=ScoringCSV
local path=self.AutoSavePath or lfs.writedir()..[[Logs\]]
local fdir=path..self.ScoringCSV.." "..os.date("%Y-%m-%d %H-%M-%S")..".csv"
self.CSVFile,self.err=io.open(fdir,"w+")
if not self.CSVFile then
error("Error: Cannot open CSV file in "..lfs.writedir())
end
self.CSVFile:write('"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoalition","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n')
self.RunTime=os.date("%y-%m-%d_%H-%M-%S")
else
error("A string containing the CSV file name must be given.")
end
else
self:F("The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used...")
end
return self
end
function SCORING:ScoreCSV(PlayerName,TargetPlayerName,ScoreType,ScoreTimes,ScoreAmount,PlayerUnitName,PlayerUnitCoalition,PlayerUnitCategory,PlayerUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType)
local ScoreTime=self:SecondsToClock(timer.getTime())
PlayerName=PlayerName:gsub('"','_')
TargetPlayerName=TargetPlayerName or""
TargetPlayerName=TargetPlayerName:gsub('"','_')
if PlayerUnitName and PlayerUnitName~=''then
local PlayerUnit=Unit.getByName(PlayerUnitName)
if PlayerUnit then
if not PlayerUnitCategory then
PlayerUnitCategory=_SCORINGCategory[PlayerUnit:getDesc().category]
end
if not PlayerUnitCoalition then
PlayerUnitCoalition=_SCORINGCoalition[PlayerUnit:getCoalition()]
end
if not PlayerUnitType then
PlayerUnitType=PlayerUnit:getTypeName()
end
else
PlayerUnitName=''
PlayerUnitCategory=''
PlayerUnitCoalition=''
PlayerUnitType=''
end
else
PlayerUnitName=''
PlayerUnitCategory=''
PlayerUnitCoalition=''
PlayerUnitType=''
end
TargetUnitCoalition=TargetUnitCoalition or""
TargetUnitCategory=TargetUnitCategory or""
TargetUnitType=TargetUnitType or""
TargetUnitName=TargetUnitName or""
if lfs and io and os and self.AutoSave then
self.CSVFile:write(
'"'..self.GameName..'"'..','..
'"'..self.RunTime..'"'..','..
''..ScoreTime..''..','..
'"'..PlayerName..'"'..','..
'"'..TargetPlayerName..'"'..','..
'"'..ScoreType..'"'..','..
'"'..PlayerUnitCoalition..'"'..','..
'"'..PlayerUnitCategory..'"'..','..
'"'..PlayerUnitType..'"'..','..
'"'..PlayerUnitName..'"'..','..
'"'..TargetUnitCoalition..'"'..','..
'"'..TargetUnitCategory..'"'..','..
'"'..TargetUnitType..'"'..','..
'"'..TargetUnitName..'"'..','..
''..ScoreTimes..''..','..
''..ScoreAmount
)
self.CSVFile:write("\n")
end
end
function SCORING:CloseCSV()
if lfs and io and os and self.AutoSave then
self.CSVFile:close()
end
end
function SCORING:SwitchAutoSave(OnOff)
self.AutoSave=OnOff
return self
end
function SCORING:OnKillPvP(PlayerName,TargetPlayerName,IsTeamKill,TargetThreatLevel,PlayerThreatLevel,Score)
end
function SCORING:OnKillPvE(PlayerName,TargetUnitName,IsTeamKill,TargetThreatLevel,PlayerThreatLevel,Score)
end
CLEANUP_AIRBASE={
ClassName="CLEANUP_AIRBASE",
TimeInterval=0.2,
CleanUpList={},
}
CLEANUP_AIRBASE.__={}
CLEANUP_AIRBASE.__.Airbases={}
function CLEANUP_AIRBASE:New(AirbaseNames)
local self=BASE:Inherit(self,BASE:New())
self:F({AirbaseNames})
if type(AirbaseNames)=='table'then
for AirbaseID,AirbaseName in pairs(AirbaseNames)do
self:AddAirbase(AirbaseName)
end
else
local AirbaseName=AirbaseNames
self:AddAirbase(AirbaseName)
end
self:HandleEvent(EVENTS.Birth,self.__.OnEventBirth)
self.__.CleanUpScheduler=SCHEDULER:New(self,self.__.CleanUpSchedule,{},1,self.TimeInterval)
self:HandleEvent(EVENTS.EngineShutdown,self.__.EventAddForCleanUp)
self:HandleEvent(EVENTS.EngineStartup,self.__.EventAddForCleanUp)
self:HandleEvent(EVENTS.Hit,self.__.EventAddForCleanUp)
self:HandleEvent(EVENTS.PilotDead,self.__.OnEventCrash)
self:HandleEvent(EVENTS.Dead,self.__.OnEventCrash)
self:HandleEvent(EVENTS.Crash,self.__.OnEventCrash)
for UnitName,Unit in pairs(_DATABASE.UNITS)do
local Unit=Unit
if Unit:IsAlive()~=nil then
if self:IsInAirbase(Unit:GetVec2())then
self:F({UnitName=UnitName})
self.CleanUpList[UnitName]={}
self.CleanUpList[UnitName].CleanUpUnit=Unit
self.CleanUpList[UnitName].CleanUpGroup=Unit:GetGroup()
self.CleanUpList[UnitName].CleanUpGroupName=Unit:GetGroup():GetName()
self.CleanUpList[UnitName].CleanUpUnitName=Unit:GetName()
end
end
end
return self
end
function CLEANUP_AIRBASE:AddAirbase(AirbaseName)
self.__.Airbases[AirbaseName]=AIRBASE:FindByName(AirbaseName)
self:F({"Airbase:",AirbaseName,self.__.Airbases[AirbaseName]:GetDesc()})
return self
end
function CLEANUP_AIRBASE:RemoveAirbase(AirbaseName)
self.__.Airbases[AirbaseName]=nil
return self
end
function CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles)
if CleanMissiles then
self:HandleEvent(EVENTS.Shot,self.__.OnEventShot)
else
self:UnHandleEvent(EVENTS.Shot)
end
end
function CLEANUP_AIRBASE.__:IsInAirbase(Vec2)
local InAirbase=false
for AirbaseName,Airbase in pairs(self.__.Airbases)do
local Airbase=Airbase
if Airbase:GetZone():IsVec2InZone(Vec2)then
InAirbase=true
break;
end
end
return InAirbase
end
function CLEANUP_AIRBASE.__:DestroyUnit(CleanUpUnit)
self:F({CleanUpUnit})
if CleanUpUnit then
local CleanUpUnitName=CleanUpUnit:GetName()
local CleanUpGroup=CleanUpUnit:GetGroup()
if CleanUpGroup:IsAlive()then
local CleanUpGroupUnits=CleanUpGroup:GetUnits()
if#CleanUpGroupUnits==1 then
local CleanUpGroupName=CleanUpGroup:GetName()
CleanUpGroup:Destroy()
else
CleanUpUnit:Destroy()
end
self.CleanUpList[CleanUpUnitName]=nil
end
end
end
function CLEANUP_AIRBASE.__:DestroyMissile(MissileObject)
self:F({MissileObject})
if MissileObject and MissileObject:isExist()then
MissileObject:destroy()
self:T("MissileObject Destroyed")
end
end
function CLEANUP_AIRBASE.__:OnEventBirth(EventData)
self:F({EventData})
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()~=nil then
if self:IsInAirbase(EventData.IniUnit:GetVec2())then
self.CleanUpList[EventData.IniDCSUnitName]={}
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit=EventData.IniUnit
self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup=EventData.IniGroup
self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName=EventData.IniDCSGroupName
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName=EventData.IniDCSUnitName
end
end
end
function CLEANUP_AIRBASE.__:OnEventCrash(Event)
self:F({Event})
if Event.IniDCSUnitName and Event.IniCategory==Object.Category.UNIT then
self.CleanUpList[Event.IniDCSUnitName]={}
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit=Event.IniUnit
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup=Event.IniGroup
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName=Event.IniDCSGroupName
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName=Event.IniDCSUnitName
end
end
function CLEANUP_AIRBASE.__:OnEventShot(Event)
self:F({Event})
if self:IsInAirbase(Event.IniUnit:GetVec2())then
self:DestroyMissile(Event.Weapon)
end
end
function CLEANUP_AIRBASE.__:OnEventHit(Event)
self:F({Event})
if Event.IniUnit then
if self:IsInAirbase(Event.IniUnit:GetVec2())then
self:T({"Life: ",Event.IniDCSUnitName,' = ',Event.IniUnit:GetLife(),"/",Event.IniUnit:GetLife0()})
if Event.IniUnit:GetLife()<Event.IniUnit:GetLife0()then
self:T("CleanUp: Destroy: "..Event.IniDCSUnitName)
CLEANUP_AIRBASE.__:DestroyUnit(Event.IniUnit)
end
end
end
if Event.TgtUnit then
if self:IsInAirbase(Event.TgtUnit:GetVec2())then
self:T({"Life: ",Event.TgtDCSUnitName,' = ',Event.TgtUnit:GetLife(),"/",Event.TgtUnit:GetLife0()})
if Event.TgtUnit:GetLife()<Event.TgtUnit:GetLife0()then
self:T("CleanUp: Destroy: "..Event.TgtDCSUnitName)
CLEANUP_AIRBASE.__:DestroyUnit(Event.TgtUnit)
end
end
end
end
function CLEANUP_AIRBASE.__:AddForCleanUp(CleanUpUnit,CleanUpUnitName)
self:F({CleanUpUnit,CleanUpUnitName})
self.CleanUpList[CleanUpUnitName]={}
self.CleanUpList[CleanUpUnitName].CleanUpUnit=CleanUpUnit
self.CleanUpList[CleanUpUnitName].CleanUpUnitName=CleanUpUnitName
local CleanUpGroup=CleanUpUnit:GetGroup()
self.CleanUpList[CleanUpUnitName].CleanUpGroup=CleanUpGroup
self.CleanUpList[CleanUpUnitName].CleanUpGroupName=CleanUpGroup:GetName()
self.CleanUpList[CleanUpUnitName].CleanUpTime=timer.getTime()
self.CleanUpList[CleanUpUnitName].CleanUpMoved=false
self:T({"CleanUp: Add to CleanUpList: ",CleanUpGroup:GetName(),CleanUpUnitName})
end
function CLEANUP_AIRBASE.__:EventAddForCleanUp(Event)
self:F({Event})
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.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)
end
end
end
end
function CLEANUP_AIRBASE.__:CleanUpSchedule()
local CleanUpCount=0
for CleanUpUnitName,CleanUpListData in pairs(self.CleanUpList)do
CleanUpCount=CleanUpCount+1
local CleanUpUnit=CleanUpListData.CleanUpUnit
local CleanUpGroupName=CleanUpListData.CleanUpGroupName
if CleanUpUnit and CleanUpUnit:IsAlive()~=nil then
if self:IsInAirbase(CleanUpUnit:GetVec2())then
if _DATABASE:GetStatusGroup(CleanUpGroupName)~="ReSpawn"then
local CleanUpCoordinate=CleanUpUnit:GetCoordinate()
self:T({"CleanUp Scheduler",CleanUpUnitName})
if CleanUpUnit:GetLife()<=CleanUpUnit:GetLife0()*0.95 then
if CleanUpUnit:IsAboveRunway()then
if CleanUpUnit:InAir()then
local CleanUpLandHeight=CleanUpCoordinate:GetLandHeight()
local CleanUpUnitHeight=CleanUpCoordinate.y-CleanUpLandHeight
if CleanUpUnitHeight<100 then
self:T({"CleanUp Scheduler","Destroy "..CleanUpUnitName.." because below safe height and damaged."})
self:DestroyUnit(CleanUpUnit)
end
else
self:T({"CleanUp Scheduler","Destroy "..CleanUpUnitName.." because on runway and damaged."})
self:DestroyUnit(CleanUpUnit)
end
end
end
if CleanUpUnit and(CleanUpUnit.GetPlayerName==nil or not CleanUpUnit:GetPlayerName())then
local CleanUpUnitVelocity=CleanUpUnit:GetVelocityKMH()
if CleanUpUnitVelocity<1 then
if CleanUpListData.CleanUpMoved then
if CleanUpListData.CleanUpTime+180<=timer.getTime()then
self:T({"CleanUp Scheduler","Destroy due to not moving anymore "..CleanUpUnitName})
self:DestroyUnit(CleanUpUnit)
end
end
else
CleanUpListData.CleanUpTime=timer.getTime()
CleanUpListData.CleanUpMoved=true
end
end
else
self.CleanUpList[CleanUpUnitName]=nil
end
else
self.CleanUpList[CleanUpUnitName]=nil
end
else
self:T("CleanUp: Group "..CleanUpUnitName.." cannot be found in DCS RTE, removing ...")
self.CleanUpList[CleanUpUnitName]=nil
end
end
self:T(CleanUpCount)
return true
end
MOVEMENT={
ClassName="MOVEMENT",
}
function MOVEMENT:New(MovePrefixes,MoveMaximum)
local self=BASE:Inherit(self,BASE:New())
self:F({MovePrefixes,MoveMaximum})
if type(MovePrefixes)=='table'then
self.MovePrefixes=MovePrefixes
else
self.MovePrefixes={MovePrefixes}
end
self.MoveCount=0
self.MoveMaximum=MoveMaximum
self.AliveUnits=0
self.MoveUnits={}
self:HandleEvent(EVENTS.Birth)
self:ScheduleStart()
return self
end
function MOVEMENT:ScheduleStart()
self:F()
self.MoveFunction=SCHEDULER:New(self,self._Scheduler,{},1,120)
end
function MOVEMENT:ScheduleStop()
self:F()
end
function MOVEMENT:OnEventBirth(EventData)
self:F({EventData})
if timer.getTime0()<timer.getAbsTime()then
if EventData.IniDCSUnit then
self:T("Birth object : "..EventData.IniDCSUnitName)
if EventData.IniDCSGroup and EventData.IniDCSGroup:isExist()then
for MovePrefixID,MovePrefix in pairs(self.MovePrefixes)do
if string.find(EventData.IniDCSUnitName,MovePrefix,1,true)then
self.AliveUnits=self.AliveUnits+1
self.MoveUnits[EventData.IniDCSUnitName]=EventData.IniDCSGroupName
self:T(self.AliveUnits)
end
end
end
end
EventData.IniUnit:HandleEvent(EVENTS.DEAD,self.OnDeadOrCrash)
end
end
function MOVEMENT:OnDeadOrCrash(Event)
self:F({Event})
if Event.IniDCSUnit then
self:T("Dead object : "..Event.IniDCSUnitName)
for MovePrefixID,MovePrefix in pairs(self.MovePrefixes)do
if string.find(Event.IniDCSUnitName,MovePrefix,1,true)then
self.AliveUnits=self.AliveUnits-1
self.MoveUnits[Event.IniDCSUnitName]=nil
self:T(self.AliveUnits)
end
end
end
end
function MOVEMENT:_Scheduler()
self:F({self.MovePrefixes,self.MoveMaximum,self.AliveUnits,self.MovementGroups})
if self.AliveUnits>0 then
local MoveProbability=(self.MoveMaximum*100)/self.AliveUnits
self:T('Move Probability = '..MoveProbability)
for MovementUnitName,MovementGroupName in pairs(self.MoveUnits)do
local MovementGroup=Group.getByName(MovementGroupName)
if MovementGroup and MovementGroup:isExist()then
local MoveOrStop=math.random(1,100)
self:T('MoveOrStop = '..MoveOrStop)
if MoveOrStop<=MoveProbability then
self:T('Group continues moving = '..MovementGroupName)
trigger.action.groupContinueMoving(MovementGroup)
else
self:T('Group stops moving = '..MovementGroupName)
trigger.action.groupStopMoving(MovementGroup)
end
else
self.MoveUnits[MovementUnitName]=nil
end
end
end
return true
end
SEAD={
ClassName="SEAD",
TargetSkill={
Average={Evade=30,DelayOn={40,60}},
Good={Evade=20,DelayOn={30,50}},
High={Evade=15,DelayOn={20,40}},
Excellent={Evade=10,DelayOn={10,30}}
},
SEADGroupPrefixes={},
SuppressedGroups={},
EngagementRange=75,
Padding=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",
["X_28"]="X_28",
["X_25"]="X_25",
["X_31"]="X_31",
["Kh25"]="Kh25",
["BGM_109"]="BGM_109",
["AGM_154"]="AGM_154",
["HY-2"]="HY-2",
["ADM_141A"]="ADM_141A",
}
SEAD.HarmData={
["AGM_88"]={150,3},
["AGM_45"]={12,2},
["AGM_65"]={16,0.9},
["AGM_122"]={16.5,2.3},
["AGM_84"]={280,0.8},
["ALARM"]={45,2},
["LD-10"]={60,4},
["X_58"]={70,4},
["X_28"]={80,2.5},
["X_25"]={25,0.76},
["X_31"]={150,3},
["Kh25"]={25,0.8},
["BGM_109"]={460,0.705},
["AGM_154"]={130,0.61},
["HY-2"]={90,1},
["ADM_141A"]={126,0.6},
}
function SEAD:New(SEADGroupPrefixes,Padding)
local self=BASE:Inherit(self,FSM:New())
self:T(SEADGroupPrefixes)
if type(SEADGroupPrefixes)=='table'then
for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do
self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix
end
else
self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes
end
local padding=Padding or 10
if padding<10 then padding=10 end
self.Padding=padding
self.UseEmissionsOnOff=true
self.debug=false
self.CallBack=nil
self.UseCallBack=false
self:HandleEvent(EVENTS.Shot,self.HandleEventShot)
self:SetStartState("Running")
self:AddTransition("*","ManageEvasion","*")
self:AddTransition("*","CalculateHitZone","*")
self:I("*** SEAD - Started Version 0.4.9")
return self
end
function SEAD:UpdateSet(SEADGroupPrefixes)
self:T(SEADGroupPrefixes)
if type(SEADGroupPrefixes)=='table'then
for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do
self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix
end
else
self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes
end
return self
end
function SEAD:SetEngagementRange(range)
self:T({range})
range=range or 75
if range<0 or range>100 then
range=75
end
self.EngagementRange=range
self:T(string.format("*** SEAD - Engagement range set to %s",range))
return self
end
function SEAD:SetPadding(Padding)
self:T({Padding})
local padding=Padding or 10
if padding<10 then padding=10 end
self.Padding=padding
return self
end
function SEAD:SwitchEmissions(Switch)
self:T({Switch})
self.UseEmissionsOnOff=Switch
return self
end
function SEAD:AddCallBack(Object)
self:T({Class=Object.ClassName})
self.CallBack=Object
self.UseCallBack=true
return self
end
function SEAD:_CheckHarms(WeaponName)
self:T({WeaponName})
local hit=false
local name=""
for _,_name in pairs(SEAD.Harms)do
if string.find(WeaponName,_name,1,true)then
hit=true
name=_name
break
end
end
return hit,name
end
function SEAD:_GetDistance(_point1,_point2)
self:T("_GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName)
self:T("**** Calculating hit zone for "..(SEADWeaponName or"None"))
if SEADWeapon and SEADWeapon:isExist()then
local position=SEADWeapon:getPosition()
local mheight=height
local wph=math.atan2(position.x.z,position.x.x)
if wph<0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
local wpndata=SEAD.HarmData["AGM_88"]
if string.find(SEADWeaponName,"154",1)then
wpndata=SEAD.HarmData["AGM_154"]
end
local mveloc=math.floor(wpndata[2]*340.29)
local c1=(2*mheight*9.81)/(mveloc^2)
local c2=(mveloc^2)/9.81
local Ropt=c2*math.sqrt(c1+1)
if height<=5000 then
Ropt=Ropt*0.72
elseif height<=7500 then
Ropt=Ropt*0.82
elseif height<=10000 then
Ropt=Ropt*0.87
elseif height<=12500 then
Ropt=Ropt*0.98
end
for n=1,3 do
local dist=Ropt-((n-1)*20000)
local predpos=pos0:Translate(dist,wph)
if predpos then
local targetzone=ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
local tgtgrp=seadset:GetRandom()
local _targetgroup=nil
local _targetgroupname="none"
local _targetskill="Random"
if tgtgrp and tgtgrp:IsAlive()then
_targetgroup=tgtgrp
_targetgroupname=tgtgrp:GetName()
_targetskill=tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".._targetgroupname)
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup,20)
end
end
end
end
return self
end
function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset,Weapon)
local timeoffset=timeoffset or 0
if _targetskill=="Random"then
local Skills={"Average","Good","High","Excellent"}
_targetskill=Skills[math.random(1,4)]
end
if self.TargetSkill[_targetskill]then
local _evade=math.random(1,100)
if(_evade>self.TargetSkill[_targetskill].Evade)then
self:T("*** SEAD - Evading")
local _targetpos=_targetgroup:GetCoordinate()
local _distance=self:_GetDistance(SEADPlanePos,_targetpos)
local hit,data=self:_CheckHarms(SEADWeaponName)
local wpnspeed=666
local reach=10
if hit then
local wpndata=SEAD.HarmData[data]
reach=wpndata[1]*1.1
local mach=wpndata[2]
wpnspeed=math.floor(mach*340.29)
if Weapon and Weapon:GetSpeed()>0 then
wpnspeed=Weapon:GetSpeed()
self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed))
end
end
local _tti=math.floor(_distance/wpnspeed)-timeoffset
if _distance>0 then
_distance=math.floor(_distance/1000)
else
_distance=0
end
self:T(string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec",_targetskill,_distance,reach,_tti))
if reach>=_distance then
self:T("*** SEAD - Shot in Reach")
local function SuppressionStart(args)
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
local grp=args[1]
local name=args[2]
local attacker=args[3]
if self.UseEmissionsOnOff then
grp:EnableEmission(false)
end
grp:OptionAlarmStateGreen()
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond",true)
if self.UseCallBack then
local object=self.CallBack
object:SeadSuppressionStart(grp,name,attacker)
end
end
local function SuppressionStop(args)
self:T(string.format("*** SEAD - %s Radar On",args[2]))
local grp=args[1]
local name=args[2]
if self.UseEmissionsOnOff then
grp:EnableEmission(true)
end
grp:OptionAlarmStateRed()
grp:OptionEngageRange(self.EngagementRange)
self.SuppressedGroups[name]=false
if self.UseCallBack then
local object=self.CallBack
object:SeadSuppressionEnd(grp,name)
end
end
local delay=math.random(self.TargetSkill[_targetskill].DelayOn[1],self.TargetSkill[_targetskill].DelayOn[2])
if delay>_tti then delay=delay/2 end
if _tti>600 then delay=_tti-90 end
local SuppressionStartTime=timer.getTime()+delay
local SuppressionEndTime=timer.getTime()+delay+_tti+self.Padding+delay
local _targetgroupname=_targetgroup:GetName()
if not self.SuppressedGroups[_targetgroupname]then
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname,SEADGroup},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
self.SuppressedGroups[_targetgroupname]=true
if self.UseCallBack then
local object=self.CallBack
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime,SEADGroup)
end
end
end
end
end
return self
end
function SEAD:HandleEventShot(EventData)
self:T({EventData.id})
local 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 WeaponWrapper=WEAPON:New(EventData.Weapon)
self:T("*** SEAD - Missile Launched = "..SEADWeaponName)
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: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)
end
return self
end
local targetcat=Object.getCategory(_target)
local _targetUnit=nil
local _targetgroup=nil
self:T(string.format("*** Targetcat = %d",targetcat))
if targetcat==Object.Category.UNIT then
self:T("*** Target Category UNIT")
_targetUnit=UNIT:Find(_target)
if _targetUnit and _targetUnit:IsAlive()then
_targetgroup=_targetUnit:GetGroup()
_targetgroupname=_targetgroup:GetName()
local _targetUnitName=_targetUnit:GetName()
_targetUnit:GetSkill()
_targetskill=_targetUnit:GetSkill()
end
elseif targetcat==Object.Category.STATIC then
self:T("*** Target Category STATIC")
local seadset=SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce()
local targetpoint=_target:getPoint()or{x=0,y=0,z=0}
local tgtcoord=COORDINATE:NewFromVec3(targetpoint)
local tgtgrp=seadset:FindNearestGroupFromPointVec2(tgtcoord)
if tgtgrp and tgtgrp:IsAlive()then
_targetgroup=tgtgrp
_targetgroupname=tgtgrp:GetName()
_targetskill=tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".._targetgroupname)
end
end
local SEADGroupFound=false
for SEADGroupPrefixID,SEADGroupPrefix in pairs(self.SEADGroupPrefixes)do
self:T("Target = ".._targetgroupname.." | Prefix = "..SEADGroupPrefix)
if string.find(_targetgroupname,SEADGroupPrefix,1,true)then
SEADGroupFound=true
self:T('*** SEAD - Group Match Found')
break
end
end
if SEADGroupFound==true then
if string.find(SEADWeaponName,"ADM_141",1,true)then
self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,2,WeaponWrapper)
else
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper)
end
end
end
return self
end
ESCORT={
ClassName="ESCORT",
EscortName=nil,
EscortClient=nil,
EscortGroup=nil,
EscortMode=1,
MODE={
FOLLOW=1,
MISSION=2,
},
Targets={},
FollowScheduler=nil,
ReportTargets=true,
OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE,
OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION,
SmokeDirectionVector=false,
TaskPoints={}
}
function ESCORT:New(EscortClient,EscortGroup,EscortName,EscortBriefing)
local self=BASE:Inherit(self,BASE:New())
self:F({EscortClient,EscortGroup,EscortName})
self.EscortClient=EscortClient
self.EscortGroup=EscortGroup
self.EscortName=EscortName
self.EscortBriefing=EscortBriefing
self.EscortSetGroup=SET_GROUP:New()
self.EscortSetGroup:AddObject(self.EscortGroup)
self.EscortSetGroup:Flush()
self.Detection=DETECTION_UNITS:New(self.EscortSetGroup,15000)
self.EscortGroup.Detection=self.Detection
if not self.EscortClient._EscortGroups then
self.EscortClient._EscortGroups={}
end
if not self.EscortClient._EscortGroups[EscortGroup:GetName()]then
self.EscortClient._EscortGroups[EscortGroup:GetName()]={}
self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup=self.EscortGroup
self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName
self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection=self.EscortGroup.Detection
end
self.EscortMenu=MENU_GROUP:New(self.EscortClient:GetGroup(),self.EscortName)
self.EscortGroup:WayPointInitialize(1)
self.EscortGroup:OptionROTVertical()
self.EscortGroup:OptionROEOpenFire()
if not EscortBriefing then
EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") reporting! "..
"We're escorting your flight. "..
"Use the Radio Menu and F10 and use the options under + "..EscortName.."\n",
60,EscortClient
)
else
EscortGroup:MessageToClient(EscortGroup:GetCategoryName().." '"..EscortName.."' ("..EscortGroup:GetCallsign()..") "..EscortBriefing,
60,EscortClient
)
end
self.FollowDistance=100
self.CT1=0
self.GT1=0
self.FollowScheduler,self.FollowSchedule=SCHEDULER:New(self,self._FollowScheduler,{},1,.5,.01)
self.FollowScheduler:Stop(self.FollowSchedule)
self.EscortMode=ESCORT.MODE.MISSION
return self
end
function ESCORT:SetDetection(Detection)
self.Detection=Detection
self.EscortGroup.Detection=self.Detection
self.EscortClient._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection
Detection:__Start(1)
end
function ESCORT:TestSmokeDirectionVector(SmokeDirection)
self.SmokeDirectionVector=(SmokeDirection==true)and true or false
end
function ESCORT:Menus()
self:F()
self:MenuFollowAt(100)
self:MenuFollowAt(200)
self:MenuFollowAt(300)
self:MenuFollowAt(400)
self:MenuScanForTargets(100,60)
self:MenuHoldAtEscortPosition(30)
self:MenuHoldAtLeaderPosition(30)
self:MenuFlare()
self:MenuSmoke()
self:MenuReportTargets(60)
self:MenuAssistedAttack()
self:MenuROE()
self:MenuEvasion()
self:MenuResumeMission()
return self
end
function ESCORT:MenuFollowAt(Distance)
self:F(Distance)
if self.EscortGroup:IsAir()then
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu)
end
if not self.EscortMenuJoinUpAndFollow then
self.EscortMenuJoinUpAndFollow={}
end
self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1]=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Join-Up and Follow at "..Distance,self.EscortMenuReportNavigation,ESCORT._JoinUpAndFollow,self,Distance)
self.EscortMode=ESCORT.MODE.FOLLOW
end
return self
end
function ESCORT:MenuHoldAtEscortPosition(Height,Seconds,MenuTextFormat)
self:F({Height,Seconds,MenuTextFormat})
if self.EscortGroup:IsAir()then
if not self.EscortMenuHold then
self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu)
end
if not Height then
Height=30
end
if not Seconds then
Seconds=0
end
local MenuText=""
if not MenuTextFormat then
if Seconds==0 then
MenuText=string.format("Hold at %d meter",Height)
else
MenuText=string.format("Hold at %d meter for %d seconds",Height,Seconds)
end
else
if Seconds==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Seconds)
end
end
if not self.EscortMenuHoldPosition then
self.EscortMenuHoldPosition={}
end
self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1]=MENU_GROUP_COMMAND
:New(
self.EscortClient:GetGroup(),
MenuText,
self.EscortMenuHold,
ESCORT._HoldPosition,
self,
self.EscortGroup,
Height,
Seconds
)
end
return self
end
function ESCORT:MenuHoldAtLeaderPosition(Height,Seconds,MenuTextFormat)
self:F({Height,Seconds,MenuTextFormat})
if self.EscortGroup:IsAir()then
if not self.EscortMenuHold then
self.EscortMenuHold=MENU_GROUP:New(self.EscortClient:GetGroup(),"Hold position",self.EscortMenu)
end
if not Height then
Height=30
end
if not Seconds then
Seconds=0
end
local MenuText=""
if not MenuTextFormat then
if Seconds==0 then
MenuText=string.format("Rejoin and hold at %d meter",Height)
else
MenuText=string.format("Rejoin and hold at %d meter for %d seconds",Height,Seconds)
end
else
if Seconds==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Seconds)
end
end
if not self.EscortMenuHoldAtLeaderPosition then
self.EscortMenuHoldAtLeaderPosition={}
end
self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1]=MENU_GROUP_COMMAND
:New(
self.EscortClient:GetGroup(),
MenuText,
self.EscortMenuHold,
ESCORT._HoldPosition,
{ParamSelf=self,
ParamOrbitGroup=self.EscortClient,
ParamHeight=Height,
ParamSeconds=Seconds
}
)
end
return self
end
function ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat)
self:F({Height,Seconds,MenuTextFormat})
if self.EscortGroup:IsAir()then
if not self.EscortMenuScan then
self.EscortMenuScan=MENU_GROUP:New(self.EscortClient:GetGroup(),"Scan for targets",self.EscortMenu)
end
if not Height then
Height=100
end
if not Seconds then
Seconds=30
end
local MenuText=""
if not MenuTextFormat then
if Seconds==0 then
MenuText=string.format("At %d meter",Height)
else
MenuText=string.format("At %d meter for %d seconds",Height,Seconds)
end
else
if Seconds==0 then
MenuText=string.format(MenuTextFormat,Height)
else
MenuText=string.format(MenuTextFormat,Height,Seconds)
end
end
if not self.EscortMenuScanForTargets then
self.EscortMenuScanForTargets={}
end
self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND
:New(
self.EscortClient:GetGroup(),
MenuText,
self.EscortMenuScan,
ESCORT._ScanTargets,
self,
30
)
end
return self
end
function ESCORT:MenuFlare(MenuTextFormat)
self:F()
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu)
end
local MenuText=""
if not MenuTextFormat then
MenuText="Flare"
else
MenuText=MenuTextFormat
end
if not self.EscortMenuFlare then
self.EscortMenuFlare=MENU_GROUP:New(self.EscortClient:GetGroup(),MenuText,self.EscortMenuReportNavigation,ESCORT._Flare,self)
self.EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Green,"Released a green flare!")
self.EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Red,"Released a red flare!")
self.EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.White,"Released a white flare!")
self.EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release yellow flare",self.EscortMenuFlare,ESCORT._Flare,self,FLARECOLOR.Yellow,"Released a yellow flare!")
end
return self
end
function ESCORT:MenuSmoke(MenuTextFormat)
self:F()
if not self.EscortGroup:IsAir()then
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation=MENU_GROUP:New(self.EscortClient:GetGroup(),"Navigation",self.EscortMenu)
end
local MenuText=""
if not MenuTextFormat then
MenuText="Smoke"
else
MenuText=MenuTextFormat
end
if not self.EscortMenuSmoke then
self.EscortMenuSmoke=MENU_GROUP:New(self.EscortClient:GetGroup(),"Smoke",self.EscortMenuReportNavigation,ESCORT._Smoke,self)
self.EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release green smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Green,"Releasing green smoke!")
self.EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release red smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Red,"Releasing red smoke!")
self.EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release white smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.White,"Releasing white smoke!")
self.EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release orange smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!")
self.EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Release blue smoke",self.EscortMenuSmoke,ESCORT._Smoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!")
end
end
return self
end
function ESCORT:MenuReportTargets(Seconds)
self:F({Seconds})
if not self.EscortMenuReportNearbyTargets then
self.EscortMenuReportNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Report targets",self.EscortMenu)
end
if not Seconds then
Seconds=30
end
self.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets now!",self.EscortMenuReportNearbyTargets,ESCORT._ReportNearbyTargetsNow,self)
self.EscortMenuReportNearbyTargetsOn=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets on",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,true)
self.EscortMenuReportNearbyTargetsOff=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Report targets off",self.EscortMenuReportNearbyTargets,ESCORT._SwitchReportNearbyTargets,self,false)
self.EscortMenuAttackNearbyTargets=MENU_GROUP:New(self.EscortClient:GetGroup(),"Attack targets",self.EscortMenu)
self.ReportTargetsScheduler,self.ReportTargetsSchedulerID=SCHEDULER:New(self,self._ReportTargetsScheduler,{},1,Seconds)
return self
end
function ESCORT:MenuAssistedAttack()
self:F()
self.EscortMenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),"Request assistance from",self.EscortMenu)
return self
end
function ESCORT:MenuROE(MenuTextFormat)
self:F(MenuTextFormat)
if not self.EscortMenuROE then
self.EscortMenuROE=MENU_GROUP:New(self.EscortClient:GetGroup(),"ROE",self.EscortMenu)
if self.EscortGroup:OptionROEHoldFirePossible()then
self.EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Hold Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEHoldFire(),"Holding weapons!")
end
if self.EscortGroup:OptionROEReturnFirePossible()then
self.EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Return Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEReturnFire(),"Returning fire!")
end
if self.EscortGroup:OptionROEOpenFirePossible()then
self.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Open Fire",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEOpenFire(),"Opening fire on designated targets!!")
end
if self.EscortGroup:OptionROEWeaponFreePossible()then
self.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Weapon Free",self.EscortMenuROE,ESCORT._ROE,self,self.EscortGroup:OptionROEWeaponFree(),"Opening fire on targets of opportunity!")
end
end
return self
end
function ESCORT:MenuEvasion(MenuTextFormat)
self:F(MenuTextFormat)
if self.EscortGroup:IsAir()then
if not self.EscortMenuEvasion then
self.EscortMenuEvasion=MENU_GROUP:New(self.EscortClient:GetGroup(),"Evasion",self.EscortMenu)
if self.EscortGroup:OptionROTNoReactionPossible()then
self.EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Fight until death",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTNoReaction(),"Fighting until death!")
end
if self.EscortGroup:OptionROTPassiveDefensePossible()then
self.EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Use flares, chaff and jammers",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTPassiveDefense(),"Defending using jammers, chaff and flares!")
end
if self.EscortGroup:OptionROTEvadeFirePossible()then
self.EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Evade enemy fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTEvadeFire(),"Evading on enemy fire!")
end
if self.EscortGroup:OptionROTVerticalPossible()then
self.EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),"Go below radar and evade fire",self.EscortMenuEvasion,ESCORT._ROT,self,self.EscortGroup:OptionROTVertical(),"Evading on enemy fire with vertical manoeuvres!")
end
end
end
return self
end
function ESCORT:MenuResumeMission()
self:F()
if not self.EscortMenuResumeMission then
self.EscortMenuResumeMission=MENU_GROUP:New(self.EscortClient:GetGroup(),"Resume mission from",self.EscortMenu)
end
return self
end
function ESCORT:_HoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
local OrbitUnit=OrbitGroup:GetUnit(1)
self.FollowScheduler:Stop(self.FollowSchedule)
local PointFrom={}
local GroupVec3=EscortGroup:GetUnit(1):GetVec3()
PointFrom={}
PointFrom.x=GroupVec3.x
PointFrom.y=GroupVec3.z
PointFrom.speed=250
PointFrom.type=AI.Task.WaypointType.TURNING_POINT
PointFrom.alt=GroupVec3.y
PointFrom.alt_type=AI.Task.AltitudeType.BARO
local OrbitPoint=OrbitUnit:GetVec2()
local PointTo={}
PointTo.x=OrbitPoint.x
PointTo.y=OrbitPoint.y
PointTo.speed=250
PointTo.type=AI.Task.WaypointType.TURNING_POINT
PointTo.alt=OrbitHeight
PointTo.alt_type=AI.Task.AltitudeType.BARO
PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0)
local Points={PointFrom,PointTo}
EscortGroup:OptionROEHoldFire()
EscortGroup:OptionROTPassiveDefense()
EscortGroup:SetTask(EscortGroup:TaskRoute(Points))
EscortGroup:MessageToClient("Orbiting at location.",10,EscortClient)
end
function ESCORT:_JoinUpAndFollow(Distance)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.Distance=Distance
self:JoinUpAndFollow(EscortGroup,EscortClient,self.Distance)
end
function ESCORT:JoinUpAndFollow(EscortGroup,EscortClient,Distance)
self:F({EscortGroup,EscortClient,Distance})
self.FollowScheduler:Stop(self.FollowSchedule)
EscortGroup:OptionROEHoldFire()
EscortGroup:OptionROTPassiveDefense()
self.EscortMode=ESCORT.MODE.FOLLOW
self.CT1=0
self.GT1=0
self.FollowScheduler:Start(self.FollowSchedule)
EscortGroup:MessageToClient("Rejoining and Following at "..Distance.."!",30,EscortClient)
end
function ESCORT:_Flare(Color,Message)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
EscortGroup:GetUnit(1):Flare(Color)
EscortGroup:MessageToClient(Message,10,EscortClient)
end
function ESCORT:_Smoke(Color,Message)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
EscortGroup:GetUnit(1):Smoke(Color)
EscortGroup:MessageToClient(Message,10,EscortClient)
end
function ESCORT:_ReportNearbyTargetsNow()
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self:_ReportTargetsScheduler()
end
function ESCORT:_SwitchReportNearbyTargets(ReportTargets)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.ReportTargets=ReportTargets
if self.ReportTargets then
if not self.ReportTargetsScheduler then
self.ReportTargetsScheduler:Schedule(self,self._ReportTargetsScheduler,{},1,30)
end
else
self.ReportTargetsScheduler:Remove(self.ReportTargetsSchedulerID)
self.ReportTargetsScheduler=nil
end
end
function ESCORT:_ScanTargets(ScanDuration)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
if EscortGroup:IsHelicopter()then
EscortGroup:PushTask(
EscortGroup:TaskControlled(
EscortGroup:TaskOrbitCircle(200,20),
EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
),1)
elseif EscortGroup:IsAirPlane()then
EscortGroup:PushTask(
EscortGroup:TaskControlled(
EscortGroup:TaskOrbitCircle(1000,500),
EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil)
),1)
end
EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortClient)
if self.EscortMode==ESCORT.MODE.FOLLOW then
self.FollowScheduler:Start(self.FollowSchedule)
end
end
function _Resume(EscortGroup)
env.info('_Resume')
local Escort=EscortGroup:GetState(EscortGroup,"Escort")
env.info("EscortMode = "..Escort.EscortMode)
if Escort.EscortMode==ESCORT.MODE.FOLLOW then
Escort:JoinUpAndFollow(EscortGroup,Escort.EscortClient,Escort.Distance)
end
end
function ESCORT:_AttackTarget(DetectedItem)
local EscortGroup=self.EscortGroup
self:F(EscortGroup)
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
if EscortGroup:IsAir()then
EscortGroup:OptionROEOpenFire()
EscortGroup:OptionROTPassiveDefense()
EscortGroup:SetState(EscortGroup,"Escort",self)
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit)
end
end,Tasks
)
Tasks[#Tasks+1]=EscortGroup:TaskFunction("_Resume",{"''"})
EscortGroup:SetTask(
EscortGroup:TaskCombo(
Tasks
),1
)
else
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
end
end,Tasks
)
EscortGroup:SetTask(
EscortGroup:TaskCombo(
Tasks
),1
)
end
EscortGroup:MessageToClient("Engaging Designated Unit!",10,EscortClient)
end
function ESCORT:_AssistTarget(EscortGroupAttack,DetectedItem)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
if EscortGroupAttack:IsAir()then
EscortGroupAttack:OptionROEOpenFire()
EscortGroupAttack:OptionROTVertical()
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroupAttack:TaskAttackUnit(DetectedUnit)
end
end,Tasks
)
Tasks[#Tasks+1]=EscortGroupAttack:TaskOrbitCircle(500,350)
EscortGroupAttack:SetTask(
EscortGroupAttack:TaskCombo(
Tasks
),1
)
else
local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem)
local Tasks={}
DetectedSet:ForEachUnit(
function(DetectedUnit,Tasks)
if DetectedUnit:IsAlive()then
Tasks[#Tasks+1]=EscortGroupAttack:TaskFireAtPoint(DetectedUnit:GetVec2(),50)
end
end,Tasks
)
EscortGroupAttack:SetTask(
EscortGroupAttack:TaskCombo(
Tasks
),1
)
end
EscortGroupAttack:MessageToClient("Assisting with the destroying the enemy unit!",10,EscortClient)
end
function ESCORT:_ROE(EscortROEFunction,EscortROEMessage)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
pcall(function()EscortROEFunction()end)
EscortGroup:MessageToClient(EscortROEMessage,10,EscortClient)
end
function ESCORT:_ROT(EscortROTFunction,EscortROTMessage)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
pcall(function()EscortROTFunction()end)
EscortGroup:MessageToClient(EscortROTMessage,10,EscortClient)
end
function ESCORT:_ResumeMission(WayPoint)
local EscortGroup=self.EscortGroup
local EscortClient=self.EscortClient
self.FollowScheduler:Stop(self.FollowSchedule)
local WayPoints=EscortGroup:GetTaskRoute()
self:T(WayPoint,WayPoints)
for WayPointIgnore=1,WayPoint do
table.remove(WayPoints,1)
end
SCHEDULER:New(EscortGroup,EscortGroup.SetTask,{EscortGroup:TaskRoute(WayPoints)},1)
EscortGroup:MessageToClient("Resuming mission from waypoint "..WayPoint..".",10,EscortClient)
end
function ESCORT:RegisterRoute()
self:F()
local EscortGroup=self.EscortGroup
local TaskPoints=EscortGroup:GetTaskRoute()
self:T(TaskPoints)
return TaskPoints
end
function ESCORT:_FollowScheduler()
self:F({self.FollowDistance})
self:T({self.EscortClient.UnitName,self.EscortGroup.GroupName})
if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then
local ClientUnit=self.EscortClient:GetClientGroupUnit()
local GroupUnit=self.EscortGroup:GetUnit(1)
local FollowDistance=self.FollowDistance
self:T({ClientUnit.UnitName,GroupUnit.UnitName})
if self.CT1==0 and self.GT1==0 then
self.CV1=ClientUnit:GetVec3()
self:T({"self.CV1",self.CV1})
self.CT1=timer.getTime()
self.GV1=GroupUnit:GetVec3()
self.GT1=timer.getTime()
else
local CT1=self.CT1
local CT2=timer.getTime()
local CV1=self.CV1
local CV2=ClientUnit:GetVec3()
self.CT1=CT2
self.CV1=CV2
local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5
local CT=CT2-CT1
local CS=(3600/CT)*(CD/1000)
self:T2({"Client:",CS,CD,CT,CV2,CV1,CT2,CT1})
local GT1=self.GT1
local GT2=timer.getTime()
local GV1=self.GV1
local GV2=GroupUnit:GetVec3()
self.GT1=GT2
self.GV1=GV2
local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5
local GT=GT2-GT1
local GS=(3600/GT)*(GD/1000)
self:T2({"Group:",GS,GD,GT,GV2,GV1,GT2,GT1})
local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z}
local GH2={x=GV2.x,y=CV2.y,z=GV2.z}
local alpha=math.atan2(GV.z,GV.x)
local CVI={x=CV2.x+FollowDistance*math.cos(alpha),
y=GH2.y,
z=CV2.z+FollowDistance*math.sin(alpha),
}
local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z}
local DVu={x=DV.x/FollowDistance,y=DV.y/FollowDistance,z=DV.z/FollowDistance}
local GDV={x=DVu.x*CS*8+CVI.x,y=CVI.y,z=DVu.z*CS*8+CVI.z}
if self.SmokeDirectionVector==true then
trigger.action.smoke(GDV,trigger.smokeColor.Red)
end
self:T2({"CV2:",CV2})
self:T2({"CVI:",CVI})
self:T2({"GDV:",GDV})
local CatchUpDistance=((GDV.x-GV2.x)^2+(GDV.y-GV2.y)^2+(GDV.z-GV2.z)^2)^0.5
local Time=10
local CatchUpSpeed=(CatchUpDistance-(CS*8.4))/Time
local Speed=CS+CatchUpSpeed
if Speed<0 then
Speed=0
end
self:T({"Client Speed, Escort Speed, Speed, FollowDistance, Time:",CS,GS,Speed,FollowDistance,Time})
self.EscortGroup:RouteToVec3(GDV,Speed/3.6)
end
return true
end
return false
end
function ESCORT:_ReportTargetsScheduler()
self:F(self.EscortGroup:GetName())
if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then
local EscortGroupName=self.EscortGroup:GetName()
self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
if self.EscortMenuTargetAssistance then
self.EscortMenuTargetAssistance:RemoveSubMenus()
end
local DetectedItems=self.Detection:GetDetectedItems()
self:F(DetectedItems)
local DetectedTargets=false
local DetectedMsgs={}
for ClientEscortGroupName,EscortGroupData in pairs(self.EscortClient._EscortGroups)do
local ClientEscortTargets=EscortGroupData.Detection
for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do
self:F({DetectedItemIndex,DetectedItem})
local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroupData.EscortGroup,_DATABASE:GetPlayerSettings(self.EscortClient:GetPlayerName()))
if ClientEscortGroupName==EscortGroupName then
local DetectedMsg=DetectedItemReportSummary:Text("\n")
DetectedMsgs[#DetectedMsgs+1]=DetectedMsg
self:T(DetectedMsg)
MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),
DetectedMsg,
self.EscortMenuAttackNearbyTargets,
ESCORT._AttackTarget,
self,
DetectedItem
)
else
if self.EscortMenuTargetAssistance then
local DetectedMsg=DetectedItemReportSummary:Text("\n")
self:T(DetectedMsg)
local MenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),EscortGroupData.EscortName,self.EscortMenuTargetAssistance)
MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(),
DetectedMsg,
MenuTargetAssistance,
ESCORT._AssistTarget,
self,
EscortGroupData.EscortGroup,
DetectedItem
)
end
end
DetectedTargets=true
end
end
self:F(DetectedMsgs)
if DetectedTargets then
self.EscortGroup:MessageToClient("Reporting detected targets:\n"..table.concat(DetectedMsgs,"\n"),20,self.EscortClient)
else
self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient)
end
return true
end
return false
end
MISSILETRAINER={
ClassName="MISSILETRAINER",
TrackingMissiles={},
}
function MISSILETRAINER._Alive(Client,self)
if self.Briefing then
Client:Message(self.Briefing,15,"Trainer")
end
if self.MenusOnOff==true then
Client:Message("Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).",15,"Trainer")
Client.MainMenu=MENU_GROUP:New(Client:GetGroup(),"Missile Trainer",nil)
Client.MenuMessages=MENU_GROUP:New(Client:GetGroup(),"Messages",Client.MainMenu)
Client.MenuOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages On",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=true})
Client.MenuOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages Off",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=false})
Client.MenuTracking=MENU_GROUP:New(Client:GetGroup(),"Tracking",Client.MainMenu)
Client.MenuTrackingToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=true})
Client.MenuTrackingToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=false})
Client.MenuTrackOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking On",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=true})
Client.MenuTrackOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking Off",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=false})
Client.MenuTrackIncrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Increase",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=-1})
Client.MenuTrackDecrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Decrease",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=1})
Client.MenuAlerts=MENU_GROUP:New(Client:GetGroup(),"Alerts",Client.MainMenu)
Client.MenuAlertsToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=true})
Client.MenuAlertsToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=false})
Client.MenuHitsOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=true})
Client.MenuHitsOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=false})
Client.MenuLaunchesOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=true})
Client.MenuLaunchesOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=false})
Client.MenuDetails=MENU_GROUP:New(Client:GetGroup(),"Details",Client.MainMenu)
Client.MenuDetailsDistanceOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=true})
Client.MenuDetailsDistanceOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=false})
Client.MenuDetailsBearingOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=true})
Client.MenuDetailsBearingOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=false})
Client.MenuDistance=MENU_GROUP:New(Client:GetGroup(),"Set distance to plane",Client.MainMenu)
Client.MenuDistance50=MENU_GROUP_COMMAND:New(Client:GetGroup(),"50 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=50/1000})
Client.MenuDistance100=MENU_GROUP_COMMAND:New(Client:GetGroup(),"100 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=100/1000})
Client.MenuDistance150=MENU_GROUP_COMMAND:New(Client:GetGroup(),"150 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=150/1000})
Client.MenuDistance200=MENU_GROUP_COMMAND:New(Client:GetGroup(),"200 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=200/1000})
else
if Client.MainMenu then
Client.MainMenu:Remove()
end
end
local ClientID=Client:GetID()
self:T(ClientID)
if not self.TrackingMissiles[ClientID]then
self.TrackingMissiles[ClientID]={}
end
self.TrackingMissiles[ClientID].Client=Client
if not self.TrackingMissiles[ClientID].MissileData then
self.TrackingMissiles[ClientID].MissileData={}
end
end
function MISSILETRAINER:New(Distance,Briefing)
local self=BASE:Inherit(self,BASE:New())
self:F(Distance)
if Briefing then
self.Briefing=Briefing
end
self.Schedulers={}
self.SchedulerID=0
self.MessageInterval=2
self.MessageLastTime=timer.getTime()
self.Distance=Distance/1000
self:HandleEvent(EVENTS.Shot)
self.DBClients=SET_CLIENT:New():FilterStart()
self.DBClients:ForEachClient(
function(Client)
self:F("ForEach:"..Client.UnitName)
Client:Alive(self._Alive,self)
end
)
self.MessagesOnOff=true
self.TrackingToAll=false
self.TrackingOnOff=true
self.TrackingFrequency=3
self.AlertsToAll=true
self.AlertsHitsOnOff=true
self.AlertsLaunchesOnOff=true
self.DetailsRangeOnOff=true
self.DetailsBearingOnOff=true
self.MenusOnOff=true
self.TrackingMissiles={}
self.TrackingScheduler=SCHEDULER:New(self,self._TrackMissiles,{},0.5,0.05,0)
return self
end
function MISSILETRAINER:InitMessagesOnOff(MessagesOnOff)
self:F(MessagesOnOff)
self.MessagesOnOff=MessagesOnOff
if self.MessagesOnOff==true then
MESSAGE:New("Messages ON",15,"Menu"):ToAll()
else
MESSAGE:New("Messages OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitTrackingToAll(TrackingToAll)
self:F(TrackingToAll)
self.TrackingToAll=TrackingToAll
if self.TrackingToAll==true then
MESSAGE:New("Missile tracking to all players ON",15,"Menu"):ToAll()
else
MESSAGE:New("Missile tracking to all players OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitTrackingOnOff(TrackingOnOff)
self:F(TrackingOnOff)
self.TrackingOnOff=TrackingOnOff
if self.TrackingOnOff==true then
MESSAGE:New("Missile tracking ON",15,"Menu"):ToAll()
else
MESSAGE:New("Missile tracking OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitTrackingFrequency(TrackingFrequency)
self:F(TrackingFrequency)
self.TrackingFrequency=self.TrackingFrequency+TrackingFrequency
if self.TrackingFrequency<0.5 then
self.TrackingFrequency=0.5
end
if self.TrackingFrequency then
MESSAGE:New("Missile tracking frequency is "..self.TrackingFrequency.." seconds.",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitAlertsToAll(AlertsToAll)
self:F(AlertsToAll)
self.AlertsToAll=AlertsToAll
if self.AlertsToAll==true then
MESSAGE:New("Alerts to all players ON",15,"Menu"):ToAll()
else
MESSAGE:New("Alerts to all players OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitAlertsHitsOnOff(AlertsHitsOnOff)
self:F(AlertsHitsOnOff)
self.AlertsHitsOnOff=AlertsHitsOnOff
if self.AlertsHitsOnOff==true then
MESSAGE:New("Alerts Hits ON",15,"Menu"):ToAll()
else
MESSAGE:New("Alerts Hits OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitAlertsLaunchesOnOff(AlertsLaunchesOnOff)
self:F(AlertsLaunchesOnOff)
self.AlertsLaunchesOnOff=AlertsLaunchesOnOff
if self.AlertsLaunchesOnOff==true then
MESSAGE:New("Alerts Launches ON",15,"Menu"):ToAll()
else
MESSAGE:New("Alerts Launches OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitRangeOnOff(DetailsRangeOnOff)
self:F(DetailsRangeOnOff)
self.DetailsRangeOnOff=DetailsRangeOnOff
if self.DetailsRangeOnOff==true then
MESSAGE:New("Range display ON",15,"Menu"):ToAll()
else
MESSAGE:New("Range display OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitBearingOnOff(DetailsBearingOnOff)
self:F(DetailsBearingOnOff)
self.DetailsBearingOnOff=DetailsBearingOnOff
if self.DetailsBearingOnOff==true then
MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll()
else
MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER:InitMenusOnOff(MenusOnOff)
self:F(MenusOnOff)
self.MenusOnOff=MenusOnOff
if self.MenusOnOff==true then
MESSAGE:New("Menus are ENABLED (only when a player rejoins a slot)",15,"Menu"):ToAll()
else
MESSAGE:New("Menus are DISABLED",15,"Menu"):ToAll()
end
return self
end
function MISSILETRAINER._MenuMessages(MenuParameters)
local self=MenuParameters.MenuSelf
if MenuParameters.MessagesOnOff~=nil then
self:InitMessagesOnOff(MenuParameters.MessagesOnOff)
end
if MenuParameters.TrackingToAll~=nil then
self:InitTrackingToAll(MenuParameters.TrackingToAll)
end
if MenuParameters.TrackingOnOff~=nil then
self:InitTrackingOnOff(MenuParameters.TrackingOnOff)
end
if MenuParameters.TrackingFrequency~=nil then
self:InitTrackingFrequency(MenuParameters.TrackingFrequency)
end
if MenuParameters.AlertsToAll~=nil then
self:InitAlertsToAll(MenuParameters.AlertsToAll)
end
if MenuParameters.AlertsHitsOnOff~=nil then
self:InitAlertsHitsOnOff(MenuParameters.AlertsHitsOnOff)
end
if MenuParameters.AlertsLaunchesOnOff~=nil then
self:InitAlertsLaunchesOnOff(MenuParameters.AlertsLaunchesOnOff)
end
if MenuParameters.DetailsRangeOnOff~=nil then
self:InitRangeOnOff(MenuParameters.DetailsRangeOnOff)
end
if MenuParameters.DetailsBearingOnOff~=nil then
self:InitBearingOnOff(MenuParameters.DetailsBearingOnOff)
end
if MenuParameters.Distance~=nil then
self.Distance=MenuParameters.Distance
MESSAGE:New("Hit detection distance set to "..(self.Distance*1000).." meters",15,"Menu"):ToAll()
end
end
function MISSILETRAINER:OnEventShot(EVentData)
self:F({EVentData})
local TrainerSourceDCSUnit=EVentData.IniDCSUnit
local TrainerSourceDCSUnitName=EVentData.IniDCSUnitName
local TrainerWeapon=EVentData.Weapon
local TrainerWeaponName=EVentData.WeaponName
self:T("Missile Launched = "..TrainerWeaponName)
local TrainerTargetDCSUnit=TrainerWeapon:getTarget()
if TrainerTargetDCSUnit then
local TrainerTargetDCSUnitName=Unit.getName(TrainerTargetDCSUnit)
local TrainerTargetSkill=_DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
self:T(TrainerTargetDCSUnitName)
local Client=self.DBClients:FindClient(TrainerTargetDCSUnitName)
if Client then
local TrainerSourceUnit=UNIT:Find(TrainerSourceDCSUnit)
local TrainerTargetUnit=UNIT:Find(TrainerTargetDCSUnit)
if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then
local Message=MESSAGE:New(
string.format("%s launched a %s",
TrainerSourceUnit:GetTypeName(),
TrainerWeaponName
)..self:_AddRange(Client,TrainerWeapon)..self:_AddBearing(Client,TrainerWeapon),5,"Launch Alert")
if self.AlertsToAll then
Message:ToAll()
else
Message:ToClient(Client)
end
end
local ClientID=Client:GetID()
self:T(ClientID)
local MissileData={}
MissileData.TrainerSourceUnit=TrainerSourceUnit
MissileData.TrainerWeapon=TrainerWeapon
MissileData.TrainerTargetUnit=TrainerTargetUnit
MissileData.TrainerWeaponTypeName=TrainerWeapon:getTypeName()
MissileData.TrainerWeaponLaunched=true
table.insert(self.TrackingMissiles[ClientID].MissileData,MissileData)
end
else
if(TrainerWeapon:getTypeName()=="9M311")then
SCHEDULER:New(TrainerWeapon,TrainerWeapon.destroy,{},1)
else
end
end
end
function MISSILETRAINER:_AddRange(Client,TrainerWeapon)
local RangeText=""
if self.DetailsRangeOnOff then
local PositionMissile=TrainerWeapon:getPoint()
local TargetVec3=Client:GetVec3()
local Range=((PositionMissile.x-TargetVec3.x)^2+
(PositionMissile.y-TargetVec3.y)^2+
(PositionMissile.z-TargetVec3.z)^2
)^0.5/1000
RangeText=string.format(", at %4.2fkm",Range)
end
return RangeText
end
function MISSILETRAINER:_AddBearing(Client,TrainerWeapon)
local BearingText=""
if self.DetailsBearingOnOff then
local PositionMissile=TrainerWeapon:getPoint()
local TargetVec3=Client:GetVec3()
self:T2({TargetVec3,PositionMissile})
local DirectionVector={x=PositionMissile.x-TargetVec3.x,y=PositionMissile.y-TargetVec3.y,z=PositionMissile.z-TargetVec3.z}
local DirectionRadians=math.atan2(DirectionVector.z,DirectionVector.x)
if DirectionRadians<0 then
DirectionRadians=DirectionRadians+2*math.pi
end
local DirectionDegrees=DirectionRadians*180/math.pi
BearingText=string.format(", %d degrees",DirectionDegrees)
end
return BearingText
end
function MISSILETRAINER:_TrackMissiles()
self:F2()
local ShowMessages=false
if self.MessagesOnOff and self.MessageLastTime+self.TrackingFrequency<=timer.getTime()then
self.MessageLastTime=timer.getTime()
ShowMessages=true
end
for ClientDataID,ClientData in pairs(self.TrackingMissiles)do
local Client=ClientData.Client
if Client and Client:IsAlive()then
for MissileDataID,MissileData in pairs(ClientData.MissileData)do
self:T3(MissileDataID)
local TrainerSourceUnit=MissileData.TrainerSourceUnit
local TrainerWeapon=MissileData.TrainerWeapon
local TrainerTargetUnit=MissileData.TrainerTargetUnit
local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then
local PositionMissile=TrainerWeapon:getPosition().p
local TargetVec3=Client:GetVec3()
local Distance=((PositionMissile.x-TargetVec3.x)^2+
(PositionMissile.y-TargetVec3.y)^2+
(PositionMissile.z-TargetVec3.z)^2
)^0.5/1000
if Distance<=self.Distance then
TrainerWeapon:destroy()
if self.MessagesOnOff==true and self.AlertsHitsOnOff==true then
self:T("killed")
local Message=MESSAGE:New(
string.format("%s launched by %s killed %s",
TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName()
),15,"Hit Alert")
if self.AlertsToAll==true then
Message:ToAll()
else
Message:ToClient(Client)
end
MissileData=nil
table.remove(ClientData.MissileData,MissileDataID)
self:T(ClientData.MissileData)
end
end
else
if not(TrainerWeapon and TrainerWeapon:isExist())then
if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then
local Message=MESSAGE:New(
string.format("%s launched by %s self destructed!",
TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName()
),5,"Tracking")
if self.AlertsToAll==true then
Message:ToAll()
else
Message:ToClient(Client)
end
end
MissileData=nil
table.remove(ClientData.MissileData,MissileDataID)
self:T(ClientData.MissileData)
end
end
end
else
self.TrackingMissiles[ClientDataID]=nil
end
end
if ShowMessages==true and self.MessagesOnOff==true and self.TrackingOnOff==true then
for ClientDataID,ClientData in pairs(self.TrackingMissiles)do
local Client=ClientData.Client
ClientData.MessageToClient=""
ClientData.MessageToAll=""
for TrackingDataID,TrackingData in pairs(self.TrackingMissiles)do
for MissileDataID,MissileData in pairs(TrackingData.MissileData)do
local TrainerSourceUnit=MissileData.TrainerSourceUnit
local TrainerWeapon=MissileData.TrainerWeapon
local TrainerTargetUnit=MissileData.TrainerTargetUnit
local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then
if ShowMessages==true then
local TrackingTo
TrackingTo=string.format("  -> %s",
TrainerWeaponTypeName
)
if ClientDataID==TrackingDataID then
if ClientData.MessageToClient==""then
ClientData.MessageToClient="Missiles to You:\n"
end
ClientData.MessageToClient=ClientData.MessageToClient..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).."\n"
else
if self.TrackingToAll==true then
if ClientData.MessageToAll==""then
ClientData.MessageToAll="Missiles to other Players:\n"
end
ClientData.MessageToAll=ClientData.MessageToAll..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).." ( "..TrainerTargetUnit:GetPlayerName().." )\n"
end
end
end
end
end
end
if ClientData.MessageToClient~=""or ClientData.MessageToAll~=""then
local Message=MESSAGE:New(ClientData.MessageToClient..ClientData.MessageToAll,1,"Tracking"):ToClient(Client)
end
end
end
return true
end
ATC_GROUND={
ClassName="ATC_GROUND",
SetClient=nil,
Airbases=nil,
AirbaseNames=nil,
}
function ATC_GROUND:New(Airbases,AirbaseList)
local self=BASE:Inherit(self,BASE:New())
self:T({self.ClassName,Airbases})
self.Airbases=Airbases
self.AirbaseList=AirbaseList
self.SetClient=SET_CLIENT:New():FilterCategories("plane"):FilterStart()
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneBoundary then
Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary)
else
Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone()
end
Airbase.ZoneRunways={}
if Airbase.PointsRunways then
for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do
Airbase.ZoneRunways[PointsRunwayID]=ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID,PointsRunway)
end
end
Airbase.Monitor=self.AirbaseList and false or true
end
for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do
self.Airbases[AirbaseName].Monitor=true
end
self.SetClient:ForEachClient(
function(Client)
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
Client:SetState(self,"Taxi",false)
end
)
SSB=USERFLAG:New("SSB")
SSB:Set(100)
return self
end
function ATC_GROUND:SmokeRunways(SmokeColor)
for AirbaseID,Airbase in pairs(self.Airbases)do
for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do
Airbase.ZoneRunways[PointsRunwayID]:SmokeZone(SmokeColor)
end
end
end
function ATC_GROUND:SetKickSpeed(KickSpeed,Airbase)
if not Airbase then
self.KickSpeed=KickSpeed
else
self.Airbases[Airbase].KickSpeed=KickSpeed
end
return self
end
function ATC_GROUND:SetKickSpeedKmph(KickSpeed,Airbase)
self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase)
return self
end
function ATC_GROUND:SetKickSpeedMiph(KickSpeedMiph,Airbase)
self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase)
return self
end
function ATC_GROUND:SetMaximumKickSpeed(MaximumKickSpeed,Airbase)
if not Airbase then
self.MaximumKickSpeed=MaximumKickSpeed
else
self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed
end
return self
end
function ATC_GROUND:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase)
self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase)
return self
end
function ATC_GROUND:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase)
self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase)
return self
end
function ATC_GROUND:_AirbaseMonitor()
self.SetClient:ForEachClient(
function(Client)
if Client:IsAlive()then
local IsOnGround=Client:InAir()==false
for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
self:T(AirbaseID,AirbaseMeta.KickSpeed)
if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
local NotInRunwayZone=true
for ZoneRunwayID,ZoneRunway in pairs(AirbaseMeta.ZoneRunways)do
NotInRunwayZone=(Client:IsNotInZone(ZoneRunway)==true)and NotInRunwayZone or false
end
if NotInRunwayZone then
if IsOnGround then
local Taxi=Client:GetState(self,"Taxi")
self:T(Taxi)
if Taxi==false then
local Velocity=VELOCITY:New(AirbaseMeta.KickSpeed or self.KickSpeed)
Client:Message("Welcome to "..AirbaseID..". The maximum taxiing speed is "..
Velocity:ToString(),20,"ATC")
Client:SetState(self,"Taxi",true)
end
local Velocity=VELOCITY_POSITIONABLE:New(Client)
local IsAboveRunway=Client:IsAboveRunway()
self:T(IsAboveRunway,IsOnGround)
if IsOnGround then
local Speeding=false
if AirbaseMeta.MaximumKickSpeed then
if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.MaximumKickSpeed then
Speeding=true
end
end
if Speeding==true then
MESSAGE:New("Penalty! Player "..Client:GetPlayerName()..
" has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
end
if IsOnGround then
local Speeding=false
if AirbaseMeta.KickSpeed then
if Velocity:Get()>AirbaseMeta.KickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.KickSpeed then
Speeding=true
end
end
if Speeding==true then
local IsSpeeding=Client:GetState(self,"Speeding")
if IsSpeeding==true then
local SpeedingWarnings=Client:GetState(self,"Warnings")
self:T(SpeedingWarnings)
if SpeedingWarnings<=3 then
Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Warnings",SpeedingWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
else
Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Speeding",true)
Client:SetState(self,"Warnings",1)
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
end
if IsOnGround and not IsAboveRunway then
local IsOffRunway=Client:GetState(self,"IsOffRunway")
if IsOffRunway==true then
local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings")
self:T(OffRunwayWarnings)
if OffRunwayWarnings<=3 then
Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC")
Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
else
Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC")
Client:SetState(self,"IsOffRunway",true)
Client:SetState(self,"OffRunwayWarnings",1)
end
else
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
local Taxi=Client:GetState(self,"Taxi")
if Taxi==true then
Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC")
Client:SetState(self,"Taxi",false)
end
end
end
end
else
Client:SetState(self,"Taxi",false)
end
end
)
return true
end
ATC_GROUND_UNIVERSAL={
ClassName="ATC_GROUND_UNIVERSAL",
Version="0.0.2",
SetClient=nil,
Airbases=nil,
AirbaseList=nil,
KickSpeed=nil,
}
function ATC_GROUND_UNIVERSAL:New(AirbaseList)
local self=BASE:Inherit(self,BASE:New())
self:T({self.ClassName})
self.Airbases={}
self.AirbaseList=AirbaseList
if not self.AirbaseList then
self.AirbaseList={}
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()
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneBoundary then
Airbase.ZoneBoundary=ZONE_POLYGON_BASE:New("Boundary "..AirbaseID,Airbase.ZoneBoundary)
else
Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone()
end
Airbase.ZoneRunways=AIRBASE:FindByName(AirbaseID):GetRunways()
Airbase.Monitor=self.AirbaseList and false or true
end
for AirbaseID,AirbaseName in pairs(self.AirbaseList or{})do
self.Airbases[AirbaseName].Monitor=true
end
self.SetClient:ForEachClient(
function(Client)
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
Client:SetState(self,"Taxi",false)
end
)
SSB=USERFLAG:New("SSB")
SSB:Set(100)
self.KickSpeed=UTILS.KnotsToMps(10)
self:SetMaximumKickSpeedMiph(30)
return self
end
function ATC_GROUND_UNIVERSAL:SetAirbaseBoundaries(Airbase,Zone)
self.Airbases[Airbase].ZoneBoundary=Zone
return self
end
function ATC_GROUND_UNIVERSAL:SmokeRunways(SmokeColor)
local SmokeColor=SmokeColor or SMOKECOLOR.Red
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneRunways then
for _,_runwaydata in pairs(Airbase.ZoneRunways)do
local runwaydata=_runwaydata
runwaydata.zone:SmokeZone(SmokeColor)
end
end
end
return self
end
function ATC_GROUND_UNIVERSAL:DrawRunways(Color)
local Color=Color or{1,0,0}
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneRunways then
for _,_runwaydata in pairs(Airbase.ZoneRunways)do
local runwaydata=_runwaydata
runwaydata.zone:DrawZone(-1,Color)
end
end
end
return self
end
function ATC_GROUND_UNIVERSAL:DrawBoundaries(Color)
local Color=Color or{1,0,0}
for AirbaseID,Airbase in pairs(self.Airbases)do
if Airbase.ZoneBoundary then
Airbase.ZoneBoundary:DrawZone(-1,Color)
end
end
return self
end
function ATC_GROUND_UNIVERSAL:SetKickSpeed(KickSpeed,Airbase)
if not Airbase then
self.KickSpeed=KickSpeed
else
self.Airbases[Airbase].KickSpeed=KickSpeed
end
return self
end
function ATC_GROUND_UNIVERSAL:SetKickSpeedKmph(KickSpeed,Airbase)
self:SetKickSpeed(UTILS.KmphToMps(KickSpeed),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:SetKickSpeedMiph(KickSpeedMiph,Airbase)
self:SetKickSpeed(UTILS.MiphToMps(KickSpeedMiph),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeed(MaximumKickSpeed,Airbase)
if not Airbase then
self.MaximumKickSpeed=MaximumKickSpeed
else
self.Airbases[Airbase].MaximumKickSpeed=MaximumKickSpeed
end
return self
end
function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedKmph(MaximumKickSpeed,Airbase)
self:SetMaximumKickSpeed(UTILS.KmphToMps(MaximumKickSpeed),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:SetMaximumKickSpeedMiph(MaximumKickSpeedMiph,Airbase)
self:SetMaximumKickSpeed(UTILS.MiphToMps(MaximumKickSpeedMiph),Airbase)
return self
end
function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
self:I("_AirbaseMonitor")
self.SetClient:ForEachClient(
function(Client)
if Client:IsAlive()then
local IsOnGround=Client:InAir()==false
for AirbaseID,AirbaseMeta in pairs(self.Airbases)do
self:T(AirbaseID,AirbaseMeta.KickSpeed)
if AirbaseMeta.Monitor==true and Client:IsInZone(AirbaseMeta.ZoneBoundary)then
local NotInRunwayZone=true
if AirbaseMeta.ZoneRunways then
for _,_runwaydata in pairs(AirbaseMeta.ZoneRunways)do
local runwaydata=_runwaydata
NotInRunwayZone=(Client:IsNotInZone(_runwaydata.zone)==true)and NotInRunwayZone or false
end
end
if NotInRunwayZone then
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 and not Taxi then
local Speeding=false
if AirbaseMeta.MaximumKickSpeed then
if Velocity:Get()>AirbaseMeta.MaximumKickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.MaximumKickSpeed then
Speeding=true
end
end
if Speeding==true then
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
local Speeding=false
if AirbaseMeta.KickSpeed then
if Velocity:Get()>AirbaseMeta.KickSpeed then
Speeding=true
end
else
if Velocity:Get()>self.KickSpeed then
Speeding=true
end
end
if Speeding==true then
local IsSpeeding=Client:GetState(self,"Speeding")
if IsSpeeding==true then
local SpeedingWarnings=Client:GetState(self,"Warnings")
self:T(SpeedingWarnings)
if SpeedingWarnings<=3 then
Client:Message("Warning "..SpeedingWarnings.."/3! Airbase traffic rule violation! Slow down now! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Warnings",SpeedingWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
else
Client:Message("Attention! You are speeding on the taxiway, slow down! Your speed is "..
Velocity:ToString(),5,"ATC")
Client:SetState(self,"Speeding",true)
Client:SetState(self,"Warnings",1)
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
end
end
if IsOnGround and not IsAboveRunway then
local IsOffRunway=Client:GetState(self,"IsOffRunway")
if IsOffRunway==true then
local OffRunwayWarnings=Client:GetState(self,"OffRunwayWarnings")
self:T(OffRunwayWarnings)
if OffRunwayWarnings<=3 then
Client:Message("Warning "..OffRunwayWarnings.."/3! Airbase traffic rule violation! Get back on the taxi immediately!",5,"ATC")
Client:SetState(self,"OffRunwayWarnings",OffRunwayWarnings+1)
else
MESSAGE:New("Penalty! Player "..Client:GetPlayerName().." has been kicked, due to a severe airbase traffic rule violation ...",10,"ATC"):ToAll()
Client:Destroy()
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
else
Client:Message("Attention! You are off the taxiway. Get back on the taxiway immediately!",5,"ATC")
Client:SetState(self,"IsOffRunway",true)
Client:SetState(self,"OffRunwayWarnings",1)
end
else
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
end
end
else
Client:SetState(self,"Speeding",false)
Client:SetState(self,"Warnings",0)
Client:SetState(self,"IsOffRunway",false)
Client:SetState(self,"OffRunwayWarnings",0)
local Taxi=Client:GetState(self,"Taxi")
if Taxi==true then
Client:Message("You have progressed to the runway ... Await take-off clearance ...",20,"ATC")
Client:SetState(self,"Taxi",false)
end
end
end
end
else
Client:SetState(self,"Taxi",false)
end
end
)
return true
end
function ATC_GROUND_UNIVERSAL:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
return self
end
ATC_GROUND_CAUCASUS={
ClassName="ATC_GROUND_CAUCASUS",
}
function ATC_GROUND_CAUCASUS:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
return self
end
function ATC_GROUND_CAUCASUS:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
end
ATC_GROUND_NEVADA={
ClassName="ATC_GROUND_NEVADA",
}
function ATC_GROUND_NEVADA:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
return self
end
function ATC_GROUND_NEVADA:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
end
ATC_GROUND_NORMANDY={
ClassName="ATC_GROUND_NORMANDY",
}
function ATC_GROUND_NORMANDY:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(40)
self:SetMaximumKickSpeedKmph(100)
return self
end
function ATC_GROUND_NORMANDY:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
end
ATC_GROUND_PERSIANGULF={
ClassName="ATC_GROUND_PERSIANGULF",
}
function ATC_GROUND_PERSIANGULF:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
end
function ATC_GROUND_PERSIANGULF:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
end
ATC_GROUND_MARIANAISLANDS={
ClassName="ATC_GROUND_MARIANAISLANDS",
}
function ATC_GROUND_MARIANAISLANDS:New(AirbaseNames)
local self=BASE:Inherit(self,ATC_GROUND_UNIVERSAL:New(AirbaseNames))
self:SetKickSpeedKmph(50)
self:SetMaximumKickSpeedKmph(150)
return self
end
function ATC_GROUND_MARIANAISLANDS:Start(RepeatScanSeconds)
RepeatScanSeconds=RepeatScanSeconds or 0.05
self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,RepeatScanSeconds)
end
do
DETECTION_BASE={
ClassName="DETECTION_BASE",
DetectionSetGroup=nil,
DetectionRange=nil,
DetectedObjects={},
DetectionRun=0,
DetectedObjectsIdentified={},
DetectedItems={},
DetectedItemsByIndex={},
}
function DETECTION_BASE:New(DetectionSet)
local self=BASE:Inherit(self,FSM:New())
self.DetectedItemCount=0
self.DetectedItemMax=0
self.DetectedItems={}
self.DetectionSet=DetectionSet
self.RefreshTimeInterval=30
self:InitDetectVisual(nil)
self:InitDetectOptical(nil)
self:InitDetectRadar(nil)
self:InitDetectRWR(nil)
self:InitDetectIRST(nil)
self:InitDetectDLINK(nil)
self:FilterCategories({
Unit.Category.AIRPLANE,
Unit.Category.GROUND_UNIT,
Unit.Category.HELICOPTER,
Unit.Category.SHIP,
Unit.Category.STRUCTURE
})
self:SetFriendliesRange(6000)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Detecting")
self:AddTransition("Detecting","Detect","Detecting")
self:AddTransition("Detecting","Detection","Detecting")
self:AddTransition("Detecting","Detected","Detecting")
self:AddTransition("Detecting","DetectedItem","Detecting")
self:AddTransition("*","Stop","Stopped")
return self
end
do
function DETECTION_BASE:onafterStart(From,Event,To)
self:__Detect(1)
end
function DETECTION_BASE:onafterDetect(From,Event,To)
local DetectDelay=0.15
self.DetectionCount=0
self.DetectionRun=0
self:UnIdentifyAllDetectedObjects()
local DetectionTimeStamp=timer.getTime()
for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do
self.DetectedObjects[DetectionObjectName].IsDetected=false
self.DetectedObjects[DetectionObjectName].IsVisible=false
self.DetectedObjects[DetectionObjectName].KnowDistance=nil
self.DetectedObjects[DetectionObjectName].LastTime=nil
self.DetectedObjects[DetectionObjectName].LastPos=nil
self.DetectedObjects[DetectionObjectName].LastVelocity=nil
self.DetectedObjects[DetectionObjectName].Distance=10000000
end
self.DetectionCount=self:CountAliveRecce()
local DetectionInterval=self.DetectionCount/(self.RefreshTimeInterval-1)
self:ForEachAliveRecce(function(DetectionGroup)
self:__Detection(DetectDelay,DetectionGroup,DetectionTimeStamp)
DetectDelay=DetectDelay+DetectionInterval
end)
self:__Detect(-self.RefreshTimeInterval)
end
function DETECTION_BASE:CountAliveRecce()
return self.DetectionSet:CountAlive()
end
function DETECTION_BASE:ForEachAliveRecce(IteratorFunction,...)
self:F2(arg)
self.DetectionSet:ForEachGroupAlive(IteratorFunction,arg)
return self
end
function DETECTION_BASE:onafterDetection(From,Event,To,Detection,DetectionTimeStamp)
self: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:GetFirstUnitAlive()
local DetectedUnits={}
local DetectedTargets=DetectionUnit:GetDetectedTargets(
self.DetectVisual,
self.DetectOptical,
self.DetectRadar,
self.DetectIRST,
self.DetectRWR,
self.DetectDLINK
)
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()
if not self.DetectedObjects[DetectedObjectName]then
self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{}
self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName
self.DetectedObjects[DetectedObjectName].Object=DetectedObject
end
end
end
for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects or{})do
local DetectedObject=DetectedObjectData.Object
if DetectedObject:isExist()then
local TargetIsDetected,TargetIsVisible,TargetKnowType,TargetKnowDistance,TargetLastTime,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected(
DetectedObject,
self.DetectVisual,
self.DetectOptical,
self.DetectRadar,
self.DetectIRST,
self.DetectRWR,
self.DetectDLINK
)
local DetectionAccepted=true
local DetectedObjectName=DetectedObject:getName()
local DetectedObjectType=DetectedObject:getTypeName()
local DetectedObjectVec3=DetectedObject:getPoint()
local DetectedObjectVec2={x=DetectedObjectVec3.x,y=DetectedObjectVec3.z}
local DetectionGroupVec3=Detection:GetVec3()or{x=0,y=0,z=0}
local DetectionGroupVec2={x=DetectionGroupVec3.x,y=DetectionGroupVec3.z}
local Distance=((DetectedObjectVec3.x-DetectionGroupVec3.x)^2+
(DetectedObjectVec3.y-DetectionGroupVec3.y)^2+
(DetectedObjectVec3.z-DetectionGroupVec3.z)^2
)^0.5/1000
local DetectedUnitCategory=DetectedObject:getDesc().category
DetectionAccepted=self._.FilterCategories[DetectedUnitCategory]~=nil and DetectionAccepted or false
if self.AcceptRange and Distance*1000>self.AcceptRange then
DetectionAccepted=false
end
if self.AcceptZones then
local AnyZoneDetection=false
for AcceptZoneID,AcceptZone in pairs(self.AcceptZones)do
local AcceptZone=AcceptZone
if AcceptZone:IsVec2InZone(DetectedObjectVec2)then
AnyZoneDetection=true
end
end
if not AnyZoneDetection then
DetectionAccepted=false
end
end
if self.RejectZones then
for RejectZoneID,RejectZone in pairs(self.RejectZones)do
local RejectZone=RejectZone
if RejectZone:IsVec2InZone(DetectedObjectVec2)==true then
DetectionAccepted=false
end
end
end
if self.RadarBlur then
MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose)
local minheight=self.RadarBlurMinHeight or 250
local thresheight=self.RadarBlurThresHeight or 90
local thresblur=self.RadarBlurThresBlur or 85
local dist=math.floor(Distance)
if dist<=self.RadarBlurClosing then
thresheight=(((dist*dist)/self.RadarBlurClosingSquare)*thresheight)
thresblur=(((dist*dist)/self.RadarBlurClosingSquare)*thresblur)
end
local fheight=math.floor(math.random(1,10000)/100)
local fblur=math.floor(math.random(1,10000)/100)
local unit=UNIT:FindByName(DetectedObjectName)
if unit and unit:IsAlive()then
local AGL=unit:GetAltitude(true)
MESSAGE:New("Unit "..DetectedObjectName.." is at "..math.floor(AGL).."m. Distance "..math.floor(Distance).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose)
MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose)
if fblur>thresblur then DetectionAccepted=false end
if AGL<=minheight and fheight<thresheight then DetectionAccepted=false end
MESSAGE:New("Detection Accepted = "..tostring(DetectionAccepted),10):ToLogIf(self.debug):ToAllIf(self.verbose)
end
end
if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.DistanceProbability then
local DistanceFactor=Distance/4
local DistanceProbabilityReversed=(1-self.DistanceProbability)*DistanceFactor
local DistanceProbability=1-DistanceProbabilityReversed
DistanceProbability=DistanceProbability*30/300
local Probability=math.random()
if Probability>DistanceProbability then
DetectionAccepted=false
end
end
if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.AlphaAngleProbability then
local NormalVec2={x=DetectedObjectVec2.x-DetectionGroupVec2.x,y=DetectedObjectVec2.y-DetectionGroupVec2.y}
local AlphaAngle=math.atan2(NormalVec2.y,NormalVec2.x)
local Sinus=math.sin(AlphaAngle)
local AlphaAngleProbabilityReversed=(1-self.AlphaAngleProbability)*(1-Sinus)
local AlphaAngleProbability=1-AlphaAngleProbabilityReversed
AlphaAngleProbability=AlphaAngleProbability*30/300
local Probability=math.random()
if Probability>AlphaAngleProbability then
DetectionAccepted=false
end
end
if not self.DetectedObjects[DetectedObjectName]and TargetIsVisible and self.ZoneProbability then
for ZoneDataID,ZoneData in pairs(self.ZoneProbability)do
self:F({ZoneData})
local ZoneObject=ZoneData[1]
local ZoneProbability=ZoneData[2]
ZoneProbability=ZoneProbability*30/300
if ZoneObject:IsVec2InZone(DetectedObjectVec2)==true then
local Probability=math.random()
if Probability>ZoneProbability then
DetectionAccepted=false
break
end
end
end
end
if DetectionAccepted then
HasDetectedObjects=true
self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{}
self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName
if TargetIsDetected and TargetIsDetected==true then
self.DetectedObjects[DetectedObjectName].IsDetected=TargetIsDetected
end
if TargetIsDetected and TargetIsVisible and TargetIsVisible==true then
self.DetectedObjects[DetectedObjectName].IsVisible=TargetIsDetected and TargetIsVisible
end
if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then
self.DetectedObjects[DetectedObjectName].KnowType=TargetIsDetected and TargetKnowType
end
self.DetectedObjects[DetectedObjectName].KnowDistance=TargetKnowDistance
self.DetectedObjects[DetectedObjectName].LastTime=(TargetIsDetected and TargetIsVisible==false)and TargetLastTime
self.DetectedObjects[DetectedObjectName].LastPos=(TargetIsDetected and TargetIsVisible==false)and TargetLastPos
self.DetectedObjects[DetectedObjectName].LastVelocity=(TargetIsDetected and TargetIsVisible==false)and TargetLastVelocity
if not self.DetectedObjects[DetectedObjectName].Distance or(Distance and self.DetectedObjects[DetectedObjectName].Distance>Distance)then
self.DetectedObjects[DetectedObjectName].Distance=Distance
end
self.DetectedObjects[DetectedObjectName].DetectionTimeStamp=DetectionTimeStamp
self:F({DetectedObject=self.DetectedObjects[DetectedObjectName]})
local DetectedUnit=UNIT:FindByName(DetectedObjectName)
DetectedUnits[DetectedObjectName]=DetectedUnit
else
self:F({DetectedObject="No more detection for "..DetectedObjectName})
if self.DetectedObjects[DetectedObjectName]then
self.DetectedObjects[DetectedObjectName]=nil
end
end
else
self:F("Removing from DetectedObjects: "..DetectionObjectName)
self.DetectedObjects[DetectionObjectName]=nil
end
end
if HasDetectedObjects then
self:__Detected(0.1,DetectedUnits)
end
end
if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then
for DetectedObjectName,DetectedObject in pairs(self.DetectedObjects)do
if self.DetectedObjects[DetectedObjectName].IsDetected==true and self.DetectedObjects[DetectedObjectName].DetectionTimeStamp+300<=DetectionTimeStamp then
self.DetectedObjects[DetectedObjectName].IsDetected=false
end
end
self:CreateDetectionItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:UpdateDetectedItemDetection(DetectedItem)
self:CleanDetectionItem(DetectedItem,DetectedItemID)
if DetectedItem then
self:__DetectedItem(0.1,DetectedItem)
end
end
end
end
end
do
function DETECTION_BASE:CleanDetectionItem(DetectedItem,DetectedItemID)
local DetectedSet=DetectedItem.Set
if DetectedSet:Count()==0 then
self:RemoveDetectedItem(DetectedItemID)
end
return self
end
function DETECTION_BASE:ForgetDetectedUnit(UnitName)
local DetectedItems=self:GetDetectedItems()
for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
if DetectedSet then
DetectedSet:RemoveUnitsByName(UnitName)
end
end
return self
end
function DETECTION_BASE:CreateDetectionItems()
self:F("Error, in DETECTION_BASE class...")
return self
end
end
do
function DETECTION_BASE:InitDetectVisual(DetectVisual)
self.DetectVisual=DetectVisual
return self
end
function DETECTION_BASE:InitDetectOptical(DetectOptical)
self:F2()
self.DetectOptical=DetectOptical
return self
end
function DETECTION_BASE:InitDetectRadar(DetectRadar)
self:F2()
self.DetectRadar=DetectRadar
return self
end
function DETECTION_BASE:InitDetectIRST(DetectIRST)
self:F2()
self.DetectIRST=DetectIRST
return self
end
function DETECTION_BASE:InitDetectRWR(DetectRWR)
self:F2()
self.DetectRWR=DetectRWR
return self
end
function DETECTION_BASE:InitDetectDLINK(DetectDLINK)
self:F2()
self.DetectDLINK=DetectDLINK
return self
end
end
do
function DETECTION_BASE:FilterCategories(FilterCategories)
self:F2()
self._.FilterCategories={}
if type(FilterCategories)=="table"then
for CategoryID,Category in pairs(FilterCategories)do
self._.FilterCategories[Category]=Category
end
else
self._.FilterCategories[FilterCategories]=FilterCategories
end
return self
end
function DETECTION_BASE:SetRadarBlur(minheight,thresheight,thresblur,closing)
self.RadarBlur=true
self.RadarBlurMinHeight=minheight or 250
self.RadarBlurThresHeight=thresheight or 90
self.RadarBlurThresBlur=thresblur or 85
self.RadarBlurClosing=closing or 20
self.RadarBlurClosingSquare=self.RadarBlurClosing*self.RadarBlurClosing
return self
end
end
do
function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval)
self:F2()
self.RefreshTimeInterval=RefreshTimeInterval
return self
end
end
do
function DETECTION_BASE:SetFriendliesRange(FriendliesRange)
self:F2()
self.FriendliesRange=FriendliesRange
return self
end
end
do
function DETECTION_BASE:SetIntercept(Intercept,InterceptDelay)
self:F2()
self.Intercept=Intercept
self.InterceptDelay=InterceptDelay
return self
end
end
do
function DETECTION_BASE:SetAcceptRange(AcceptRange)
self:F2()
self.AcceptRange=AcceptRange
return self
end
function DETECTION_BASE:SetAcceptZones(AcceptZones)
self:F2()
if type(AcceptZones)=="table"then
if AcceptZones.ClassName and AcceptZones:IsInstanceOf(ZONE_BASE)then
self.AcceptZones={AcceptZones}
else
self.AcceptZones=AcceptZones
end
else
self:F({"AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",AcceptZones})
error()
end
return self
end
function DETECTION_BASE:SetRejectZones(RejectZones)
self:F2()
if type(RejectZones)=="table"then
if RejectZones.ClassName and RejectZones:IsInstanceOf(ZONE_BASE)then
self.RejectZones={RejectZones}
else
self.RejectZones=RejectZones
end
else
self:F({"RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",RejectZones})
error()
end
return self
end
end
do
function DETECTION_BASE:SetDistanceProbability(DistanceProbability)
self:F2()
self.DistanceProbability=DistanceProbability
return self
end
function DETECTION_BASE:SetAlphaAngleProbability(AlphaAngleProbability)
self:F2()
self.AlphaAngleProbability=AlphaAngleProbability
return self
end
function DETECTION_BASE:SetZoneProbability(ZoneArray)
self:F2()
self.ZoneProbability=ZoneArray
return self
end
end
do
function DETECTION_BASE:AcceptChanges(DetectedItem)
DetectedItem.Changed=false
DetectedItem.Changes={}
return self
end
function DETECTION_BASE:AddChangeItem(DetectedItem,ChangeCode,ItemUnitType)
DetectedItem.Changed=true
local ID=DetectedItem.ID
DetectedItem.Changes=DetectedItem.Changes or{}
DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{}
DetectedItem.Changes[ChangeCode].ID=ID
DetectedItem.Changes[ChangeCode].ItemUnitType=ItemUnitType
self:F({"Change on Detected Item:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ItemUnitType=ItemUnitType})
return self
end
function DETECTION_BASE:AddChangeUnit(DetectedItem,ChangeCode,ChangeUnitType)
DetectedItem.Changed=true
local ID=DetectedItem.ID
DetectedItem.Changes=DetectedItem.Changes or{}
DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{}
DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]or 0
DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]+1
DetectedItem.Changes[ChangeCode].ID=ID
self:F({"Change on Detected Unit:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ChangeUnitType=ChangeUnitType})
return self
end
end
do
function DETECTION_BASE:SetFriendlyPrefixes(FriendlyPrefixes)
self.FriendlyPrefixes=self.FriendlyPrefixes or{}
if type(FriendlyPrefixes)~="table"then
FriendlyPrefixes={FriendlyPrefixes}
end
for PrefixID,Prefix in pairs(FriendlyPrefixes)do
self:F({FriendlyPrefix=Prefix})
self.FriendlyPrefixes[Prefix]=Prefix
end
return self
end
function DETECTION_BASE:IsFriendliesNearBy(DetectedItem,Category)
return(DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category]~=nil)or false
end
function DETECTION_BASE:GetFriendliesNearBy(DetectedItem,Category)
return DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category]
end
function DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem)
return DetectedItem.FriendliesNearIntercept~=nil or false
end
function DETECTION_BASE:GetFriendliesNearIntercept(DetectedItem)
return DetectedItem.FriendliesNearIntercept
end
function DETECTION_BASE:GetFriendliesDistance(DetectedItem)
return DetectedItem.FriendliesDistance
end
function DETECTION_BASE:IsPlayersNearBy(DetectedItem)
return DetectedItem.PlayersNearBy~=nil
end
function DETECTION_BASE:GetPlayersNearBy(DetectedItem)
return DetectedItem.PlayersNearBy
end
function DETECTION_BASE:ReportFriendliesNearBy(TargetData)
local DetectedItem=TargetData.DetectedItem
local DetectedSet=TargetData.DetectedItem.Set
local DetectedUnit=DetectedSet:GetFirst()
DetectedItem.FriendliesNearBy=nil
if DetectedUnit and DetectedUnit:IsAlive()then
local DetectedUnitCoord=DetectedUnit:GetCoordinate()
local InterceptCoord=TargetData.InterceptCoord or DetectedUnitCoord
local SphereSearch={
id=world.VolumeType.SPHERE,
params={
point=InterceptCoord:GetVec3(),
radius=self.FriendliesRange,
}
}
local FindNearByFriendlies=function(FoundDCSUnit,ReportGroupData)
local DetectedItem=ReportGroupData.DetectedItem
local DetectedSet=ReportGroupData.DetectedItem.Set
local DetectedUnit=DetectedSet:GetFirst()
local DetectedUnitCoord=DetectedUnit:GetCoordinate()
local InterceptCoord=ReportGroupData.InterceptCoord or DetectedUnitCoord
local ReportSetGroup=ReportGroupData.ReportSetGroup
local EnemyCoalition=DetectedUnit:GetCoalition()
local FoundUnitCoalition=FoundDCSUnit:getCoalition()
local FoundUnitCategory=FoundDCSUnit:getDesc().category
local FoundUnitName=FoundDCSUnit:getName()
local FoundUnitGroupName=FoundDCSUnit:getGroup():getName()
local EnemyUnitName=DetectedUnit:GetName()
local FoundUnitInReportSetGroup=ReportSetGroup:FindGroup(FoundUnitGroupName)~=nil
if FoundUnitInReportSetGroup==true then
for PrefixID,Prefix in pairs(self.FriendlyPrefixes or{})do
if string.find(FoundUnitName,Prefix:gsub("-","%%-"),1)then
FoundUnitInReportSetGroup=false
break
end
end
end
if FoundUnitCoalition~=EnemyCoalition and FoundUnitInReportSetGroup==false then
local FriendlyUnit=UNIT:Find(FoundDCSUnit)
local FriendlyUnitName=FriendlyUnit:GetName()
local FriendlyUnitCategory=FriendlyUnit:GetDesc().category
DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
DetectedItem.FriendliesNearBy[FoundUnitCategory]=DetectedItem.FriendliesNearBy[FoundUnitCategory]or{}
DetectedItem.FriendliesNearBy[FoundUnitCategory][FriendlyUnitName]=FriendlyUnit
local Distance=DetectedUnitCoord:Get2DDistance(FriendlyUnit:GetCoordinate())
DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
DetectedItem.FriendliesDistance[Distance]=FriendlyUnit
return true
end
return true
end
world.searchObjects(Object.Category.UNIT,SphereSearch,FindNearByFriendlies,TargetData)
DetectedItem.PlayersNearBy=nil
_DATABASE:ForEachPlayer(
function(PlayerUnitName)
local PlayerUnit=UNIT:FindByName(PlayerUnitName)
if PlayerUnit and PlayerUnit:IsAlive()then
local coord=PlayerUnit:GetCoordinate()
if coord and coord:IsInRadius(DetectedUnitCoord,self.FriendliesRange)then
local PlayerUnitCategory=PlayerUnit:GetDesc().category
if(not self.FriendliesCategory)or(self.FriendliesCategory and(self.FriendliesCategory==PlayerUnitCategory))then
local PlayerUnitName=PlayerUnit:GetName()
DetectedItem.PlayersNearBy=DetectedItem.PlayersNearBy or{}
DetectedItem.PlayersNearBy[PlayerUnitName]=PlayerUnit
DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{}
DetectedItem.FriendliesNearBy[PlayerUnitCategory]=DetectedItem.FriendliesNearBy[PlayerUnitCategory]or{}
DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName]=PlayerUnit
local Distance=DetectedUnitCoord:Get2DDistance(PlayerUnit:GetCoordinate())
DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{}
DetectedItem.FriendliesDistance[Distance]=PlayerUnit
end
end
end
end)
end
self:F({Friendlies=DetectedItem.FriendliesNearBy,Players=DetectedItem.PlayersNearBy})
end
end
function DETECTION_BASE:IsDetectedObjectIdentified(DetectedObject)
local DetectedObjectName=DetectedObject.Name
if DetectedObjectName then
local DetectedObjectIdentified=self.DetectedObjectsIdentified[DetectedObjectName]==true
return DetectedObjectIdentified
else
return nil
end
end
function DETECTION_BASE:IdentifyDetectedObject(DetectedObject)
local DetectedObjectName=DetectedObject.Name
self.DetectedObjectsIdentified[DetectedObjectName]=true
end
function DETECTION_BASE:UnIdentifyDetectedObject(DetectedObject)
local DetectedObjectName=DetectedObject.Name
self.DetectedObjectsIdentified[DetectedObjectName]=false
end
function DETECTION_BASE:UnIdentifyAllDetectedObjects()
self.DetectedObjectsIdentified={}
end
function DETECTION_BASE:GetDetectedObject(ObjectName)
self:F2({ObjectName=ObjectName})
if ObjectName then
local DetectedObject=self.DetectedObjects[ObjectName]
if DetectedObject then
local DetectedUnit=UNIT:FindByName(ObjectName)
if DetectedUnit and DetectedUnit:IsAlive()then
if self:IsDetectedObjectIdentified(DetectedObject)==false then
return DetectedObject
end
end
end
end
return nil
end
function DETECTION_BASE:GetDetectedUnitTypeName(DetectedUnit)
if DetectedUnit and DetectedUnit:IsAlive()then
local DetectedUnitName=DetectedUnit:GetName()
local DetectedObject=self.DetectedObjects[DetectedUnitName]
if DetectedObject then
if DetectedObject.KnowType then
return DetectedUnit:GetTypeName()
else
return"Unknown"
end
else
return"Unknown"
end
else
return"Dead:"..DetectedUnit:GetName()
end
return"Undetected:"..DetectedUnit:GetName()
end
function DETECTION_BASE:AddDetectedItem(ItemPrefix,DetectedItemKey,Set)
local DetectedItem={}
self.DetectedItemCount=self.DetectedItemCount+1
self.DetectedItemMax=self.DetectedItemMax+1
DetectedItemKey=DetectedItemKey or self.DetectedItemMax
self.DetectedItems[DetectedItemKey]=DetectedItem
self.DetectedItemsByIndex[DetectedItemKey]=DetectedItem
DetectedItem.Index=DetectedItemKey
DetectedItem.Set=Set or SET_UNIT:New():FilterDeads():FilterCrashes()
DetectedItem.ItemID=ItemPrefix.."."..self.DetectedItemMax
DetectedItem.ID=self.DetectedItemMax
DetectedItem.Removed=false
if self.Locking then
self:LockDetectedItem(DetectedItem)
end
return DetectedItem
end
function DETECTION_BASE:AddDetectedItemZone(ItemPrefix,DetectedItemKey,Set,Zone)
self:F({ItemPrefix,DetectedItemKey,Set,Zone})
local DetectedItem=self:AddDetectedItem(ItemPrefix,DetectedItemKey,Set)
DetectedItem.Zone=Zone
return DetectedItem
end
function DETECTION_BASE:RemoveDetectedItem(DetectedItemKey)
local DetectedItem=self.DetectedItems[DetectedItemKey]
if DetectedItem then
self.DetectedItemCount=self.DetectedItemCount-1
local DetectedItemIndex=DetectedItem.Index
self.DetectedItemsByIndex[DetectedItemIndex]=nil
self.DetectedItems[DetectedItemKey]=nil
end
end
function DETECTION_BASE:GetDetectedItems()
return self.DetectedItems
end
function DETECTION_BASE:GetDetectedItemsByIndex()
return self.DetectedItemsByIndex
end
function DETECTION_BASE:GetDetectedItemsCount()
local DetectedCount=self.DetectedItemCount
return DetectedCount
end
function DETECTION_BASE:GetDetectedItemByKey(Key)
self:F({DetectedItems=self.DetectedItems})
local DetectedItem=self.DetectedItems[Key]
if DetectedItem then
return DetectedItem
end
return nil
end
function DETECTION_BASE:GetDetectedItemByIndex(Index)
self:F({self.DetectedItemsByIndex})
local DetectedItem=self.DetectedItemsByIndex[Index]
if DetectedItem then
return DetectedItem
end
return nil
end
function DETECTION_BASE:GetDetectedItemID(DetectedItem)
return DetectedItem and DetectedItem.ItemID or""
end
function DETECTION_BASE:GetDetectedID(Index)
local DetectedItem=self.DetectedItemsByIndex[Index]
if DetectedItem then
return DetectedItem.ID
end
return""
end
function DETECTION_BASE:GetDetectedItemSet(DetectedItem)
local DetectedSetUnit=DetectedItem and DetectedItem.Set
if DetectedSetUnit then
return DetectedSetUnit
end
return nil
end
function DETECTION_BASE:UpdateDetectedItemDetection(DetectedItem)
local IsDetected=false
for UnitName,UnitData in pairs(DetectedItem.Set:GetSet())do
local DetectedObject=self.DetectedObjects[UnitName]
self:F({UnitName=UnitName,IsDetected=DetectedObject.IsDetected})
if DetectedObject.IsDetected then
IsDetected=true
break
end
end
self:F({IsDetected=DetectedItem.IsDetected})
DetectedItem.IsDetected=IsDetected
return IsDetected
end
function DETECTION_BASE:IsDetectedItemDetected(DetectedItem)
return DetectedItem.IsDetected
end
do
function DETECTION_BASE:GetDetectedItemZone(DetectedItem)
local DetectedZone=DetectedItem and DetectedItem.Zone
if DetectedZone then
return DetectedZone
end
local Detected
return nil
end
end
function DETECTION_BASE:LockDetectedItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:LockDetectedItem(DetectedItem)
end
self.Locking=true
return self
end
function DETECTION_BASE:UnlockDetectedItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:UnlockDetectedItem(DetectedItem)
end
self.Locking=nil
return self
end
function DETECTION_BASE:IsDetectedItemLocked(DetectedItem)
return self.Locking and DetectedItem.Locked==true
end
function DETECTION_BASE:LockDetectedItem(DetectedItem)
DetectedItem.Locked=true
return self
end
function DETECTION_BASE:UnlockDetectedItem(DetectedItem)
DetectedItem.Locked=nil
return self
end
function DETECTION_BASE:SetDetectedItemCoordinate(DetectedItem,Coordinate,DetectedItemUnit)
self:F({Coordinate=Coordinate})
if DetectedItem then
if DetectedItemUnit then
DetectedItem.Coordinate=Coordinate
DetectedItem.Coordinate:SetHeading(DetectedItemUnit:GetHeading())
DetectedItem.Coordinate.y=DetectedItemUnit:GetAltitude()
DetectedItem.Coordinate:SetVelocity(DetectedItemUnit:GetVelocityMPS())
end
end
end
function DETECTION_BASE:GetDetectedItemCoordinate(DetectedItem)
self:F({DetectedItem=DetectedItem})
if DetectedItem then
return DetectedItem.Coordinate
end
return nil
end
function DETECTION_BASE:GetDetectedItemCoordinates()
local Coordinates={}
for DetectedItemID,DetectedItem in pairs(self:GetDetectedItems())do
Coordinates[DetectedItem]=self:GetDetectedItemCoordinate(DetectedItem)
end
return Coordinates
end
function DETECTION_BASE:SetDetectedItemThreatLevel(DetectedItem)
local DetectedSet=DetectedItem.Set
if DetectedItem then
DetectedItem.ThreatLevel,DetectedItem.ThreatText=DetectedSet:CalculateThreatLevelA2G()
end
end
function DETECTION_BASE:GetDetectedItemThreatLevel(DetectedItem)
self:F({DetectedItem=DetectedItem})
if DetectedItem then
self:F({ThreatLevel=DetectedItem.ThreatLevel,ThreatText=DetectedItem.ThreatText})
return DetectedItem.ThreatLevel or 0,DetectedItem.ThreatText or""
end
return nil,""
end
function DETECTION_BASE:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F()
return nil
end
function DETECTION_BASE:DetectedReportDetailed(AttackGroup)
self:F()
return nil
end
function DETECTION_BASE:GetDetectionSet()
local DetectionSet=self.DetectionSet
return DetectionSet
end
function DETECTION_BASE:NearestRecce(DetectedItem)
local NearestRecce=nil
local DistanceRecce=1000000000
for RecceGroupName,RecceGroup in pairs(self.DetectionSet:GetSet())do
if RecceGroup and RecceGroup:IsAlive()then
for RecceUnit,RecceUnit in pairs(RecceGroup:GetUnits())do
if RecceUnit:IsActive()then
local RecceUnitCoord=RecceUnit:GetCoordinate()
local Distance=RecceUnitCoord:Get2DDistance(self:GetDetectedItemCoordinate(DetectedItem))
if Distance<DistanceRecce then
DistanceRecce=Distance
NearestRecce=RecceUnit
end
end
end
end
end
DetectedItem.NearestFAC=NearestRecce
DetectedItem.DistanceRecce=DistanceRecce
end
function DETECTION_BASE:Schedule(DelayTime,RepeatInterval)
self:F2()
self.ScheduleDelayTime=DelayTime
self.ScheduleRepeatInterval=RepeatInterval
self.DetectionScheduler=SCHEDULER:New(self,self._DetectionScheduler,{self,"Detection"},DelayTime,RepeatInterval)
return self
end
end
do
DETECTION_UNITS={
ClassName="DETECTION_UNITS",
DetectionRange=nil,
}
function DETECTION_UNITS:New(DetectionSetGroup)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_UNITS:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   New target(s) detected: "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   Invisible or destroyed target(s): "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_UNITS:CreateDetectionItems()
for DetectedItemKey,_DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=_DetectedItem
local DetectedItemSet=DetectedItem.Set
for DetectedUnitName,DetectedUnitData in pairs(DetectedItemSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedObject=nil
if DetectedUnit:IsAlive()then
DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
end
if DetectedObject then
self:IdentifyDetectedObject(DetectedObject)
self:F({"**DETECTED**",IsVisible=DetectedObject.IsVisible})
DetectedItem.TypeName=DetectedUnit:GetTypeName()
DetectedItem.CategoryName=DetectedUnit:GetCategoryName()
DetectedItem.Name=DetectedObject.Name
DetectedItem.IsVisible=DetectedObject.IsVisible
DetectedItem.LastTime=DetectedObject.LastTime
DetectedItem.LastPos=DetectedObject.LastPos
DetectedItem.LastVelocity=DetectedObject.LastVelocity
DetectedItem.KnowType=DetectedObject.KnowType
DetectedItem.KnowDistance=DetectedObject.KnowDistance
DetectedItem.Distance=DetectedObject.Distance
else
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitName)
DetectedItemSet:Remove(DetectedUnitName)
end
end
if DetectedItemSet:Count()==0 then
self:RemoveDetectedItem(DetectedItemKey)
end
end
for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
local DetectedObject=self:GetDetectedObject(DetectedUnitName)
if DetectedObject then
self:T({"Detected Unit #",DetectedUnitName})
local DetectedUnit=UNIT:FindByName(DetectedUnitName)
if DetectedUnit then
local DetectedTypeName=DetectedUnit:GetTypeName()
local DetectedItem=self:GetDetectedItemByKey(DetectedUnitName)
if not DetectedItem then
self:T("Added new DetectedItem")
DetectedItem=self:AddDetectedItem("UNIT",DetectedUnitName)
DetectedItem.TypeName=DetectedUnit:GetTypeName()
DetectedItem.Name=DetectedObject.Name
DetectedItem.IsVisible=DetectedObject.IsVisible
DetectedItem.LastTime=DetectedObject.LastTime
DetectedItem.LastPos=DetectedObject.LastPos
DetectedItem.LastVelocity=DetectedObject.LastVelocity
DetectedItem.KnowType=DetectedObject.KnowType
DetectedItem.KnowDistance=DetectedObject.KnowDistance
DetectedItem.Distance=DetectedObject.Distance
end
DetectedItem.Set:AddUnit(DetectedUnit)
self:AddChangeUnit(DetectedItem,"AU",DetectedTypeName)
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=DetectedItem.Set
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedFirstUnitCoord=DetectedFirstUnit:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedFirstUnitCoord,DetectedFirstUnit)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet})
self:SetDetectedItemThreatLevel(DetectedItem)
self:NearestRecce(DetectedItem)
end
end
function DETECTION_UNITS:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings,ForceA2GCoordinate)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local ReportSummary=""
local UnitDistanceText=""
local UnitCategoryText=""
if DetectedItem.KnowType then
local UnitCategoryName=DetectedItem.CategoryName
if UnitCategoryName then
UnitCategoryText=UnitCategoryName
end
if DetectedItem.TypeName then
UnitCategoryText=UnitCategoryText.." ("..DetectedItem.TypeName..")"
end
else
UnitCategoryText="Unknown"
end
if DetectedItem.KnowDistance then
if DetectedItem.IsVisible then
UnitDistanceText=" at "..string.format("%.2f",DetectedItem.Distance).." km"
end
else
if DetectedItem.IsVisible then
UnitDistanceText=" at +/- "..string.format("%.0f",DetectedItem.Distance).." km"
end
end
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
if ForceA2GCoordinate then
DetectedItemCoordText=DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings)
end
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %s%s",UnitCategoryText,UnitDistanceText))
Report:Add(string.format("Visible: %s",DetectedItem.IsVisible and"yes"or"no"))
Report:Add(string.format("Detected: %s",DetectedItem.IsDetected and"yes"or"no"))
Report:Add(string.format("Distance: %s",DetectedItem.KnowDistance and"yes"or"no"))
return Report
end
return nil
end
function DETECTION_UNITS:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected units:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
end
do
DETECTION_TYPES={
ClassName="DETECTION_TYPES",
DetectionRange=nil,
}
function DETECTION_TYPES:New(DetectionSetGroup)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_TYPES:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   New target(s) detected: "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="   Invisible or destroyed target(s): "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_TYPES:CreateDetectionItems()
for DetectedItemKey,DetectedItem in pairs(self.DetectedItems)do
local DetectedItemSet=DetectedItem.Set
local DetectedTypeName=DetectedItem.TypeName
for DetectedUnitName,DetectedUnitData in pairs(DetectedItemSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedObject=nil
if DetectedUnit:IsAlive()then
DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
end
if DetectedObject then
self:IdentifyDetectedObject(DetectedObject)
else
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitName)
DetectedItemSet:Remove(DetectedUnitName)
end
end
if DetectedItemSet:Count()==0 then
self:RemoveDetectedItem(DetectedItemKey)
end
end
for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
local DetectedObject=self:GetDetectedObject(DetectedUnitName)
if DetectedObject then
self:T({"Detected Unit #",DetectedUnitName})
local DetectedUnit=UNIT:FindByName(DetectedUnitName)
if DetectedUnit then
local DetectedTypeName=DetectedUnit:GetTypeName()
local DetectedItem=self:GetDetectedItemByKey(DetectedTypeName)
if not DetectedItem then
DetectedItem=self:AddDetectedItem("TYPE",DetectedTypeName)
DetectedItem.TypeName=DetectedTypeName
DetectedItem.Name=DetectedUnitName
end
DetectedItem.Set:AddUnit(DetectedUnit)
self:AddChangeUnit(DetectedItem,"AU",DetectedTypeName)
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=DetectedItem.Set
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedUnitCoord=DetectedFirstUnit:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedUnitCoord,DetectedFirstUnit)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet})
self:SetDetectedItemThreatLevel(DetectedItem)
self:NearestRecce(DetectedItem)
end
end
function DETECTION_TYPES:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
self:T(DetectedItem)
if DetectedItem then
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local DetectedItemsCount=DetectedSet:Count()
local DetectedItemType=DetectedItem.TypeName
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemType))
return Report
end
end
function DETECTION_TYPES:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected types:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
end
do
DETECTION_AREAS={
ClassName="DETECTION_AREAS",
DetectionZoneRange=nil,
}
function DETECTION_AREAS:New(DetectionSetGroup,DetectionZoneRange)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetGroup))
self.DetectionZoneRange=DetectionZoneRange or 5000
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_AREAS:GetDetectionZones()
local zoneset=SET_ZONE:New()
for _ID,_Item in pairs(self.DetectedItems)do
local item=_Item
if item.Zone then
zoneset:AddZone(item.Zone)
end
end
return zoneset
end
function DETECTION_AREAS:GetDetectionZoneByID(ID)
local zone=nil
for _ID,_Item in pairs(self.DetectedItems)do
local item=_Item
if item.ID==ID then
zone=item.Zone
break
end
end
return zone
end
function DETECTION_AREAS:GetDetectionZoneCount()
local zoneset=0
for _ID,_Item in pairs(self.DetectedItems)do
if _Item.Zone then
zoneset=zoneset+1
end
end
return zoneset
end
function DETECTION_AREAS:DetectedItemReportMenu(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local ReportSummaryItem
local DetectedZone=self:GetDetectedItemZone(DetectedItem)
local DetectedItemCoordinate=DetectedZone:GetCoordinate()
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local Report=REPORT:New()
Report:Add(DetectedItemID)
Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
return Report
end
return nil
end
function DETECTION_AREAS:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local ReportSummaryItem
local DetectedItemCoordinate=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedAir=DetectedSet:HasAirUnits()
local DetectedAltitude=self:GetDetectedItemCoordinate(DetectedItem)
local DetectedItemCoordText=""
if DetectedAir>0 then
DetectedItemCoordText=DetectedItemCoordinate:ToStringA2A(AttackGroup,Settings)
else
DetectedItemCoordText=DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings)
end
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local DetectedItemsCount=DetectedSet:Count()
local DetectedItemsTypes=DetectedSet:GetTypeNames()
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes))
return Report
end
return nil
end
function DETECTION_AREAS:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected areas:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
function DETECTION_AREAS:CalculateIntercept(DetectedItem)
local DetectedCoord=DetectedItem.Coordinate
local DetectedSpeed=DetectedCoord:GetVelocity()
local DetectedHeading=DetectedCoord:GetHeading()
if self.Intercept then
local DetectedSet=DetectedItem.Set
local TranslateDistance=DetectedSpeed*self.InterceptDelay
local InterceptCoord=DetectedCoord:Translate(TranslateDistance,DetectedHeading)
DetectedItem.InterceptCoord=InterceptCoord
else
DetectedItem.InterceptCoord=DetectedCoord
end
end
function DETECTION_AREAS:SmokeDetectedUnits()
self:F2()
self._SmokeDetectedUnits=true
return self
end
function DETECTION_AREAS:FlareDetectedUnits()
self:F2()
self._FlareDetectedUnits=true
return self
end
function DETECTION_AREAS:SmokeDetectedZones()
self:F2()
self._SmokeDetectedZones=true
return self
end
function DETECTION_AREAS:FlareDetectedZones()
self:F2()
self._FlareDetectedZones=true
return self
end
function DETECTION_AREAS:BoundDetectedZones()
self:F2()
self._BoundDetectedZones=true
return self
end
function DETECTION_AREAS:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AA"then
MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target."
end
if ChangeCode=="AAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RA"then
MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area."
end
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_AREAS:CreateDetectionItems()
self:F("Checking Detected Items for new Detected Units ...")
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
if DetectedItem then
self:T2({"Detected Item ID: ",DetectedItemID})
local DetectedSet=DetectedItem.Set
local AreaExists=false
self:T3({"Zone Center Unit:",DetectedItem.Zone.ZoneUNIT.UnitName})
local DetectedZoneObject=self:GetDetectedObject(DetectedItem.Zone.ZoneUNIT.UnitName)
self:T3({"Detected Zone Object:",DetectedItem.Zone:GetName(),DetectedZoneObject})
if DetectedZoneObject then
AreaExists=true
else
DetectedSet:RemoveUnitsByName(DetectedItem.Zone.ZoneUNIT.UnitName)
self:AddChangeItem(DetectedItem,'RAU',self:GetDetectedUnitTypeName(DetectedItem.Zone.ZoneUNIT))
for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedObject=self:GetDetectedObject(DetectedUnit.UnitName)
local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit)
if DetectedObject then
self:IdentifyDetectedObject(DetectedObject)
AreaExists=true
DetectedItem.Zone=ZONE_UNIT:New(DetectedUnit:GetName(),DetectedUnit,self.DetectionZoneRange)
self:AddChangeItem(DetectedItem,"AAU",DetectedUnitTypeName)
break
else
DetectedSet:Remove(DetectedUnitName)
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName)
end
end
end
if AreaExists then
for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do
local DetectedUnit=DetectedUnitData
local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit)
local DetectedObject=nil
if DetectedUnit:IsAlive()then
DetectedObject=self:GetDetectedObject(DetectedUnit:GetName())
end
if DetectedObject then
if DetectedUnit:IsInZone(DetectedItem.Zone)then
self:IdentifyDetectedObject(DetectedObject)
DetectedSet:AddUnit(DetectedUnit)
else
DetectedSet:Remove(DetectedUnitName)
self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName)
end
else
self:AddChangeUnit(DetectedItem,"RU","destroyed target")
DetectedSet:Remove(DetectedUnitName)
end
end
else
self:RemoveDetectedItem(DetectedItemID)
self:AddChangeItem(DetectedItem,"RA")
end
end
end
for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do
local DetectedObject=self:GetDetectedObject(DetectedUnitName)
if DetectedObject then
local DetectedUnit=UNIT:FindByName(DetectedUnitName)
local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit)
local AddedToDetectionArea=false
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
if DetectedItem then
local DetectedSet=DetectedItem.Set
if not self:IsDetectedObjectIdentified(DetectedObject)and DetectedUnit:IsInZone(DetectedItem.Zone)then
self:IdentifyDetectedObject(DetectedObject)
DetectedSet:AddUnit(DetectedUnit)
AddedToDetectionArea=true
self:AddChangeUnit(DetectedItem,"AU",DetectedUnitTypeName)
end
end
end
if AddedToDetectionArea==false then
local DetectedItem=self:AddDetectedItemZone("AREA",nil,
SET_UNIT:New():FilterDeads():FilterCrashes(),
ZONE_UNIT:New(DetectedUnitName,DetectedUnit,self.DetectionZoneRange)
)
DetectedItem.Set:AddUnit(DetectedUnit)
self:AddChangeItem(DetectedItem,"AA",DetectedUnitTypeName)
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=DetectedItem.Set
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedZone=DetectedItem.Zone
local DetectedZoneCoord=DetectedZone:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit)
self:CalculateIntercept(DetectedItem)
local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet})
local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then
DetectedItem.Changed=true
end
self:SetDetectedItemThreatLevel(DetectedItem)
self:NearestRecce(DetectedItem)
if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedZone.ZoneUNIT:SmokeRed()
end
DetectedSet:ForEachUnit(
function(DetectedUnit)
if DetectedUnit:IsAlive()then
if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then
DetectedUnit:FlareGreen()
end
if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedUnit:SmokeGreen()
end
end
end)
if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then
DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90))
end
if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then
DetectedZone:SmokeZone(SMOKECOLOR.White,30)
end
if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then
self.CountryID=DetectedSet:GetFirst():GetCountry()
DetectedZone:BoundZone(12,self.CountryID)
end
end
end
end
do
DETECTION_ZONES={
ClassName="DETECTION_ZONES",
DetectionZoneRange=nil,
}
function DETECTION_ZONES:New(DetectionSetZone,DetectionCoalition)
local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetZone))
self.DetectionSetZone=DetectionSetZone
self.DetectionCoalition=DetectionCoalition
self._SmokeDetectedUnits=false
self._FlareDetectedUnits=false
self._SmokeDetectedZones=false
self._FlareDetectedZones=false
self._BoundDetectedZones=false
return self
end
function DETECTION_ZONES:CountAliveRecce()
return self.DetectionSetZone:Count()
end
function DETECTION_ZONES:ForEachAliveRecce(IteratorFunction,...)
self:F2(arg)
self.DetectionSetZone:ForEachZone(IteratorFunction,arg)
return self
end
function DETECTION_ZONES:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings)
self:F({DetectedItem=DetectedItem})
local DetectedItemID=self:GetDetectedItemID(DetectedItem)
if DetectedItem then
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local ReportSummaryItem
local DetectedZone=self:GetDetectedItemZone(DetectedItem)
local DetectedItemCoordinate=DetectedZone:GetCoordinate()
local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings)
local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem)
local DetectedItemsCount=DetectedSet:Count()
local DetectedItemsTypes=DetectedSet:GetTypeNames()
local Report=REPORT:New()
Report:Add(DetectedItemID..", "..DetectedItemCoordText)
Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G)))
Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes))
Report:Add(string.format("Detected: %s",DetectedItem.IsDetected and"yes"or"no"))
return Report
end
return nil
end
function DETECTION_ZONES:DetectedReportDetailed(AttackGroup)
self:F()
local Report=REPORT:New()
for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do
local DetectedItem=DetectedItem
local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup)
Report:SetTitle("Detected areas:")
Report:Add(ReportSummary:Text())
end
local ReportText=Report:Text()
return ReportText
end
function DETECTION_ZONES:CalculateIntercept(DetectedItem)
local DetectedCoord=DetectedItem.Coordinate
DetectedItem.InterceptCoord=DetectedCoord
end
function DETECTION_ZONES:SmokeDetectedUnits()
self:F2()
self._SmokeDetectedUnits=true
return self
end
function DETECTION_ZONES:FlareDetectedUnits()
self:F2()
self._FlareDetectedUnits=true
return self
end
function DETECTION_ZONES:SmokeDetectedZones()
self:F2()
self._SmokeDetectedZones=true
return self
end
function DETECTION_ZONES:FlareDetectedZones()
self:F2()
self._FlareDetectedZones=true
return self
end
function DETECTION_ZONES:BoundDetectedZones()
self:F2()
self._BoundDetectedZones=true
return self
end
function DETECTION_ZONES:GetChangeText(DetectedItem)
self:F(DetectedItem)
local MT={}
for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do
if ChangeCode=="AA"then
MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target."
end
if ChangeCode=="AAU"then
MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."."
end
if ChangeCode=="RA"then
MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area."
end
if ChangeCode=="AU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."."
end
if ChangeCode=="RU"then
local MTUT={}
for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do
if ChangeUnitType~="ID"then
MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType
end
end
MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."."
end
end
return table.concat(MT,"\n")
end
function DETECTION_ZONES:CreateDetectionItems()
self:F("Checking Detected Items for new Detected Units ...")
local DetectedUnits=SET_UNIT:New()
for ZoneName,DetectionZone in pairs(self.DetectionSetZone:GetSet())do
local DetectedItem=self:GetDetectedItemByKey(ZoneName)
if DetectedItem==nil then
DetectedItem=self:AddDetectedItemZone("ZONE",ZoneName,nil,DetectionZone)
end
local DetectedItemSetUnit=self:GetDetectedItemSet(DetectedItem)
DetectionZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
local ZoneUnits=DetectionZone:GetScannedUnits()
for DCSUnitID,DCSUnit in pairs(ZoneUnits)do
local UnitName=DCSUnit:getName()
local ZoneUnit=UNIT:FindByName(UnitName)
local ZoneUnitCoalition=ZoneUnit:GetCoalition()
if ZoneUnitCoalition==self.DetectionCoalition then
if DetectedItemSetUnit:FindUnit(UnitName)==nil and DetectedUnits:FindUnit(UnitName)==nil then
self:F("Adding "..UnitName)
DetectedItemSetUnit:AddUnit(ZoneUnit)
DetectedUnits:AddUnit(ZoneUnit)
end
end
end
end
for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do
local DetectedItem=DetectedItemData
local DetectedSet=self:GetDetectedItemSet(DetectedItem)
local DetectedFirstUnit=DetectedSet:GetFirst()
local DetectedZone=self:GetDetectedItemZone(DetectedItem)
local DetectedZoneCoord=DetectedZone:GetCoordinate()
self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit)
self:CalculateIntercept(DetectedItem)
local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup})
local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT)
if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then
DetectedItem.Changed=true
end
self:SetDetectedItemThreatLevel(DetectedItem)
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedZone:SmokeZone(SMOKECOLOR.Red,30)
end
DetectedSet:ForEachUnit(
function(DetectedUnit)
if DetectedUnit:IsAlive()then
if DETECTION_ZONES._FlareDetectedUnits or self._FlareDetectedUnits then
DetectedUnit:FlareGreen()
end
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
DetectedUnit:SmokeGreen()
end
end
end
)
if DETECTION_ZONES._FlareDetectedZones or self._FlareDetectedZones then
DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90))
end
if DETECTION_ZONES._SmokeDetectedZones or self._SmokeDetectedZones then
DetectedZone:SmokeZone(SMOKECOLOR.White,30)
end
if DETECTION_ZONES._BoundDetectedZones or self._BoundDetectedZones then
self.CountryID=DetectedSet:GetFirst():GetCountry()
DetectedZone:BoundZone(12,self.CountryID)
end
end
end
function DETECTION_ZONES:onafterDetection(From,Event,To,Detection,DetectionTimeStamp)
self.DetectionRun=self.DetectionRun+1
if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then
self:CreateDetectionItems()
for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do
self:UpdateDetectedItemDetection(DetectedItem)
self:CleanDetectionItem(DetectedItem,DetectedItemID)
if DetectedItem then
self:__DetectedItem(0.1,DetectedItem)
end
end
self:__Detect(-self.RefreshTimeInterval)
end
end
function DETECTION_ZONES:UpdateDetectedItemDetection(DetectedItem)
local IsDetected=true
DetectedItem.IsDetected=true
return IsDetected
end
end
do
DESIGNATE={
ClassName="DESIGNATE",
}
function DESIGNATE:New(CC,Detection,AttackSet,Mission)
local self=BASE:Inherit(self,FSM:New())
self:F({Detection})
self:SetStartState("Designating")
self:AddTransition("*","Detect","*")
self:AddTransition("*","LaseOn","Lasing")
self:AddTransition("Lasing","Lasing","Lasing")
self:AddTransition("*","LaseOff","Designate")
self:AddTransition("*","Smoke","*")
self:AddTransition("*","Illuminate","*")
self:AddTransition("*","DoneSmoking","*")
self:AddTransition("*","DoneIlluminating","*")
self:AddTransition("*","Status","*")
self.CC=CC
self.Detection=Detection
self.AttackSet=AttackSet
self.RecceSet=Detection:GetDetectionSet()
self.Recces={}
self.Designating={}
self:SetDesignateName()
self:SetLaseDuration()
self:SetFlashStatusMenu(false)
self:SetFlashDetectionMessages(true)
self:SetMission(Mission)
self:SetLaserCodes({1688,1130,4785,6547,1465,4578})
self:SetAutoLase(false,false)
self:SetThreatLevelPrioritization(false)
self:SetMaximumDesignations(5)
self:SetMaximumDistanceDesignations(8000)
self:SetMaximumMarkings(2)
self:SetDesignateMenu()
self.LaserCodesUsed={}
self.MenuLaserCodes={}
self.Detection:__Start(2)
self:__Detect(-15)
self.MarkScheduler=SCHEDULER:New(self)
return self
end
function DESIGNATE:SetFlashStatusMenu(FlashMenu)
self.FlashStatusMenu={}
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
self.FlashStatusMenu[AttackGroup]=FlashMenu
end
)
return self
end
function DESIGNATE:SetFlashDetectionMessages(FlashDetectionMessage)
self.FlashDetectionMessage={}
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
self.FlashDetectionMessage[AttackGroup]=FlashDetectionMessage
end
)
return self
end
function DESIGNATE:SetMaximumDesignations(MaximumDesignations)
self.MaximumDesignations=MaximumDesignations
return self
end
function DESIGNATE:SetMaximumDistanceGroundDesignation(MaximumDistanceGroundDesignation)
self.MaximumDistanceGroundDesignation=MaximumDistanceGroundDesignation
return self
end
function DESIGNATE:SetMaximumDistanceAirDesignation(MaximumDistanceAirDesignation)
self.MaximumDistanceAirDesignation=MaximumDistanceAirDesignation
return self
end
function DESIGNATE:SetMaximumDistanceDesignations(MaximumDistanceDesignations)
self.MaximumDistanceDesignations=MaximumDistanceDesignations
return self
end
function DESIGNATE:SetMaximumMarkings(MaximumMarkings)
self.MaximumMarkings=MaximumMarkings
return self
end
function DESIGNATE:SetLaserCodes(LaserCodes)
self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes}
self:F({LaserCodes=self.LaserCodes})
self.LaserCodesUsed={}
return self
end
function DESIGNATE:AddMenuLaserCode(LaserCode,MenuText)
self.MenuLaserCodes[LaserCode]=MenuText
self:SetDesignateMenu()
return self
end
function DESIGNATE:RemoveMenuLaserCode(LaserCode)
self.MenuLaserCodes[LaserCode]=nil
self:SetDesignateMenu()
return self
end
function DESIGNATE:SetDesignateName(DesignateName)
self.DesignateName="Designation"..(DesignateName and(" for "..DesignateName)or"")
return self
end
function DESIGNATE:SetLaseDuration(LaseDuration)
self.LaseDuration=LaseDuration or 120
return self
end
function DESIGNATE:GenerateLaserCodes()
self.LaserCodes={}
local function containsDigit(_number,_numberToFind)
local _thisNumber=_number
local _thisDigit=0
while _thisNumber~=0 do
_thisDigit=_thisNumber%10
_thisNumber=math.floor(_thisNumber/10)
if _thisDigit==_numberToFind then
return true
end
end
return false
end
local _code=1111
local _count=1
while _code<1777 and _count<30 do
while true do
_code=_code+1
if not containsDigit(_code,8)
and not containsDigit(_code,9)
and not containsDigit(_code,0)then
self:T(_code)
table.insert(self.LaserCodes,_code)
break
end
end
_count=_count+1
end
self.LaserCodesUsed={}
return self
end
function DESIGNATE:SetAutoLase(AutoLase,Message)
self.AutoLase=AutoLase or false
if Message then
local AutoLaseOnOff=(self.AutoLase==true)and"On"or"Off"
local CC=self.CC:GetPositionable()
if CC then
CC:MessageToSetGroup(self.DesignateName..": Auto Lase "..AutoLaseOnOff..".",15,self.AttackSet)
end
end
self:CoordinateLase()
self:SetDesignateMenu()
return self
end
function DESIGNATE:SetThreatLevelPrioritization(Prioritize)
self.ThreatLevelPrioritization=Prioritize
return self
end
function DESIGNATE:SetMission(Mission)
self.Mission=Mission
return self
end
function DESIGNATE:onafterDetect()
self:__Detect(-math.random(60))
self:DesignationScope()
self:CoordinateLase()
self:SendStatus()
self:SetDesignateMenu()
return self
end
function DESIGNATE:DesignationScope()
local DetectedItems=self.Detection:GetDetectedItemsByIndex()
local DetectedItemCount=0
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex)
if DetectedItem then
local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem)
self:F({IsDetected=IsDetected})
if IsDetected==false then
self:F("Removing")
self.Designating[DesignateIndex]=nil
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
if AttackGroup:IsAlive()==true then
local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ")
self.CC:GetPositionable():MessageToGroup("Targets out of LOS\n"..DetectionText,10,AttackGroup,self.DesignateName)
end
end
)
else
DetectedItemCount=DetectedItemCount+1
end
else
self.Designating[DesignateIndex]=nil
end
end
if DetectedItemCount<5 then
for DesignateIndex,DetectedItem in pairs(DetectedItems)do
local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem)
if IsDetected==true then
self:F({DistanceRecce=DetectedItem.DistanceRecce})
if DetectedItem.DistanceRecce<=self.MaximumDistanceDesignations then
if self.Designating[DesignateIndex]==nil then
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
if self.FlashDetectionMessage[AttackGroup]==true then
local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ")
self.CC:GetPositionable():MessageToGroup("Targets detected at \n"..DetectionText,10,AttackGroup,self.DesignateName)
end
end
)
self.Designating[DesignateIndex]=""
break
end
end
end
end
end
return self
end
function DESIGNATE:CoordinateLase()
local DetectedItems=self.Detection:GetDetectedItemsByIndex()
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=DetectedItems[DesignateIndex]
if DetectedItem then
if self.AutoLase then
self:LaseOn(DesignateIndex,self.LaseDuration)
end
end
end
return self
end
function DESIGNATE:SendStatus(MenuAttackGroup)
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
if self.FlashStatusMenu[AttackGroup]or(MenuAttackGroup and(AttackGroup:GetName()==MenuAttackGroup:GetName()))then
local DetectedReport=REPORT:New("Targets ready for Designation:")
local DetectedItems=self.Detection:GetDetectedItemsByIndex()
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=DetectedItems[DesignateIndex]
if DetectedItem then
local Report=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup,nil,true):Text(", ")
DetectedReport:Add(string.rep("-",40))
DetectedReport:Add(" - "..Report)
if string.find(Designating,"L")then
DetectedReport:Add(" - ".."Lasing Targets")
end
if string.find(Designating,"S")then
DetectedReport:Add(" - ".."Smoking Targets")
end
if string.find(Designating,"I")then
DetectedReport:Add(" - ".."Illuminating Area")
end
end
end
local CC=self.CC:GetPositionable()
CC:MessageTypeToGroup(DetectedReport:Text("\n"),MESSAGE.Type.Information,AttackGroup,self.DesignateName)
local DesignationReport=REPORT:New("Marking Targets:")
self.RecceSet:ForEachGroupAlive(
function(RecceGroup)
local RecceUnits=RecceGroup:GetUnits()
for UnitID,RecceData in pairs(RecceUnits)do
local Recce=RecceData
if Recce:IsLasing()then
DesignationReport:Add(" - "..Recce:GetMessageText("Marking "..Recce:GetSpot().Target:GetTypeName().." with laser "..Recce:GetSpot().LaserCode.."."))
end
end
end
)
CC:MessageTypeToGroup(DesignationReport:Text(),MESSAGE.Type.Information,AttackGroup,self.DesignateName)
end
end
)
return self
end
function DESIGNATE:SetMenu(AttackGroup)
self.MenuDesignate=self.MenuDesignate or{}
local MissionMenu=nil
if self.Mission then
MissionMenu=self.Mission:GetMenu(AttackGroup)
end
local MenuTime=timer.getTime()
self.MenuDesignate[AttackGroup]=MENU_GROUP_DELAYED:New(AttackGroup,self.DesignateName,MissionMenu):SetTime(MenuTime):SetTag(self.DesignateName)
local MenuDesignate=self.MenuDesignate[AttackGroup]
if self.AutoLase then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase Off",MenuDesignate,self.MenuAutoLase,self,false):SetTime(MenuTime):SetTag(self.DesignateName)
else
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Auto Lase On",MenuDesignate,self.MenuAutoLase,self,true):SetTime(MenuTime):SetTag(self.DesignateName)
end
local StatusMenu=MENU_GROUP_DELAYED:New(AttackGroup,"Status",MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Report Status",StatusMenu,self.MenuStatus,self,AttackGroup):SetTime(MenuTime):SetTag(self.DesignateName)
if self.FlashStatusMenu[AttackGroup]then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report Off",StatusMenu,self.MenuFlashStatus,self,AttackGroup,false):SetTime(MenuTime):SetTag(self.DesignateName)
else
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Flash Status Report On",StatusMenu,self.MenuFlashStatus,self,AttackGroup,true):SetTime(MenuTime):SetTag(self.DesignateName)
end
local DesignateCount=0
for DesignateIndex,Designating in pairs(self.Designating)do
local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex)
if DetectedItem then
local Coord=self.Detection:GetDetectedItemCoordinate(DetectedItem)
local ID=self.Detection:GetDetectedItemID(DetectedItem)
local MenuText=ID
if DetectedItem.DesignateMenuName then
MenuText=string.format("(%3s) %s",Designating,DetectedItem.DesignateMenuName)
else
MenuText=string.format("(%3s) %s",Designating,MenuText)
end
local DetectedMenu=MENU_GROUP_DELAYED:New(AttackGroup,MenuText,MenuDesignate):SetTime(MenuTime):SetTag(self.DesignateName)
if string.find(Designating,"L",1,true)==nil then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Search other target",DetectedMenu,self.MenuForget,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
for LaserCode,MenuText in pairs(self.MenuLaserCodes)do
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,string.format(MenuText,LaserCode),DetectedMenu,self.MenuLaseCode,self,DesignateIndex,self.LaseDuration,LaserCode):SetTime(MenuTime):SetTag(self.DesignateName)
end
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Lase with random laser code(s)",DetectedMenu,self.MenuLaseOn,self,DesignateIndex,self.LaseDuration):SetTime(MenuTime):SetTag(self.DesignateName)
else
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Stop lasing",DetectedMenu,self.MenuLaseOff,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
end
if string.find(Designating,"S",1,true)==nil then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke red",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Red):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke blue",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Blue):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke green",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Green):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke white",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.White):SetTime(MenuTime):SetTag(self.DesignateName)
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Smoke orange",DetectedMenu,self.MenuSmoke,self,DesignateIndex,SMOKECOLOR.Orange):SetTime(MenuTime):SetTag(self.DesignateName)
end
if string.find(Designating,"I",1,true)==nil then
MENU_GROUP_COMMAND_DELAYED:New(AttackGroup,"Illuminate",DetectedMenu,self.MenuIlluminate,self,DesignateIndex):SetTime(MenuTime):SetTag(self.DesignateName)
end
end
DesignateCount=DesignateCount+1
if DesignateCount>10 then
break
end
end
MenuDesignate:Remove(MenuTime,self.DesignateName)
MenuDesignate:Set()
end
function DESIGNATE:SetDesignateMenu()
self.AttackSet:Flush(self)
local Delay=1
self.AttackSet:ForEachGroupAlive(
function(AttackGroup)
self:ScheduleOnce(Delay,self.SetMenu,self,AttackGroup)
Delay=Delay+1
end
)
return self
end
function DESIGNATE:MenuStatus(AttackGroup)
self:F("Status")
self:SendStatus(AttackGroup)
end
function DESIGNATE:MenuFlashStatus(AttackGroup,Flash)
self:F("Flash Status")
self.FlashStatusMenu[AttackGroup]=Flash
self:SetDesignateMenu()
end
function DESIGNATE:MenuForget(Index)
self:F("Forget")
self.Designating[Index]=""
self:SetDesignateMenu()
end
function DESIGNATE:MenuAutoLase(AutoLase)
self:F("AutoLase")
self:SetAutoLase(AutoLase,true)
end
function DESIGNATE:MenuSmoke(Index,Color)
self:F("Designate through Smoke")
if string.find(self.Designating[Index],"S")==nil then
self.Designating[Index]=self.Designating[Index].."S"
end
self:Smoke(Index,Color)
self:SetDesignateMenu()
end
function DESIGNATE:MenuIlluminate(Index)
self:F("Designate through Illumination")
if string.find(self.Designating[Index],"I",1,true)==nil then
self.Designating[Index]=self.Designating[Index].."I"
end
self:__Illuminate(1,Index)
self:SetDesignateMenu()
end
function DESIGNATE:MenuLaseOn(Index,Duration)
self:F("Designate through Lase")
self:__LaseOn(1,Index,Duration)
self:SetDesignateMenu()
end
function DESIGNATE:MenuLaseCode(Index,Duration,LaserCode)
self:F("Designate through Lase using "..LaserCode)
self:__LaseOn(1,Index,Duration,LaserCode)
self:SetDesignateMenu()
end
function DESIGNATE:MenuLaseOff(Index,Duration)
self:F("Lasing off")
self.Designating[Index]=string.gsub(self.Designating[Index],"L","")
self:__LaseOff(1,Index)
self:SetDesignateMenu()
end
function DESIGNATE:onafterLaseOn(From,Event,To,Index,Duration,LaserCode)
if string.find(self.Designating[Index],"L",1,true)==nil then
self.Designating[Index]=self.Designating[Index].."L"
self.LaseStart=timer.getTime()
self.LaseDuration=Duration
self:Lasing(Index,Duration,LaserCode)
end
end
function DESIGNATE:onafterLasing(From,Event,To,Index,Duration,LaserCodeRequested)
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local MarkingCount=0
local MarkedTypes={}
for TargetUnit,RecceData in pairs(self.Recces)do
local Recce=RecceData
self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()})
if not Recce:IsLasing()then
local LaserCode=Recce:GetLaserCode()
self:F({ClearingLaserCode=LaserCode})
self.LaserCodesUsed[LaserCode]=nil
self.Recces[TargetUnit]=nil
end
end
if LaserCodeRequested then
for TargetUnit,RecceData in pairs(self.Recces)do
local Recce=RecceData
self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()})
if Recce:IsLasing()then
Recce:LaseOff()
local LaserCode=Recce:GetLaserCode()
self:F({ClearingLaserCode=LaserCode})
self.LaserCodesUsed[LaserCode]=nil
self.Recces[TargetUnit]=nil
break
end
end
end
if 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)
self:F({TargetUnit=TargetUnit:GetName()})
if MarkingCount<self.MaximumMarkings then
if TargetUnit:IsAlive()then
local Recce=self.Recces[TargetUnit]
if not Recce then
self:F("Lasing...")
for RecceGroupID,RecceGroup in pairs(self.RecceSet:GetSet())do
for UnitID,UnitData in pairs(RecceGroup:GetUnits()or{})do
local RecceUnit=UnitData
local RecceUnitDesc=RecceUnit:GetDesc()
if RecceUnit:IsLasing()==false then
if RecceUnit:IsDetected(TargetUnit)and RecceUnit:IsLOS(TargetUnit)then
local LaserCodeIndex=math.random(1,#self.LaserCodes)
local LaserCode=self.LaserCodes[LaserCodeIndex]
if LaserCodeRequested and LaserCodeRequested~=LaserCode then
LaserCode=LaserCodeRequested
LaserCodeRequested=nil
end
if not self.LaserCodesUsed[LaserCode]then
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 "..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 "..TargetUnitType.." with laser "..RecceUnit:GetSpot().LaserCode.." for "..Duration.."s.",
10,self.AttackSet,DesignateName)
if not MarkedTypes[TargetUnitType]then
MarkedTypes[TargetUnitType]=true
end
return
end
else
end
else
if not RecceUnit:IsDetected(TargetUnit)or not RecceUnit:IsLOS(TargetUnit)then
local Recce=self.Recces[TargetUnit]
if Recce then
Recce:LaseOff()
Recce:MessageToSetGroup("Target "..TargetUnit:GetTypeName()"out of LOS. Cancelling lase!",10,self.AttackSet,self.DesignateName)
end
else
local TargetUnitType=TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType]then
MarkedTypes[TargetUnitType]=true
end
end
end
end
end
else
MarkingCount=MarkingCount+1
local TargetUnitType=TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType]then
MarkedTypes[TargetUnitType]=true
end
Recce:MessageToSetGroup(self.DesignateName..": Marking "..TargetUnit:GetTypeName().." with laser "..Recce.LaserCode..".",10,self.AttackSet)
end
end
end
end
)
self:__Lasing(-self.LaseDuration,Index,Duration,LaserCodeRequested)
self:SetDesignateMenu()
else
self:LaseOff(Index)
end
end
function DESIGNATE:onafterLaseOff(From,Event,To,Index)
local CC=self.CC:GetPositionable()
if CC then
CC:MessageToSetGroup("Stopped lasing.",5,self.AttackSet,self.DesignateName)
end
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local Recces=self.Recces
for TargetID,RecceData in pairs(Recces)do
local Recce=RecceData
Recce:MessageToSetGroup("Stopped lasing "..Recce:GetSpot().Target:GetTypeName()..".",5,self.AttackSet,self.DesignateName)
Recce:LaseOff()
end
Recces=nil
self.Recces={}
self.LaserCodesUsed={}
self.Designating[Index]=string.gsub(self.Designating[Index],"L","")
self:SetDesignateMenu()
end
function DESIGNATE:onafterSmoke(From,Event,To,Index,Color)
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local TargetSetUnitCount=TargetSetUnit:Count()
local MarkedCount=0
TargetSetUnit:ForEachUnitPerThreatLevel(10,0,
function(SmokeUnit)
if MarkedCount<self.MaximumMarkings then
MarkedCount=MarkedCount+1
self:F("Smoking ...")
local RecceGroup=self.RecceSet:FindNearestGroupFromPointVec2(SmokeUnit:GetPointVec2())
local RecceUnit=RecceGroup:GetUnit(1)
if RecceUnit then
RecceUnit:MessageToSetGroup("Smoking "..SmokeUnit:GetTypeName()..".",5,self.AttackSet,self.DesignateName)
if SmokeUnit:IsAlive()then
SmokeUnit:Smoke(Color,50,2)
end
self.MarkScheduler:Schedule(self,
function()
self:DoneSmoking(Index)
end,{},math.random(180,240)
)
end
end
end
)
end
function DESIGNATE:onafterIlluminate(From,Event,To,Index)
local DetectedItem=self.Detection:GetDetectedItemByIndex(Index)
local TargetSetUnit=self.Detection:GetDetectedItemSet(DetectedItem)
local TargetUnit=TargetSetUnit:GetFirst()
if TargetUnit then
local RecceGroup=self.RecceSet:FindNearestGroupFromPointVec2(TargetUnit:GetPointVec2())
local RecceUnit=RecceGroup:GetUnit(1)
if RecceUnit then
RecceUnit:MessageToSetGroup("Illuminating "..TargetUnit:GetTypeName()..".",5,self.AttackSet,self.DesignateName)
if TargetUnit:IsAlive()then
TargetUnit:GetPointVec3():AddY(math.random(350,500)):AddX(math.random(-50,50)):AddZ(math.random(-50,50)):IlluminationBomb()
TargetUnit:GetPointVec3():AddY(math.random(350,500)):AddX(math.random(-50,50)):AddZ(math.random(-50,50)):IlluminationBomb()
end
self.MarkScheduler:Schedule(self,
function()
self:DoneIlluminating(Index)
end,{},math.random(60,90)
)
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()
end
end
RAT={
ClassName="RAT",
Debug=false,
templategroup=nil,
alias=nil,
spawninitialized=false,
spawndelay=5,
spawninterval=5,
coalition=nil,
country=nil,
category=nil,
groupsize=nil,
friendly="same",
ctable={},
aircraft={},
Vcruisemax=nil,
Vclimb=1500,
AlphaDescent=3.6,
roe="hold",
rot="noreaction",
takeoff=0,
landing=9,
mindist=5000,
maxdist=5000000,
airports_map={},
airports={},
random_departure=true,
random_destination=true,
departure_ports={},
destination_ports={},
Ndestination_Airports=0,
Ndestination_Zones=0,
Ndeparture_Airports=0,
Ndeparture_Zones=0,
destinationzone=false,
return_zones={},
returnzone=false,
excluded_ports={},
departure_Azone=nil,
destination_Azone=nil,
addfriendlydepartures=false,
addfriendlydestinations=false,
ratcraft={},
Tinactive=600,
reportstatus=false,
statusinterval=30,
placemarkers=false,
FLcruise=nil,
FLminuser=nil,
FLmaxuser=nil,
FLuser=nil,
commute=false,
starshape=false,
homebase=nil,
continuejourney=false,
alive=0,
ngroups=nil,
f10menu=false,
Menu={},
SubMenuName=nil,
respawn_at_landing=false,
norespawn=false,
respawn_after_takeoff=false,
respawn_after_crash=true,
respawn_inair=true,
respawn_delay=0,
markerids={},
waypointdescriptions={},
waypointstatus={},
livery=nil,
skill="High",
ATCswitch=true,
radio=nil,
frequency=nil,
modulation=nil,
actype=nil,
uncontrolled=false,
invisible=false,
immortal=false,
activate_uncontrolled=false,
activate_delay=5,
activate_delta=5,
activate_frand=0,
activate_max=1,
onboardnum=nil,
onboardnum0=1,
checkonrunway=true,
onrunwayradius=75,
onrunwaymaxretry=3,
checkontop=false,
ontopradius=2,
termtype=nil,
parkingscanradius=40,
parkingscanscenery=false,
parkingverysafe=false,
despawnair=true,
eplrs=false,
}
RAT.cat={
plane="plane",
heli="heli",
}
RAT.wp={
coldorhot=0,
air=1,
runway=2,
hot=3,
cold=4,
climb=5,
cruise=6,
descent=7,
holding=8,
landing=9,
finalwp=10,
}
RAT.status={
Departure="At departure point",
Climb="Climbing",
Cruise="Cruising",
Uturn="Flying back home",
Descent="Descending",
DescentHolding="Descend to holding point",
Holding="Holding",
Destination="Arrived at destination",
Uncontrolled="Uncontrolled",
Spawned="Spawned",
EventBirthAir="Born in air",
EventBirth="Ready and starting engines",
EventEngineStartAir="On journey",
EventEngineStart="Started engines and taxiing",
EventTakeoff="Airborne after take-off",
EventLand="Landed and taxiing",
EventEngineShutdown="Engines off",
EventDead="Dead",
EventCrash="Crashed",
}
RAT.coal={
same="same",
sameonly="sameonly",
neutral="neutral",
}
RAT.unit={
ft2meter=0.305,
kmh2ms=0.278,
FL2m=30.48,
nm2km=1.852,
nm2m=1852,
}
RAT.ROE={
weaponhold="hold",
weaponfree="free",
returnfire="return",
}
RAT.ROT={
evade="evade",
passive="passive",
noreaction="noreaction",
}
RAT.ATC={
init=false,
flight={},
airport={},
unregistered=-1,
onfinal=-100,
Nclearance=2,
delay=240,
messages=true,
}
RAT.markerid=0
RAT.MenuF10=nil
RAT.id="RAT | "
RAT.version={
version="3.0.0",
print=true,
}
function RAT:New(groupname,alias)
self=BASE:Inherit(self,SPAWN:NewWithAlias(groupname,alias))
self.lid=string.format("RAT %s | ",alias or groupname)
if RAT.version.print then
env.info(self.lid.."Version "..RAT.version.version)
RAT.version.print=false
end
self:F(self.lid..string.format("Creating new RAT object from template: %s.",groupname))
alias=alias or groupname
self.alias=alias
local DCSgroup=Group.getByName(groupname)
if DCSgroup==nil then
self:E(self.lid..string.format("ERROR: Group with name %s does not exist in the mission editor!",groupname))
return nil
end
self.templategroup=GROUP:FindByName(groupname)
self.groupsize=self.templategroup:GetSize()
self.coalition=DCSgroup:getCoalition()
self:_InitAircraft(DCSgroup)
self:_GetAirportsOfMap()
return self
end
function RAT:Stop(delay)
self:T3(self.lid..string.format("Stopping RAT! Delay %s sec!",tostring(delay)))
if delay and delay>0 then
self:T2(self.lid..string.format("Stopping RAT in %d sec!",delay))
self:ScheduleOnce(delay,RAT.Stop,self)
else
self:T(self.lid.."Stopping RAT: Clearing schedulers and unhandling events!")
if self.sid_Activate then
self.Scheduler:ScheduleStop(self.sid_Activate)
end
if self.sid_Spawn then
self.Scheduler:ScheduleStop(self.sid_Spawn)
end
if self.sid_Status then
self.Scheduler:ScheduleStop(self.sid_Status)
end
if self.Scheduler then
self.Scheduler:Clear()
end
self.norespawn=true
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.EngineStartup)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Hit)
end
end
function RAT:Spawn(naircraft)
if self.spawninitialized==true then
self:E("ERROR: Spawn function should only be called once per RAT object! Exiting and returning nil.")
return nil
else
self.spawninitialized=true
end
self.ngroups=naircraft or 1
if self.ATCswitch and not RAT.ATC.init then
RAT._ATCInit(self.airports_map)
end
if self.f10menu and not RAT.MenuF10 then
RAT.MenuF10=MENU_MISSION:New("RAT")
end
self:_SetCoalitionTable()
self:_GetAirportsOfCoalition()
if not self.SubMenuName then
self.SubMenuName=self.alias
end
if self.departure_Azone~=nil then
self.departure_ports=self:_GetAirportsInZone(self.departure_Azone)
end
if self.destination_Azone~=nil then
self.destination_ports=self:_GetAirportsInZone(self.destination_Azone)
end
if self.addfriendlydepartures then
self:_AddFriendlyAirports(self.departure_ports)
end
if self.addfriendlydestinations then
self:_AddFriendlyAirports(self.destination_ports)
end
if self.FLcruise==nil then
if self.category==RAT.cat.plane then
self.FLcruise=200*RAT.unit.FL2m
else
self.FLcruise=005*RAT.unit.FL2m
end
end
if self.category==RAT.cat.heli then
self.mindist=50
end
self:_CheckConsistency()
local text=string.format("\n******************************************************\n")
text=text..string.format("Spawning %i aircraft from template %s of type %s.\n",self.ngroups,self.SpawnTemplatePrefix,self.aircraft.type)
text=text..string.format("Alias: %s\n",self.alias)
text=text..string.format("Category: %s\n",self.category)
text=text..string.format("Friendly coalitions: %s\n",self.friendly)
text=text..string.format("Number of airports on map  : %i\n",#self.airports_map)
text=text..string.format("Number of friendly airports: %i\n",#self.airports)
text=text..string.format("Totally random departure: %s\n",tostring(self.random_departure))
if not self.random_departure then
text=text..string.format("Number of departure airports: %d\n",self.Ndeparture_Airports)
text=text..string.format("Number of departure zones   : %d\n",self.Ndeparture_Zones)
end
text=text..string.format("Totally random destination: %s\n",tostring(self.random_destination))
if not self.random_destination then
text=text..string.format("Number of destination airports: %d\n",self.Ndestination_Airports)
text=text..string.format("Number of destination zones   : %d\n",self.Ndestination_Zones)
end
text=text..string.format("Min dist to destination: %4.1f\n",self.mindist)
text=text..string.format("Max dist to destination: %4.1f\n",self.maxdist)
text=text..string.format("Terminal type: %s\n",tostring(self.termtype))
text=text..string.format("Takeoff type: %i\n",self.takeoff)
text=text..string.format("Landing type: %i\n",self.landing)
text=text..string.format("Commute: %s\n",tostring(self.commute))
text=text..string.format("Journey: %s\n",tostring(self.continuejourney))
text=text..string.format("Destination Zone: %s\n",tostring(self.destinationzone))
text=text..string.format("Return Zone: %s\n",tostring(self.returnzone))
text=text..string.format("Spawn delay: %4.1f\n",self.spawndelay)
text=text..string.format("Spawn interval: %4.1f\n",self.spawninterval)
text=text..string.format("Respawn delay: %s\n",tostring(self.respawn_delay))
text=text..string.format("Respawn off: %s\n",tostring(self.norespawn))
text=text..string.format("Respawn after landing: %s\n",tostring(self.respawn_at_landing))
text=text..string.format("Respawn after take-off: %s\n",tostring(self.respawn_after_takeoff))
text=text..string.format("Respawn after crash: %s\n",tostring(self.respawn_after_crash))
text=text..string.format("Respawn in air: %s\n",tostring(self.respawn_inair))
text=text..string.format("ROE: %s\n",tostring(self.roe))
text=text..string.format("ROT: %s\n",tostring(self.rot))
text=text..string.format("Immortal: %s\n",tostring(self.immortal))
text=text..string.format("Invisible: %s\n",tostring(self.invisible))
text=text..string.format("Vclimb: %4.1f\n",self.Vclimb)
text=text..string.format("AlphaDescent: %4.2f\n",self.AlphaDescent)
text=text..string.format("Vcruisemax: %s\n",tostring(self.Vcruisemax))
text=text..string.format("FLcruise =  %6.1f km = FL%3.0f\n",self.FLcruise/1000,self.FLcruise/RAT.unit.FL2m)
text=text..string.format("FLuser: %s\n",tostring(self.Fluser))
text=text..string.format("FLminuser: %s\n",tostring(self.FLminuser))
text=text..string.format("FLmaxuser: %s\n",tostring(self.FLmaxuser))
text=text..string.format("Place markers: %s\n",tostring(self.placemarkers))
text=text..string.format("Report status: %s\n",tostring(self.reportstatus))
text=text..string.format("Status interval: %4.1f\n",self.statusinterval)
text=text..string.format("Time inactive: %4.1f\n",self.Tinactive)
text=text..string.format("Create F10 menu : %s\n",tostring(self.f10menu))
text=text..string.format("F10 submenu name: %s\n",self.SubMenuName)
text=text..string.format("ATC enabled : %s\n",tostring(self.ATCswitch))
text=text..string.format("Radio comms      : %s\n",tostring(self.radio))
text=text..string.format("Radio frequency  : %s\n",tostring(self.frequency))
text=text..string.format("Radio modulation : %s\n",tostring(self.frequency))
text=text..string.format("Tail # prefix    : %s\n",tostring(self.onboardnum))
text=text..string.format("Check on runway: %s\n",tostring(self.checkonrunway))
text=text..string.format("Max respawn attempts: %s\n",tostring(self.onrunwaymaxretry))
text=text..string.format("Check on top: %s\n",tostring(self.checkontop))
text=text..string.format("Uncontrolled: %s\n",tostring(self.uncontrolled))
if self.uncontrolled and self.activate_uncontrolled then
text=text..string.format("Uncontrolled max  : %4.1f\n",self.activate_max)
text=text..string.format("Uncontrolled delay: %4.1f\n",self.activate_delay)
text=text..string.format("Uncontrolled delta: %4.1f\n",self.activate_delta)
text=text..string.format("Uncontrolled frand: %4.1f\n",self.activate_frand)
end
if self.livery then
text=text..string.format("Available liveries:\n")
for _,livery in pairs(self.livery)do
text=text..string.format("- %s\n",livery)
end
end
text=text..string.format("******************************************************\n")
self:T(self.lid..text)
if self.f10menu then
self.Menu[self.SubMenuName]=MENU_MISSION:New(self.SubMenuName,RAT.MenuF10)
self.Menu[self.SubMenuName]["groups"]=MENU_MISSION:New("Groups",self.Menu[self.SubMenuName])
MENU_MISSION_COMMAND:New("Spawn new group",self.Menu[self.SubMenuName],self._SpawnWithRoute,self)
MENU_MISSION_COMMAND:New("Delete markers",self.Menu[self.SubMenuName],self._DeleteMarkers,self)
MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName],self.Status,self,true)
end
local Tstart=self.spawndelay
local dt=self.spawninterval
if self.takeoff==RAT.wp.runway and not self.random_departure then
dt=math.max(dt,180)
end
local Tstop=Tstart+dt*(self.ngroups-1)
self.sid_Status=self:ScheduleRepeat(Tstart+1,self.statusinterval,nil,nil,RAT.Status,self)
self:HandleEvent(EVENTS.Birth,self._OnBirth)
self:HandleEvent(EVENTS.EngineStartup,self._OnEngineStartup)
self:HandleEvent(EVENTS.Takeoff,self._OnTakeoff)
self:HandleEvent(EVENTS.Land,self._OnLand)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutdown)
self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash)
self:HandleEvent(EVENTS.Hit,self._OnHit)
if self.ngroups==0 then
return nil
end
self.sid_Spawn=self:ScheduleRepeat(Tstart,dt,0.0,Tstop,RAT._SpawnWithRoute,self)
if self.uncontrolled and self.activate_uncontrolled then
self.sid_Activate=self:ScheduleRepeat(self.activate_delay,self.activate_delta,self.activate_frand,nil,RAT._ActivateUncontrolled,self)
end
return true
end
function RAT:_CheckConsistency()
self:F2()
if not self.random_departure then
for _,name in pairs(self.departure_ports)do
if self:_AirportExists(name)then
self.Ndeparture_Airports=self.Ndeparture_Airports+1
elseif self:_ZoneExists(name)then
self.Ndeparture_Zones=self.Ndeparture_Zones+1
end
end
if self.Ndeparture_Zones>0 and self.takeoff~=RAT.wp.air then
self.takeoff=RAT.wp.air
self:E(self.lid..string.format("WARNING: At least one zone defined as departure and takeoff is NOT set to air. Enabling air start for RAT group %s!",self.alias))
end
if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then
self.random_departure=true
local text=string.format("No airports or zones found given in SetDeparture(). Enabling random departure airports for RAT group %s!",self.alias)
self:E(self.lid.."ERROR: "..text)
MESSAGE:New(text,30):ToAll()
end
end
if not self.random_destination then
for _,name in pairs(self.destination_ports)do
if self:_AirportExists(name)then
self.Ndestination_Airports=self.Ndestination_Airports+1
elseif self:_ZoneExists(name)then
self.Ndestination_Zones=self.Ndestination_Zones+1
end
end
if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then
self.landing=RAT.wp.air
self.destinationzone=true
self:E(self.lid.."WARNING: At least one zone defined as destination and landing is NOT set to air. Enabling destination zone!")
end
if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then
self.random_destination=true
local text="No airports or zones found given in SetDestination(). Enabling random destination airports!"
self:E(self.lid.."ERROR: "..text)
MESSAGE:New(text,30):ToAll()
end
end
if self.destinationzone and self.returnzone then
self:E(self.lid.."ERROR: Destination zone _and_ return to zone not possible! Disabling return to zone.")
self.returnzone=false
end
if self.returnzone and self.takeoff==RAT.wp.air then
self.landing=RAT.wp.air
end
if self.FLminuser then
self.FLminuser=math.min(self.FLminuser,self.aircraft.ceiling)
end
if self.FLmaxuser then
self.FLmaxuser=math.min(self.FLmaxuser,self.aircraft.ceiling)
end
if self.FLcruise then
self.FLcruise=math.min(self.FLcruise,self.aircraft.ceiling)
end
if self.FLminuser and self.FLmaxuser then
if self.FLminuser>self.FLmaxuser then
local min=self.FLminuser
local max=self.FLmaxuser
self.FLminuser=max
self.FLmaxuser=min
end
end
if self.FLminuser and self.FLcruise<self.FLminuser then
self.FLcruise=self.FLminuser
end
if self.FLmaxuser and self.FLcruise>self.FLmaxuser then
self.FLcruise=self.FLmaxuser
end
if self.uncontrolled then
self.takeoff=RAT.wp.cold
end
end
function RAT:SetCoalition(friendly)
self:F2(friendly)
if friendly:lower()=="sameonly"then
self.friendly=RAT.coal.sameonly
elseif friendly:lower()=="neutral"then
self.friendly=RAT.coal.neutral
else
self.friendly=RAT.coal.same
end
return self
end
function RAT:SetCoalitionAircraft(color)
self:F2(color)
if color:lower()=="blue"then
self.coalition=coalition.side.BLUE
if not self.country then
self.country=country.id.USA
end
elseif color:lower()=="red"then
self.coalition=coalition.side.RED
if not self.country then
self.country=country.id.RUSSIA
end
elseif color:lower()=="neutral"then
self.coalition=coalition.side.NEUTRAL
if not self.country then
self.country=country.id.SWITZERLAND
end
end
return self
end
function RAT:SetCountry(id)
self:F2(id)
self.country=id
return self
end
function RAT:SetTerminalType(termtype)
self:F2(termtype)
self.termtype=termtype
return self
end
function RAT:SetParkingScanRadius(radius)
self:F2(radius)
self.parkingscanradius=radius or 50
return self
end
function RAT:SetParkingScanSceneryON()
self:F2()
self.parkingscanscenery=true
return self
end
function RAT:SetParkingScanSceneryOFF()
self:F2()
self.parkingscanscenery=false
return self
end
function RAT:SetParkingSpotSafeON()
self:F2()
self.parkingverysafe=true
return self
end
function RAT:SetParkingSpotSafeOFF()
self:F2()
self.parkingverysafe=false
return self
end
function RAT:SetDespawnAirOFF()
self.despawnair=false
return self
end
function RAT:SetTakeoff(type)
self:F2(type)
local _Type
if type:lower()=="takeoff-cold"or type:lower()=="cold"then
_Type=RAT.wp.cold
elseif type:lower()=="takeoff-hot"or type:lower()=="hot"then
_Type=RAT.wp.hot
elseif type:lower()=="takeoff-runway"or type:lower()=="runway"then
_Type=RAT.wp.runway
elseif type:lower()=="air"then
_Type=RAT.wp.air
else
_Type=RAT.wp.coldorhot
end
self.takeoff=_Type
return self
end
function RAT:SetTakeoffCold()
self.takeoff=RAT.wp.cold
return self
end
function RAT:SetTakeoffHot()
self.takeoff=RAT.wp.hot
return self
end
function RAT:SetTakeoffRunway()
self.takeoff=RAT.wp.runway
return self
end
function RAT:SetTakeoffColdOrHot()
self.takeoff=RAT.wp.coldorhot
return self
end
function RAT:SetTakeoffAir()
self.takeoff=RAT.wp.air
return self
end
function RAT:SetDeparture(departurenames)
self:F2(departurenames)
self.random_departure=false
local names
if type(departurenames)=="table"then
names=departurenames
elseif type(departurenames)=="string"then
names={departurenames}
else
self:E(self.lid.."ERROR: Input parameter must be a string or a table in SetDeparture()!")
end
for _,name in pairs(names)do
if self:_AirportExists(name)then
table.insert(self.departure_ports,name)
elseif self:_ZoneExists(name)then
table.insert(self.departure_ports,name)
else
self:E(self.lid.."ERROR: No departure airport or zone found with name "..name)
end
end
return self
end
function RAT:SetDestination(destinationnames)
self:F2(destinationnames)
self.random_destination=false
local names
if type(destinationnames)=="table"then
names=destinationnames
elseif type(destinationnames)=="string"then
names={destinationnames}
else
self:E(self.lid.."ERROR: Input parameter must be a string or a table in SetDestination()!")
end
for _,name in pairs(names)do
if self:_AirportExists(name)then
table.insert(self.destination_ports,name)
elseif self:_ZoneExists(name)then
table.insert(self.destination_ports,name)
else
self:E(self.lid.."ERROR: No destination airport or zone found with name "..name)
end
end
return self
end
function RAT:DestinationZone()
self:F2()
self.destinationzone=true
self.landing=RAT.wp.air
return self
end
function RAT:ReturnZone()
self:F2()
self.returnzone=true
return self
end
function RAT:SetDestinationsFromZone(zone)
self:F2(zone)
self.random_destination=false
self.destination_Azone=zone
return self
end
function RAT:SetDeparturesFromZone(zone)
self:F2(zone)
self.random_departure=false
self.departure_Azone=zone
return self
end
function RAT:AddFriendlyAirportsToDepartures()
self:F2()
self.addfriendlydepartures=true
return self
end
function RAT:AddFriendlyAirportsToDestinations()
self:F2()
self.addfriendlydestinations=true
return self
end
function RAT:ExcludedAirports(ports)
self:F2(ports)
if type(ports)=="string"then
self.excluded_ports={ports}
else
self.excluded_ports=ports
end
return self
end
function RAT:SetAISkill(skill)
self:F2(skill)
if skill:lower()=="average"then
self.skill="Average"
elseif skill:lower()=="good"then
self.skill="Good"
elseif skill:lower()=="excellent"then
self.skill="Excellent"
elseif skill:lower()=="random"then
self.skill="Random"
else
self.skill="High"
end
return self
end
function RAT:Livery(skins)
self:F2(skins)
if type(skins)=="string"then
self.livery={skins}
else
self.livery=skins
end
return self
end
function RAT:ChangeAircraft(actype)
self:F2(actype)
self.actype=actype
return self
end
function RAT:ContinueJourney()
self:F2()
self.continuejourney=true
self.commute=false
return self
end
function RAT:Commute(starshape)
self:F2()
self.commute=true
self.continuejourney=false
if starshape then
self.starshape=starshape
else
self.starshape=false
end
return self
end
function RAT:SetSpawnDelay(delay)
self:F2(delay)
delay=delay or 5
self.spawndelay=math.max(0.5,delay)
return self
end
function RAT:SetSpawnInterval(interval)
self:F2(interval)
interval=interval or 5
self.spawninterval=math.max(0.5,interval)
return self
end
function RAT:SetSpawnLimit(Nmax)
self.NspawnMax=Nmax
return self
end
function RAT:RespawnAfterLanding(delay)
self:F2(delay)
self.respawn_at_landing=true
self:SetRespawnDelay(delay)
return self
end
function RAT:SetRespawnDelay(delay)
self:F2(delay)
delay=delay or 1.0
delay=math.max(1.0,delay)
self.respawn_delay=delay
return self
end
function RAT:NoRespawn()
self:F2()
self.norespawn=true
return self
end
function RAT:SetMaxRespawnTriedWhenSpawnedOnRunway(n)
self:F2(n)
n=n or 3
self.onrunwaymaxretry=n
return self
end
function RAT:RespawnAfterTakeoff()
self:F2()
self.respawn_after_takeoff=true
return self
end
function RAT:RespawnAfterCrashON()
self:F2()
self.respawn_after_crash=true
return self
end
function RAT:RespawnAfterCrashOFF()
self:F2()
self.respawn_after_crash=false
return self
end
function RAT:RespawnInAirAllowed()
self:F2()
self.respawn_inair=true
return self
end
function RAT:RespawnInAirNotAllowed()
self:F2()
self.respawn_inair=false
return self
end
function RAT:CheckOnRunway(switch,distance)
self:F2(switch)
if switch==nil then
switch=true
end
self.checkonrunway=switch
self.onrunwayradius=distance or 75
return self
end
function RAT:CheckOnTop(switch,radius)
self:F2(switch)
if switch==nil then
switch=true
end
self.checkontop=switch
self.ontopradius=radius or 2
return self
end
function RAT:RadioON()
self:F2()
self.radio=true
return self
end
function RAT:RadioOFF()
self:F2()
self.radio=false
return self
end
function RAT:RadioFrequency(frequency)
self:F2(frequency)
self.frequency=frequency
return self
end
function RAT:RadioModulation(modulation)
self:F2(modulation)
if modulation=="AM"then
self.modulation=radio.modulation.AM
elseif modulation=="FM"then
self.modulation=radio.modulation.FM
else
self.modulation=radio.modulation.AM
end
return self
end
function RAT:RadioMenuON()
self:F2()
self.f10menu=true
return self
end
function RAT:RadioMenuOFF()
self:F2()
self.f10menu=false
return self
end
function RAT:Invisible()
self:F2()
self.invisible=true
return self
end
function RAT:SetEPLRS(switch)
if switch==nil or switch==true then
self.eplrs=true
else
self.eplrs=false
end
return self
end
function RAT:Immortal()
self:F2()
self.immortal=true
return self
end
function RAT:Uncontrolled()
self:F2()
self.uncontrolled=true
return self
end
function RAT:ActivateUncontrolled(maxactivated,delay,delta,frand)
self:F2({max=maxactivated,delay=delay,delta=delta,rand=frand})
self.activate_uncontrolled=true
self.activate_max=maxactivated or 1
self.activate_delay=delay or 1
self.activate_delta=delta or 1
self.activate_frand=frand or 0
self.activate_delay=math.max(self.activate_delay,1)
self.activate_delta=math.max(self.activate_delta,0)
self.activate_frand=math.max(self.activate_frand,0)
self.activate_frand=math.min(self.activate_frand,1)
return self
end
function RAT:TimeDestroyInactive(time)
self:F2(time)
time=time or self.Tinactive
time=math.max(time,60)
self.Tinactive=time
return self
end
function RAT:SetMaxCruiseSpeed(speed)
self:F2(speed)
self.Vcruisemax=speed/3.6
return self
end
function RAT:SetClimbRate(rate)
self:F2(rate)
rate=rate or self.Vclimb
rate=math.max(rate,100)
rate=math.min(rate,15000)
self.Vclimb=rate
return self
end
function RAT:SetDescentAngle(angle)
self:F2(angle)
angle=angle or self.AlphaDescent
angle=math.max(angle,0.5)
angle=math.min(angle,50)
self.AlphaDescent=angle
return self
end
function RAT:SetROE(roe)
self:F2(roe)
if roe=="return"then
self.roe=RAT.ROE.returnfire
elseif roe=="free"then
self.roe=RAT.ROE.weaponfree
else
self.roe=RAT.ROE.weaponhold
end
return self
end
function RAT:SetROT(rot)
self:F2(rot)
if rot=="passive"then
self.rot=RAT.ROT.passive
elseif rot=="evade"then
self.rot=RAT.ROT.evade
else
self.rot=RAT.ROT.noreaction
end
return self
end
function RAT:MenuName(name)
self:F2(name)
self.SubMenuName=tostring(name)
return self
end
function RAT:EnableATC(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.ATCswitch=switch
return self
end
function RAT:ATC_Messages(switch)
self:F2(switch)
if switch==nil then
switch=true
end
RAT.ATC.messages=switch
return self
end
function RAT:ATC_Clearance(n)
self:F2(n)
RAT.ATC.Nclearance=n or 2
return self
end
function RAT:ATC_Delay(time)
self:F2(time)
RAT.ATC.delay=time or 240
return self
end
function RAT:SetMinDistance(dist)
self:F2(dist)
self.mindist=math.max(100,dist*1000)
return self
end
function RAT:SetMaxDistance(dist)
self:F2(dist)
self.maxdist=dist*1000
return self
end
function RAT:_Debug(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.Debug=switch
return self
end
function RAT:Debugmode()
self:F2()
self.Debug=true
return self
end
function RAT:StatusReports(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.reportstatus=switch
return self
end
function RAT:PlaceMarkers(switch)
self:F2(switch)
if switch==nil then
switch=true
end
self.placemarkers=switch
return self
end
function RAT:SetFL(FL)
self:F2(FL)
FL=FL or self.FLcruise
FL=math.max(FL,0)
self.FLuser=FL*RAT.unit.FL2m
return self
end
function RAT:SetFLmax(FL)
self:F2(FL)
self.FLmaxuser=FL*RAT.unit.FL2m
return self
end
function RAT:SetMaxCruiseAltitude(alt)
self:F2(alt)
self.FLmaxuser=alt
return self
end
function RAT:SetFLmin(FL)
self:F2(FL)
self.FLminuser=FL*RAT.unit.FL2m
return self
end
function RAT:SetMinCruiseAltitude(alt)
self:F2(alt)
self.FLminuser=alt
return self
end
function RAT:SetFLcruise(FL)
self:F2(FL)
self.FLcruise=FL*RAT.unit.FL2m
return self
end
function RAT:SetCruiseAltitude(alt)
self:F2(alt)
self.FLcruise=alt
return self
end
function RAT:SetOnboardNum(tailnumprefix,zero)
self:F2({tailnumprefix=tailnumprefix,zero=zero})
self.onboardnum=tailnumprefix
if zero~=nil then
self.onboardnum0=zero
end
return self
end
function RAT:_InitAircraft(DCSgroup)
self:F2(DCSgroup)
local DCSunit=DCSgroup:getUnit(1)
local DCSdesc=DCSunit:getDesc()
local DCScategory=DCSgroup:getCategory()
local DCStype=DCSunit:getTypeName()
if DCScategory==Group.Category.AIRPLANE then
self.category=RAT.cat.plane
elseif DCScategory==Group.Category.HELICOPTER then
self.category=RAT.cat.heli
else
self.category="other"
self:E(self.lid.."ERROR: Group of RAT is neither airplane nor helicopter!")
end
self.aircraft.type=DCStype
self.aircraft.fuel=DCSunit:getFuel()
self.aircraft.Rmax=DCSdesc.range*RAT.unit.nm2m
self.aircraft.Reff=self.aircraft.Rmax*self.aircraft.fuel*0.95
self.aircraft.Vmax=DCSdesc.speedMax
self.aircraft.Vymax=DCSdesc.VyMax
self.aircraft.ceiling=DCSdesc.Hmax
if DCSdesc.box then
self.aircraft.length=DCSdesc.box.max.x
self.aircraft.height=DCSdesc.box.max.y
self.aircraft.width=DCSdesc.box.max.z
elseif DCStype=="Mirage-F1CE"then
self.aircraft.length=16
self.aircraft.height=5
self.aircraft.width=9
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")
text=text..string.format("Aircraft parameters:\n")
text=text..string.format("Template group  =  %s\n",self.SpawnTemplatePrefix)
text=text..string.format("Alias           =  %s\n",self.alias)
text=text..string.format("Category        =  %s\n",self.category)
text=text..string.format("Type            =  %s\n",self.aircraft.type)
text=text..string.format("Length (x)      = %6.1f m\n",self.aircraft.length)
text=text..string.format("Width  (z)      = %6.1f m\n",self.aircraft.width)
text=text..string.format("Height (y)      = %6.1f m\n",self.aircraft.height)
text=text..string.format("Max air speed   = %6.1f m/s\n",self.aircraft.Vmax)
text=text..string.format("Max climb speed = %6.1f m/s\n",self.aircraft.Vymax)
text=text..string.format("Initial Fuel    = %6.1f\n",self.aircraft.fuel*100)
text=text..string.format("Max range       = %6.1f km\n",self.aircraft.Rmax/1000)
text=text..string.format("Eff range       = %6.1f km (with 95 percent initial fuel amount)\n",self.aircraft.Reff/1000)
text=text..string.format("Ceiling         = %6.1f km = FL%3.0f\n",self.aircraft.ceiling/1000,self.aircraft.ceiling/RAT.unit.FL2m)
text=text..string.format("******************************************************\n")
self:T(self.lid..text)
end
function RAT:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata)
self:F({rat=self.lid,departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,waypoint=_waypoint,lastpos=_lastpos,nrespawn=_nrespawn})
if self.NspawnMax and self.SpawnIndex>=self.NspawnMax then
self:T(self.lid..string.format("Max limit of spawns reached %d >= %d! Will not spawn any more groups",self.NspawnMax,self.SpawnIndex))
return
else
self:T2(self.lid..string.format("Spawning with spawn index=%d",self.SpawnIndex))
end
local takeoff=self.takeoff
local landing=self.landing
if _takeoff then
takeoff=_takeoff
end
if _landing then
landing=_landing
end
if takeoff==RAT.wp.coldorhot then
local temp={RAT.wp.cold,RAT.wp.hot}
takeoff=temp[math.random(2)]
end
local nrespawn=0
if _nrespawn then
nrespawn=_nrespawn
end
local departure,destination,waypoints,wpdesc,wpstatus=self:_SetRoute(takeoff,landing,_departure,_destination,_waypoint)
if not(departure and destination and waypoints)then
return nil
end
local livery
if _livery then
livery=_livery
elseif self.livery then
livery=self.livery[math.random(#self.livery)]
local text=string.format("Chosen livery for group %s: %s",self:_AnticipatedGroupName(),livery)
self:T(self.lid..text)
else
livery=nil
end
local uncontrolled=self.uncontrolled
local isFlightcontrol=self:_IsFlightControlAirbase(departure)
if takeoff~=RAT.wp.air and departure and isFlightcontrol then
takeoff=RAT.wp.cold
uncontrolled=true
end
local successful=self:_ModifySpawnTemplate(waypoints,livery,_lastpos,departure,takeoff,parkingdata,uncontrolled)
if not successful then
return nil
end
local group=self:SpawnWithIndex(self.SpawnIndex)
local groupname=group:GetName()
local flightgroup=FLIGHTGROUP:New(group)
if self.ATCswitch then
flightgroup.holdtime=nil
end
flightgroup.stuckDespawn=false
self.alive=self.alive+1
self:T(self.lid..string.format("Alive groups counter now = %d.",self.alive))
if self.ATCswitch and landing==RAT.wp.landing then
local airbasename=destination:GetName()
if self.returnzone then
airbasename=departure:GetName()
end
if not self:_IsFlightControlAirbase(airbasename)then
self:_ATCAddFlight(groupname,airbasename)
end
end
if self.placemarkers then
self:_PlaceMarkers(waypoints,wpdesc,self.SpawnIndex)
end
if isFlightcontrol and not self.activate_uncontrolled then
local N=math.random(120)
self:T(self.lid..string.format("Flight will be ready for takeoff in %d seconds",N))
flightgroup:SetReadyForTakeoff(true,N)
end
if self.invisible then
flightgroup:SetDefaultInvisible(true)
flightgroup:SwitchInvisible(true)
end
if self.immortal then
flightgroup:SetDefaultImmortal(true)
flightgroup:SwitchImmortal(true)
end
if self.eplrs then
flightgroup:SetDefaultEPLRS(true)
flightgroup:SwitchEPLRS(true)
end
self:_SetROE(flightgroup,self.roe)
self:_SetROT(flightgroup,self.rot)
local ratcraft={}
ratcraft.index=self.SpawnIndex
ratcraft.group=group
ratcraft.flightgroup=flightgroup
ratcraft.destination=destination
ratcraft.departure=departure
ratcraft.waypoints=waypoints
ratcraft.airborne=group:InAir()
ratcraft.nunits=group:GetInitialSize()
ratcraft.Pnow=group:GetCoordinate()
ratcraft.Distance=0
ratcraft.takeoff=takeoff
ratcraft.landing=landing
ratcraft.wpdesc=wpdesc
ratcraft.wpstatus=wpstatus
ratcraft.active=not uncontrolled
ratcraft.status=RAT.status.Spawned
ratcraft.livery=livery
ratcraft.despawnme=false
ratcraft.nrespawn=nrespawn
self.ratcraft[self.SpawnIndex]=ratcraft
if self.f10menu then
local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex)
self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name,self.Menu[self.SubMenuName].groups)
self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE",self.Menu[self.SubMenuName].groups[self.SpawnIndex])
MENU_MISSION_COMMAND:New("Weapons hold",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,RAT.ROE.weaponhold)
MENU_MISSION_COMMAND:New("Weapons free",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,RAT.ROE.weaponfree)
MENU_MISSION_COMMAND:New("Return fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,flightgroup,RAT.ROE.returnfire)
self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT",self.Menu[self.SubMenuName].groups[self.SpawnIndex])
MENU_MISSION_COMMAND:New("No reaction",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,RAT.ROT.noreaction)
MENU_MISSION_COMMAND:New("Passive defense",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,RAT.ROT.passive)
MENU_MISSION_COMMAND:New("Evade on fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,flightgroup,RAT.ROT.evade)
MENU_MISSION_COMMAND:New("Despawn group",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._Despawn,self,group)
MENU_MISSION_COMMAND:New("Place markers",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._PlaceMarkers,self,waypoints,self.SpawnIndex)
MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self.Status,self,true,self.SpawnIndex)
end
function flightgroup.OnAfterPassingWaypoint(Flightgroup,From,Event,To,Waypoint)
local waypoint=Waypoint
local flightgroup=Flightgroup
local wpid=waypoint.uid
local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group)
local wpdescription=tostring(ratcraft.wpdesc[wpid])
local wpstatus=ratcraft.wpstatus[wpid]
self:T(self.lid..string.format("RAT passed waypoint %s [uid=%d]: %s [status=%s]",waypoint.name,wpid,wpdescription,wpstatus))
self:_SetStatus(group,wpstatus)
if waypoint.uid==3 then
end
end
function flightgroup.OnAfterPassedFinalWaypoint(flightgroup,From,Event,To)
self:T(self.lid..string.format("RAT passed FINAL waypoint"))
local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group)
local text=string.format("Flight %s arrived at final destination %s.",group:GetName(),destination:GetName())
MESSAGE:New(text,10):ToAllIf(self.reportstatus)
self:T(self.lid..text)
if landing==RAT.wp.air then
local text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.",group:GetName())
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
ratcraft.despawnme=true
end
end
function flightgroup.OnAfterRTB(flightgroup,From,Event,To,airbase,SpeedTo,SpeedHold,SpeedLand)
self:T(self.lid..string.format("RAT group is RTB"))
end
function flightgroup.OnAfterHolding(Flightgroup,From,Event,To)
local flightgroup=Flightgroup
local ratcraft=self:_GetRatcraftFromGroup(flightgroup.group)
local destinationname=ratcraft.destination:GetName()
local text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.",groupname,destinationname)
self:T(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.reportstatus)
local fc=_DATABASE:GetFlightControl(destinationname)
if self.ATCswitch and not fc then
self:T(self.lid..string.format("RAT group is HOLDING ==> ATCRegisterFlight"))
if self.f10menu then
MENU_MISSION_COMMAND:New("Clear for landing",self.Menu[self.SubMenuName].groups[self.SpawnIndex],flightgroup.ClearToLand,flightgroup)
end
RAT._ATCRegisterFlight(groupname,timer.getTime())
end
end
function flightgroup.OnAfterLanded(Flightgroup,From,Event,To,Airport)
self:T(self.lid..string.format("RAT group landed at airbase"))
end
function flightgroup.OnAfterArrived(Flightgroup,From,Event,To)
self:T(self.lid..string.format("RAT group arrived"))
end
function flightgroup.OnAfterStuck(Flightgroup,From,Event,To,Stucktime)
local flightgroup=Flightgroup
self:T(self.lid..string.format("Group %s got stuck for %d seconds",flightgroup:GetName(),Stucktime))
if Stucktime>10*60 then
self:_Respawn(flightgroup.group)
end
end
return self.SpawnIndex
end
function RAT:_IsFlightControlAirbase(airbase)
if type(airbase)=="table"then
airbase=airbase:GetName()
end
if airbase then
local fc=_DATABASE:GetFlightControl(airbase)
if fc then
self:T(self.lid..string.format("Airbase %s has a FLIGHTCONTROL running",airbase))
return true
else
return false
end
end
return nil
end
function RAT:ClearForLanding(name)
trigger.action.setUserFlag(name,1)
local flagvalue=trigger.misc.getUserFlag(name)
self:T(self.lid.."ATC: User flag value (landing) for "..name.." set to "..flagvalue)
end
function RAT:_Respawn(group,lastpos,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,RAT._Respawn,self,group,lastpos,0)
else
if group then
self:T(self.lid..string.format("Respawning ratcraft from group %s",group:GetName()))
else
self:E(self.lid..string.format("ERROR: group is nil in _Respawn!"))
return nil
end
local ratcraft=self:_GetRatcraftFromGroup(group)
lastpos=lastpos or group:GetCoordinate()
local departure=ratcraft.departure
local destination=ratcraft.destination
local takeoff=ratcraft.takeoff
local landing=ratcraft.landing
local livery=ratcraft.livery
local lastwp=ratcraft.waypoints[#ratcraft.waypoints]
local flightgroup=ratcraft.flightgroup
local parkingdata=nil
if self.continuejourney or self.commute then
for _,_element in pairs(flightgroup.elements)do
local element=_element
if element.parking then
if parkingdata==nil then
parkingdata={}
end
self:T(self.lid..string.format("Element %s was parking at spot id=%d",element.name,element.parking.TerminalID))
table.insert(parkingdata,UTILS.DeepCopy(element.parking))
else
self:E(self.lid..string.format("WARNING: Element %s did NOT have a not parking spot!",tostring(element.name)))
end
end
end
self:_Despawn(ratcraft.group)
local _departure=nil
local _destination=nil
local _takeoff=nil
local _landing=nil
local _livery=nil
local _lastwp=nil
local _lastpos=nil
if self.continuejourney then
_departure=destination:GetName()
_livery=livery
if landing==RAT.wp.landing and not(self.respawn_at_landing or self.respawn_after_takeoff)then
if destination:GetCategory()==4 then
_lastpos=lastpos
end
end
if self.destinationzone then
_takeoff=RAT.wp.air
_landing=RAT.wp.air
elseif self.returnzone then
_takeoff=self.takeoff
if self.takeoff==RAT.wp.air then
_landing=RAT.wp.air
else
_landing=RAT.wp.landing
end
_departure=departure:GetName()
else
_takeoff=self.takeoff
_landing=self.landing
end
elseif self.commute then
if self.starshape==true then
if destination:GetName()==self.homebase then
_departure=self.homebase
_destination=nil
else
_departure=destination:GetName()
_destination=self.homebase
end
else
_departure=destination:GetName()
_destination=departure:GetName()
end
_livery=livery
if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then
if destination:GetCategory()==4 then
_lastpos=lastpos
end
end
if self.destinationzone then
if self.takeoff==RAT.wp.air then
_takeoff=RAT.wp.air
_landing=RAT.wp.air
else
if takeoff==RAT.wp.air then
_takeoff=self.takeoff
_landing=RAT.wp.air
else
_takeoff=RAT.wp.air
_landing=RAT.wp.landing
end
end
elseif self.returnzone then
_departure=departure:GetName()
_destination=destination:GetName()
_takeoff=self.takeoff
_landing=self.landing
end
end
if _takeoff==RAT.wp.air and(self.continuejourney or self.commute)then
_lastwp=lastwp
end
self:T2({departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,lastwp=_lastwp})
local respawndelay=self.respawn_delay or 1
self:T(self.lid..string.format("%s delayed respawn in %.1f seconds.",self.alias,respawndelay))
self:ScheduleOnce(respawndelay,RAT._SpawnWithRoute,self,_departure,_destination,_takeoff,_landing,_livery,nil,_lastpos,nil,parkingdata)
end
end
function RAT:_Despawn(group,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,RAT._Despawn,self,group,0)
else
if group then
local index=self:GetSpawnIndexFromGroup(group)
if index then
self:T(self.lid..string.format("Despawning group %s (index=%d)",group:GetName(),index))
local ratcraft=self.ratcraft[index]
ratcraft.flightgroup:Despawn()
ratcraft.flightgroup:__Stop(0.1)
self.ratcraft[index].group=nil
self.ratcraft[index]["status"]="Dead"
self.ratcraft[index]=nil
if self.f10menu and self.SubMenuName~=nil then
self.Menu[self.SubMenuName]["groups"][index]:Remove()
end
end
end
end
end
function RAT:_SetRoute(takeoff,landing,_departure,_destination,_waypoint)
local VxCruiseMax
if self.Vcruisemax then
VxCruiseMax=math.min(self.Vcruisemax,self.aircraft.Vmax)
else
VxCruiseMax=math.min(self.aircraft.Vmax*0.90,250)
end
local VxCruiseMin=math.min(VxCruiseMax*0.70,166)
local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax)
local VxClimb=math.min(self.aircraft.Vmax*0.90,200)
local VxDescent=math.min(self.aircraft.Vmax*0.60,140)
local VxHolding=VxDescent*0.9
local VxFinal=VxHolding*0.9
local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60,self.aircraft.Vymax)
local AlphaClimb=math.asin(VyClimb/VxClimb)
local AlphaDescent=math.rad(self.AlphaDescent)
local FLcruise_expect=self.FLcruise
local departure=nil
if _departure then
if self:_AirportExists(_departure)then
departure=AIRBASE:FindByName(_departure)
if takeoff==RAT.wp.air then
departure=departure:GetZone()
end
elseif self:_ZoneExists(_departure)then
departure=ZONE:FindByName(_departure)
else
local text=string.format("ERROR! Specified departure airport %s does not exist for %s.",_departure,self.alias)
self:E(self.lid..text)
end
else
departure=self:_PickDeparture(takeoff)
if self.commute and self.starshape==true and self.homebase==nil then
self.homebase=departure:GetName()
end
end
if not departure then
local text=string.format("ERROR! No valid departure airport could be found for %s.",self.alias)
self:E(self.lid..text)
return nil
end
local Pdeparture
if takeoff==RAT.wp.air then
if _waypoint then
Pdeparture=COORDINATE:New(_waypoint.x,_waypoint.alt,_waypoint.y)
else
local vec2=departure:GetRandomVec2()
Pdeparture=COORDINATE:NewFromVec2(vec2)
end
else
Pdeparture=departure:GetCoordinate()
end
local H_departure
if takeoff==RAT.wp.air then
local Hmin
if self.category==RAT.cat.plane then
Hmin=1000
else
Hmin=50
end
H_departure=self:_Randomize(FLcruise_expect*0.7,0.3,Pdeparture.y+Hmin,FLcruise_expect)
if self.FLminuser then
H_departure=math.max(H_departure,self.FLminuser)
end
if _waypoint then
H_departure=_waypoint.alt
end
else
H_departure=Pdeparture.y
end
local mindist=self.mindist
if self.FLminuser then
local hclimb=self.FLminuser-H_departure
local hdescent=self.FLminuser-H_departure
local Dclimb,Ddescent,Dtot=self:_MinDistance(AlphaClimb,AlphaDescent,hclimb,hdescent)
if takeoff==RAT.wp.air and landing==RAT.wpair then
mindist=0
elseif takeoff==RAT.wp.air then
mindist=Ddescent
elseif landing==RAT.wp.air then
mindist=Dclimb
else
mindist=Dtot
end
mindist=math.max(self.mindist,mindist)
local text=string.format("Adjusting min distance to %d km (for given min FL%03d)",mindist/1000,self.FLminuser/RAT.unit.FL2m)
self:T(self.lid..text)
end
local destination=nil
if _destination then
if self:_AirportExists(_destination)then
destination=AIRBASE:FindByName(_destination)
if landing==RAT.wp.air or self.returnzone then
destination=destination:GetZone()
end
elseif self:_ZoneExists(_destination)then
destination=ZONE:FindByName(_destination)
else
local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!",_destination,self.alias)
self:E(self.lid.."ERROR: "..text)
end
else
local random=self.random_destination
if self.continuejourney and _departure and#self.destination_ports<3 then
random=true
end
local mylanding=landing
local acrange=self.aircraft.Reff
if self.returnzone then
mylanding=RAT.wp.air
acrange=self.aircraft.Reff/2
end
destination=self:_PickDestination(departure,Pdeparture,mindist,math.min(acrange,self.maxdist),random,mylanding)
end
if not destination then
local text=string.format("No valid destination airport could be found for %s!",self.alias)
MESSAGE:New(text,60):ToAll()
self:E(self.lid.."ERROR: "..text)
return nil
end
if destination:GetName()==departure:GetName()then
local text=string.format("%s: Destination and departure are identical. Airport/zone %s.",self.alias,destination:GetName())
MESSAGE:New(text,30):ToAll()
self:E(self.lid.."ERROR: "..text)
end
local Preturn
local destination_returnzone
if self.returnzone then
local vec2=destination:GetRandomVec2()
Preturn=COORDINATE:NewFromVec2(vec2)
destination_returnzone=destination
destination=departure
end
local Pdestination
if landing==RAT.wp.air then
local vec2=destination:GetRandomVec2()
Pdestination=COORDINATE:NewFromVec2(vec2)
else
Pdestination=destination:GetCoordinate()
end
local H_destination=Pdestination.y
local Rhmin=8000
local Rhmax=20000
if self.category==RAT.cat.heli then
Rhmin=500
Rhmax=1000
end
local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax,Rhmin)
local Pholding=COORDINATE:NewFromVec2(Vholding)
local H_holding=Pholding.y
local h_holding
if self.category==RAT.cat.plane then
h_holding=1200
else
h_holding=150
end
h_holding=self:_Randomize(h_holding,0.2)
local Hh_holding=H_holding+h_holding
if landing==RAT.wp.air then
Hh_holding=H_departure
end
local d_holding=Pholding:Get2DDistance(Pdestination)
local heading
local d_total
if self.returnzone then
heading=self:_Course(Pdeparture,Preturn)
d_total=Pdeparture:Get2DDistance(Preturn)+Preturn:Get2DDistance(Pholding)
else
heading=self:_Course(Pdeparture,Pholding)
d_total=Pdeparture:Get2DDistance(Pholding)
end
if takeoff==RAT.wp.air then
local H_departure_max
if landing==RAT.wp.air then
H_departure_max=H_departure
else
H_departure_max=d_total*math.tan(AlphaDescent)+Hh_holding
end
H_departure=math.min(H_departure,H_departure_max)
end
local deltaH=math.abs(H_departure-Hh_holding)
local phi=math.atan(deltaH/d_total)
local phi_climb
local phi_descent
if(H_departure>Hh_holding)then
phi_climb=AlphaClimb+phi
phi_descent=AlphaDescent-phi
else
phi_climb=AlphaClimb-phi
phi_descent=AlphaDescent+phi
end
local D_total
if self.returnzone then
D_total=math.sqrt(deltaH*deltaH+d_total/2*d_total/2)
else
D_total=math.sqrt(deltaH*deltaH+d_total*d_total)
end
local gamma=math.rad(180)-phi_climb-phi_descent
local a=D_total*math.sin(phi_climb)/math.sin(gamma)
local b=D_total*math.sin(phi_descent)/math.sin(gamma)
local hphi_max=b*math.sin(phi_climb)
local hphi_max2=a*math.sin(phi_descent)
local h_max1=b*math.sin(AlphaClimb)
local h_max2=a*math.sin(AlphaDescent)
local h_max
if(H_departure>Hh_holding)then
h_max=math.min(h_max1,h_max2)
else
h_max=math.max(h_max1,h_max2)
end
local FLmax=h_max+H_departure
local FLmin=math.max(H_departure,Hh_holding)
if self.category==RAT.cat.heli then
FLmin=math.max(H_departure,H_destination)+50
FLmax=math.max(H_departure,H_destination)+1000
end
FLmax=math.min(FLmax,self.aircraft.ceiling)
if self.FLminuser then
FLmin=math.max(self.FLminuser,FLmin)
end
if self.FLmaxuser then
FLmax=math.min(self.FLmaxuser,FLmax)
end
if FLmin>FLmax then
FLmin=FLmax
end
if FLcruise_expect<FLmin then
FLcruise_expect=FLmin
end
if FLcruise_expect>FLmax then
FLcruise_expect=FLmax
end
local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax)
if self.FLuser then
FLcruise=self.FLuser
FLcruise=math.max(FLcruise,FLmin)
FLcruise=math.min(FLcruise,FLmax)
end
local h_climb=FLcruise-H_departure
local h_descent=FLcruise-Hh_holding
local d_climb=h_climb/math.tan(AlphaClimb)
local d_descent=h_descent/math.tan(AlphaDescent)
local d_cruise=d_total-d_climb-d_descent
local text=string.format("\n******************************************************\n")
text=text..string.format("Template      =  %s\n",self.SpawnTemplatePrefix)
text=text..string.format("Alias         =  %s\n",self.alias)
text=text..string.format("Group name    =  %s\n\n",self:_AnticipatedGroupName())
text=text..string.format("Speeds:\n")
text=text..string.format("VxCruiseMin   = %6.1f m/s = %5.1f km/h\n",VxCruiseMin,VxCruiseMin*3.6)
text=text..string.format("VxCruiseMax   = %6.1f m/s = %5.1f km/h\n",VxCruiseMax,VxCruiseMax*3.6)
text=text..string.format("VxCruise      = %6.1f m/s = %5.1f km/h\n",VxCruise,VxCruise*3.6)
text=text..string.format("VxClimb       = %6.1f m/s = %5.1f km/h\n",VxClimb,VxClimb*3.6)
text=text..string.format("VxDescent     = %6.1f m/s = %5.1f km/h\n",VxDescent,VxDescent*3.6)
text=text..string.format("VxHolding     = %6.1f m/s = %5.1f km/h\n",VxHolding,VxHolding*3.6)
text=text..string.format("VxFinal       = %6.1f m/s = %5.1f km/h\n",VxFinal,VxFinal*3.6)
text=text..string.format("VyClimb       = %6.1f m/s\n",VyClimb)
text=text..string.format("\nDistances:\n")
text=text..string.format("d_climb       = %6.1f km\n",d_climb/1000)
text=text..string.format("d_cruise      = %6.1f km\n",d_cruise/1000)
text=text..string.format("d_descent     = %6.1f km\n",d_descent/1000)
text=text..string.format("d_holding     = %6.1f km\n",d_holding/1000)
text=text..string.format("d_total       = %6.1f km\n",d_total/1000)
text=text..string.format("\nHeights:\n")
text=text..string.format("H_departure   = %6.1f m ASL\n",H_departure)
text=text..string.format("H_destination = %6.1f m ASL\n",H_destination)
text=text..string.format("H_holding     = %6.1f m ASL\n",H_holding)
text=text..string.format("h_climb       = %6.1f m\n",h_climb)
text=text..string.format("h_descent     = %6.1f m\n",h_descent)
text=text..string.format("h_holding     = %6.1f m\n",h_holding)
text=text..string.format("delta H       = %6.1f m\n",deltaH)
text=text..string.format("FLmin         = %6.1f m ASL = FL%03d\n",FLmin,FLmin/RAT.unit.FL2m)
text=text..string.format("FLcruise      = %6.1f m ASL = FL%03d\n",FLcruise,FLcruise/RAT.unit.FL2m)
text=text..string.format("FLmax         = %6.1f m ASL = FL%03d\n",FLmax,FLmax/RAT.unit.FL2m)
text=text..string.format("\nAngles:\n")
text=text..string.format("Alpha climb   = %6.2f Deg\n",math.deg(AlphaClimb))
text=text..string.format("Alpha descent = %6.2f Deg\n",math.deg(AlphaDescent))
text=text..string.format("Phi (slope)   = %6.2f Deg\n",math.deg(phi))
text=text..string.format("Phi climb     = %6.2f Deg\n",math.deg(phi_climb))
text=text..string.format("Phi descent   = %6.2f Deg\n",math.deg(phi_descent))
if self.Debug then
local h_climb_max=FLmax-H_departure
local h_descent_max=FLmax-Hh_holding
local d_climb_max=h_climb_max/math.tan(AlphaClimb)
local d_descent_max=h_descent_max/math.tan(AlphaDescent)
local d_cruise_max=d_total-d_climb_max-d_descent_max
text=text..string.format("Heading       = %6.1f Deg\n",heading)
text=text..string.format("\nSSA triangle:\n")
text=text..string.format("D_total       = %6.1f km\n",D_total/1000)
text=text..string.format("gamma         = %6.1f Deg\n",math.deg(gamma))
text=text..string.format("a             = %6.1f m\n",a)
text=text..string.format("b             = %6.1f m\n",b)
text=text..string.format("hphi_max      = %6.1f m\n",hphi_max)
text=text..string.format("hphi_max2     = %6.1f m\n",hphi_max2)
text=text..string.format("h_max1        = %6.1f m\n",h_max1)
text=text..string.format("h_max2        = %6.1f m\n",h_max2)
text=text..string.format("h_max         = %6.1f m\n",h_max)
text=text..string.format("\nMax heights and distances:\n")
text=text..string.format("d_climb_max   = %6.1f km\n",d_climb_max/1000)
text=text..string.format("d_cruise_max  = %6.1f km\n",d_cruise_max/1000)
text=text..string.format("d_descent_max = %6.1f km\n",d_descent_max/1000)
text=text..string.format("h_climb_max   = %6.1f m\n",h_climb_max)
text=text..string.format("h_descent_max = %6.1f m\n",h_descent_max)
end
text=text..string.format("******************************************************\n")
self:T2(self.lid..text)
if d_cruise<0 then
d_cruise=100
end
local wp={}
local c={}
local waypointdescriptions={}
local waypointstatus={}
local wpholding=nil
local wpfinal=nil
c[#c+1]=Pdeparture
wp[#wp+1]=self:_Waypoint(#wp+1,"Departure",takeoff,c[#wp+1],VxClimb,H_departure,departure)
waypointdescriptions[#wp]="Departure"
waypointstatus[#wp]=RAT.status.Departure
if takeoff==RAT.wp.air then
if d_climb<5000 or d_cruise<5000 then
d_cruise=d_cruise+d_climb
else
c[#c+1]=c[#c]:Translate(d_climb,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
waypointdescriptions[#wp]="Begin of Cruise"
waypointstatus[#wp]=RAT.status.Cruise
end
else
c[#c+1]=c[#c]:Translate(d_climb/2,heading)
c[#c+1]=c[#c]:Translate(d_climb/2,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"Climb",RAT.wp.climb,c[#wp+1],VxClimb,H_departure+(FLcruise-H_departure)/2)
waypointdescriptions[#wp]="Climb"
waypointstatus[#wp]=RAT.status.Climb
wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
waypointdescriptions[#wp]="Begin of Cruise"
waypointstatus[#wp]=RAT.status.Cruise
end
if self.returnzone then
c[#c+1]=Preturn
wp[#wp+1]=self:_Waypoint(#wp+1,"Return Zone",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
waypointdescriptions[#wp]="Return Zone"
waypointstatus[#wp]=RAT.status.Uturn
end
if landing==RAT.wp.air then
c[#c+1]=Pdestination
wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",RAT.wp.finalwp,c[#wp+1],VxCruise,FLcruise)
waypointdescriptions[#wp]="Final Destination"
waypointstatus[#wp]=RAT.status.Destination
elseif self.returnzone then
c[#c+1]=c[#c]:Translate(d_cruise/2,heading-180)
wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
waypointdescriptions[#wp]="End of Cruise"
waypointstatus[#wp]=RAT.status.Descent
else
c[#c+1]=c[#c]:Translate(d_cruise,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise)
waypointdescriptions[#wp]="End of Cruise"
waypointstatus[#wp]=RAT.status.Descent
end
if landing==RAT.wp.landing then
if self.returnzone then
c[#c+1]=c[#c]:Translate(d_descent/2,heading-180)
wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2)
waypointdescriptions[#wp]="Descent"
waypointstatus[#wp]=RAT.status.DescentHolding
else
c[#c+1]=c[#c]:Translate(d_descent/2,heading)
wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2)
waypointdescriptions[#wp]="Descent"
waypointstatus[#wp]=RAT.status.DescentHolding
end
end
if landing==RAT.wp.landing then
c[#c+1]=Pdestination
wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",landing,c[#wp+1],VxFinal,H_destination,destination)
waypointdescriptions[#wp]="Final Destination"
waypointstatus[#wp]=RAT.status.Destination
end
wpfinal=#wp
local waypoints={}
for _,p in ipairs(wp)do
table.insert(waypoints,p)
end
self:_Routeinfo(waypoints,"Waypoint info in set_route:",waypointdescriptions)
if self.returnzone then
return departure,destination_returnzone,waypoints,waypointdescriptions,waypointstatus
else
return departure,destination,waypoints,waypointdescriptions,waypointstatus
end
end
function RAT:_PickDeparture(takeoff)
local departures={}
if self.random_departure then
for _,_airport in pairs(self.airports)do
local airport=_airport
local name=airport:GetName()
if not self:_Excluded(name)then
if takeoff==RAT.wp.air then
table.insert(departures,airport:GetZone())
else
local nspots=1
if self.termtype~=nil then
nspots=airport:GetParkingSpotsNumber(self.termtype)
end
if nspots>0 then
table.insert(departures,airport)
end
end
end
end
else
for _,name in pairs(self.departure_ports)do
local dep=nil
if self:_AirportExists(name)then
if takeoff==RAT.wp.air then
dep=AIRBASE:FindByName(name):GetZone()
else
dep=AIRBASE:FindByName(name)
if self.termtype~=nil and dep~=nil then
local _dep=dep
local nspots=_dep:GetParkingSpotsNumber(self.termtype)
if nspots==0 then
dep=nil
end
end
end
elseif self:_ZoneExists(name)then
if takeoff==RAT.wp.air then
dep=ZONE:FindByName(name)
else
self:E(self.lid..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.",name))
end
else
self:E(self.lid..string.format("ERROR: No airport or zone found with name %s.",name))
end
if dep then
table.insert(departures,dep)
end
end
end
self:T(self.lid..string.format("Number of possible departures for %s= %d",self.alias,#departures))
local departure=departures[math.random(#departures)]
local text
if departure and departure:GetName()then
if takeoff==RAT.wp.air then
text=string.format("%s: Chosen departure zone: %s",self.alias,departure:GetName())
else
text=string.format("%s: Chosen departure airport: %s (ID %d)",self.alias,departure:GetName(),departure:GetID())
end
self:T(self.lid..text)
else
self:E(self.lid..string.format("ERROR! No departure airport or zone found for %s.",self.alias))
departure=nil
end
return departure
end
function RAT:_PickDestination(departure,q,minrange,maxrange,random,landing)
minrange=minrange or self.mindist
maxrange=maxrange or self.maxdist
local destinations={}
if random then
for _,_airport in pairs(self.airports)do
local airport=_airport
local name=airport:GetName()
if self:_IsFriendly(name)and not self:_Excluded(name)and name~=departure:GetName()then
local distance=q:Get2DDistance(airport:GetCoordinate())
if distance>=minrange and distance<=maxrange then
if landing==RAT.wp.air then
table.insert(destinations,airport:GetZone())
else
local nspot=1
if self.termtype then
nspot=airport:GetParkingSpotsNumber(self.termtype)
end
if nspot>0 then
table.insert(destinations,airport)
end
end
end
end
end
else
for _,name in pairs(self.destination_ports)do
if name~=departure:GetName()then
local dest=nil
if self:_AirportExists(name)then
if landing==RAT.wp.air then
dest=AIRBASE:FindByName(name):GetZone()
else
dest=AIRBASE:FindByName(name)
local nspot=1
if self.termtype then
nspot=dest:GetParkingSpotsNumber(self.termtype)
end
if nspot==0 then
dest=nil
end
end
elseif self:_ZoneExists(name)then
if landing==RAT.wp.air then
dest=ZONE:FindByName(name)
else
self:E(self.lid..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!",name))
end
else
self:E(self.lid..string.format("ERROR! No airport or zone found with name %s",name))
end
if dest then
local distance=q:Get2DDistance(dest:GetCoordinate())
if distance>=minrange and distance<=maxrange then
table.insert(destinations,dest)
else
local text=string.format("Destination %s is ouside range. Distance = %5.1f km, min = %5.1f km, max = %5.1f km.",name,distance,minrange,maxrange)
self:T(self.lid..text)
end
end
end
end
end
self:T(self.lid..string.format("Number of possible destinations = %s.",#destinations))
if#destinations>0 then
local function compare(a,b)
local qa=q:Get2DDistance(a:GetCoordinate())
local qb=q:Get2DDistance(b:GetCoordinate())
return qa<qb
end
table.sort(destinations,compare)
else
destinations=nil
end
local destination
if destinations and#destinations>0 then
destination=destinations[math.random(#destinations)]
local text
if landing==RAT.wp.air then
text=string.format("%s: Chosen destination zone: %s.",self.alias,destination:GetName())
else
text=string.format("%s Chosen destination airport: %s (ID %d).",self.alias,destination:GetName(),destination:GetID())
end
self:T(self.lid..text)
else
self:E(self.lid.."ERROR! No destination airport or zone found.")
destination=nil
end
return destination
end
function RAT:_GetAirportsInZone(zone)
local airports={}
for _,airport in pairs(self.airports)do
local name=airport:GetName()
local coord=airport:GetCoordinate()
if zone:IsPointVec3InZone(coord)then
table.insert(airports,name)
end
end
return airports
end
function RAT:_Excluded(port)
for _,name in pairs(self.excluded_ports)do
if name==port then
return true
end
end
return false
end
function RAT:_IsFriendly(port)
for _,airport in pairs(self.airports)do
local name=airport:GetName()
if name==port then
return true
end
end
return false
end
function RAT:_GetAirportsOfMap()
local _coalition
for i=0,2 do
if i==0 then
_coalition=coalition.side.NEUTRAL
elseif i==1 then
_coalition=coalition.side.RED
elseif i==2 then
_coalition=coalition.side.BLUE
end
local ab=coalition.getAirbases(i)
for _,airbase in pairs(ab)do
local _id=airbase:getID()
local _p=airbase:getPosition().p
local _name=airbase:getName()
local _myab=AIRBASE:FindByName(_name)
if _myab then
table.insert(self.airports_map,_myab)
local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
self:T(self.lid..text)
else
self:E(self.lid..string.format("WARNING: Airbase %s does not exsist as MOOSE object!",tostring(_name)))
end
end
end
end
function RAT:_GetAirportsOfCoalition()
for _,coalition in pairs(self.ctable)do
for _,_airport in pairs(self.airports_map)do
local airport=_airport
local category=airport:GetAirbaseCategory()
if airport:GetCoalition()==coalition then
local condition1=self.category==RAT.cat.plane and category==Airbase.Category.HELIPAD
local condition2=self.category==RAT.cat.plane and category==Airbase.Category.SHIP
if not(condition1 or condition2)then
table.insert(self.airports,airport)
end
end
end
end
if#self.airports==0 then
local text=string.format("No possible departure/destination airports found for RAT %s.",tostring(self.alias))
MESSAGE:New(text,10):ToAll()
self:E(self.lid..text)
end
end
function RAT:Status(message,forID)
self:T(self.lid.."Checking status")
local Tnow=timer.getTime()
local nalive=0
for spawnindex,_ratcraft in pairs(self.ratcraft)do
local ratcraft=_ratcraft
self:T(self.lid..string.format("Ratcraft Index=%s",tostring(spawnindex)))
local group=ratcraft.group
if group and group:IsAlive()then
nalive=nalive+1
self:T(self.lid..string.format("Ratcraft Index=%s is ALIVE",tostring(spawnindex)))
local prefix=self:_GetPrefixFromGroup(group)
local life=self:_GetLife(group)
local fuel=group:GetFuel()*100.0
local airborne=group:InAir()
local coords=group:GetCoordinate()
local alt=coords~=nil and coords.y or 1000
local departure=ratcraft.departure:GetName()
local destination=ratcraft.destination:GetName()
local type=self.aircraft.type
local status=ratcraft.status
local active=ratcraft.active
local Nunits=ratcraft.nunits
local N0units=group:GetInitialSize()
local 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=-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
text=text..string.format(" (%d/%d)\n",Nunits,N0units)
else
text=text.."\n"
end
if self.commute then
text=text..string.format("%s commuting between %s and %s\n",type,departure,destination)
elseif self.continuejourney then
text=text..string.format("%s travelling from %s to %s (and continueing form there)\n",type,departure,destination)
else
text=text..string.format("%s travelling from %s to %s\n",type,departure,destination)
end
text=text..string.format("Status: %s",status)
if airborne then
text=text.." [airborne]\n"
else
text=text.." [on ground]\n"
end
text=text..string.format("Fuel    = %3.0f %%\n",fuel)
text=text..string.format("Life    = %3.0f %%\n",life)
text=text..string.format("FL%03d  = %i m ASL\n",alt/RAT.unit.FL2m,alt)
text=text..string.format("Distance travelled = %6.1f km\n",ratcraft["Distance"]/1000)
text=text..string.format("Distance to dest   = %6.1f km",Ddestination/1000)
self:T(self.lid..text)
if message then
MESSAGE:New(text,20):ToAll()
end
end
if ratcraft.despawnme then
if self.norespawn or self.respawn_after_takeoff then
if self.despawnair then
self:T(self.lid..string.format("[STATUS despawnme] Flight %s will be despawned NOW and NO new group is created!",self.alias))
self:_Despawn(group)
end
else
self:T(self.lid..string.format("[STATUS despawnme] Flight %s will be despawned NOW and a new group is respawned!",self.alias))
self:_Respawn(group)
end
end
else
local text=string.format("Group does not exist in loop ratcraft status for spawn index=%d",spawnindex)
self:T2(self.lid..text)
self:T2(ratcraft)
end
end
local text=string.format("Alive groups of %s: %d, nalive=%d/%d",self.alias,self.alive,nalive,self.ngroups)
self:T(self.lid..text)
MESSAGE:New(text,20):ToAllIf(message and not forID)
end
function RAT:_RemoveRatcraft(ratcraft)
self.ratcraft[ratcraft.index]=nil
return self
end
function RAT:_GetRatcraftFromGroup(group)
local index=self:GetSpawnIndexFromGroup(group)
local ratcraft=self.ratcraft[index]
return ratcraft
end
function RAT:_GetLife(group)
local life=0.0
if group and group:IsAlive()then
local unit=group:GetUnit(1)
if unit then
life=unit:GetLife()/unit:GetLife0()*100
else
self:T2(self.lid.."ERROR! Unit does not exist in RAT_Getlife(). Returning zero.")
end
else
self:T2(self.lid.."ERROR! Group does not exist in RAT_Getlife(). Returning zero.")
end
return life
end
function RAT:_SetStatus(group,status)
if group and group:IsAlive()then
local ratcraft=self:_GetRatcraftFromGroup(group)
if ratcraft then
ratcraft.status=status
local no1=status==RAT.status.Departure
local no2=status==RAT.status.EventBirthAir
local no3=status==RAT.status.Holding
local text=string.format("Flight %s: %s",group:GetName(),status)
self:T(self.lid..text)
if not(no1 or no2 or no3)then
MESSAGE:New(text,10):ToAllIf(self.reportstatus)
end
end
end
end
function RAT:_GetRatcraftFromGroup(group)
if group then
local index=self:GetSpawnIndexFromGroup(group)
if self.ratcraft[index]then
return self.ratcraft[index]
end
end
return nil
end
function RAT:GetStatus(group)
if group and group:IsAlive()then
local ratcraft=self:_GetRatcraftFromGroup(group)
if ratcraft then
return ratcraft.status
end
end
return"nonexistant"
end
function RAT:_OnBirth(EventData)
self:F3(EventData)
self:T3(self.lid.."Captured event birth!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." was born."
self:T(self.lid..text)
local status="unknown in birth"
if SpawnGroup:InAir()then
status=RAT.status.EventBirthAir
elseif self.uncontrolled then
status=RAT.status.Uncontrolled
else
status=RAT.status.EventBirth
end
self:_SetStatus(SpawnGroup,status)
local i=self:GetSpawnIndexFromGroup(SpawnGroup)
local ratcraft=self.ratcraft[i]
local _departure=ratcraft.departure:GetName()
local _destination=ratcraft.destination:GetName()
local _nrespawn=ratcraft.nrespawn
local _takeoff=ratcraft.takeoff
local _landing=ratcraft.landing
local _livery=ratcraft.livery
local _airbase=AIRBASE:FindByName(_departure)
local onrunway=false
if _airbase then
if self.checkonrunway and _takeoff~=RAT.wp.runway and _takeoff~=RAT.wp.air then
onrunway=_airbase:CheckOnRunWay(SpawnGroup,self.onrunwayradius,false)
end
end
if onrunway then
local text=string.format("ERROR: RAT group of %s was spawned on runway. Group #%d will be despawned immediately!",self.alias,i)
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:E(self.lid..text)
if self.Debug then
SpawnGroup:FlareRed()
end
self:_Despawn(SpawnGroup)
if(self.Ndeparture_Airports>=2 or self.random_departure)and _nrespawn<self.onrunwaymaxretry then
_nrespawn=_nrespawn+1
text=string.format("Try spawning new aircraft of group %s at another location. Attempt %d of max %d.",self.alias,_nrespawn,self.onrunwaymaxretry)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
self:_SpawnWithRoute(nil,nil,nil,nil,nil,nil,nil,_nrespawn)
else
if self.respawn_inair and not self.uncontrolled then
text=string.format("Spawning new aircraft of group %s in air since no parking slot is available at %s.",self.alias,_departure)
MESSAGE:New(text,10):ToAll()
self:T(self.lid..text)
self:_SpawnWithRoute(_departure,_destination,RAT.wp.air,_landing,_livery)
end
end
end
local ontop=false
if self.checkontop and(_airbase and _airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME)then
ontop=self:_CheckOnTop(SpawnGroup,self.ontopradius)
end
if ontop then
local text=string.format("ERROR: Group of %s was spawned on top of another unit. Group #%d will be despawned immediately!",self.alias,i)
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:T(self.lid..text)
if self.Debug then
SpawnGroup:FlareYellow()
end
self:_Despawn(SpawnGroup)
end
end
else
self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnBirth().")
end
end
function RAT:_OnEngineStartup(EventData)
self:F3(EventData)
self:T3(self.lid.."Captured event EngineStartup!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." started engines."
self:T(self.lid..text)
local status
if SpawnGroup:InAir()then
status=RAT.status.EventEngineStartAir
else
status=RAT.status.EventEngineStart
end
self:_SetStatus(SpawnGroup,status)
end
else
self:T2(self.lid.."ERROR: Group does not exist in RAT:_EngineStartup().")
end
end
function RAT:_OnTakeoff(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." is airborne."
self:T(self.lid..text)
local status=RAT.status.EventTakeoff
self:_SetStatus(SpawnGroup,status)
if self.respawn_after_takeoff then
text="Event: Group "..SpawnGroup:GetName().." will be respawned after takeoff."
self:T(self.lid..text)
self:_SpawnWithRoute(nil,nil,nil,nil,nil,nil,nil,nil)
end
end
else
self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnTakeoff().")
end
end
function RAT:_OnLand(EventData)
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
local text="Event: Group "..SpawnGroup:GetName().." landed"
self:T(self.lid..text)
local status=RAT.status.EventLand
self:_SetStatus(SpawnGroup,status)
if self.ATCswitch then
RAT._ATCFlightLanded(SpawnGroup:GetName())
end
if self.respawn_at_landing and not self.norespawn then
text="Event: Group "..SpawnGroup:GetName().." will be respawned"
self:T(self.lid..text)
self:_Respawn(SpawnGroup)
end
end
else
self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnLand()")
end
end
function RAT:_OnEngineShutdown(EventData)
self:F3(EventData)
self:T3(self.lid.."Captured event EngineShutdown!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
if not SpawnGroup:InAir()then
local currentstate=self:GetStatus(SpawnGroup)
local text=string.format("Event: Unit %s of group %s shut down its engines. Current state %s.",EventData.IniUnitName,SpawnGroup:GetName(),currentstate)
self:T(self.lid..text)
if currentstate~=RAT.status.EventEngineShutdown and currentstate~="Dead"then
local status=RAT.status.EventEngineShutdown
self:_SetStatus(SpawnGroup,status)
if not self.respawn_at_landing and not self.norespawn then
text=string.format("Event: Group %s will be respawned. Current state %s => new state %s.",SpawnGroup:GetName(),currentstate,status)
self:T(self.lid..text)
self:_Respawn(SpawnGroup,nil,3)
else
text="Event: Group "..SpawnGroup:GetName().." will be destroyed now"
self:T(self.lid..text)
self:_Despawn(SpawnGroup)
end
end
end
end
else
self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnEngineShutdown().")
end
end
function RAT:_OnHit(EventData)
self:F3(EventData)
self:T(self.lid..string.format("Captured event Hit by %s! Initiator %s. Target %s",self.alias,tostring(EventData.IniUnitName),tostring(EventData.TgtUnitName)))
local SpawnGroup=EventData.TgtGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
self:T(self.lid..string.format("Event: Group %s was hit. Unit %s.",SpawnGroup:GetName(),tostring(EventData.TgtUnitName)))
local text=string.format("%s, unit %s was hit!",self.alias,EventData.TgtUnitName)
MESSAGE:New(text,10):ToAllIf(self.reportstatus or self.Debug)
end
end
end
function RAT:_OnDeadOrCrash(EventData)
self:F3(EventData)
self:T3(self.lid.."Captured event DeadOrCrash!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
self.alive=self.alive-1
local text=string.format("Event: Group %s crashed or died. Alive counter = %d.",SpawnGroup:GetName(),self.alive)
self:T(self.lid..text)
if EventData.id==world.event.S_EVENT_CRASH then
self:_OnCrash(EventData)
elseif EventData.id==world.event.S_EVENT_DEAD then
self:_OnDead(EventData)
end
end
end
end
end
function RAT:_OnDead(EventData)
self:F3(EventData)
self:T3(self.lid.."Captured event Dead!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix then
if EventPrefix==self.alias then
local text=string.format("Event: Group %s died. Unit %s.",SpawnGroup:GetName(),EventData.IniUnitName)
self:T(self.lid..text)
local status=RAT.status.EventDead
self:_SetStatus(SpawnGroup,status)
end
end
else
self:T2(self.lid.."ERROR: Group does not exist in RAT:_OnDead().")
end
end
function RAT:_OnCrash(EventData)
self:F3(EventData)
self:T3(self.lid.."Captured event Crash!")
local SpawnGroup=EventData.IniGroup
if SpawnGroup then
local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup)
if EventPrefix and EventPrefix==self.alias then
local ratcraft=self:_GetRatcraftFromGroup(SpawnGroup)
if ratcraft then
ratcraft.nunits=ratcraft.nunits-1
local _n0=SpawnGroup:GetInitialSize()
local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.",SpawnGroup:GetName(),EventData.IniUnitName,ratcraft.nunits,_n0)
self:T(self.lid..text)
local status=RAT.status.EventCrash
self:_SetStatus(SpawnGroup,status)
if ratcraft.nunits==0 and self.respawn_after_crash and not self.norespawn then
self:T(self.lid..string.format("No units left of group %s. Group will be respawned now.",SpawnGroup:GetName()))
self:_Respawn(SpawnGroup)
end
else
self:E(self.lid..string.format("ERROR: Could not find ratcraft object for crashed group %s!",SpawnGroup:GetName()))
end
end
else
if self.Debug then
self:E(self.lid.."ERROR: Group does not exist in RAT:_OnCrash()!")
end
end
end
function RAT:_Waypoint(index,description,Type,Coord,Speed,Altitude,Airport)
local _Altitude=Altitude or Coord.y
local Hland=Coord:GetLandHeight()
local _Type=nil
local _Action=nil
local _alttype="RADIO"
if Type==RAT.wp.cold then
_Type="TakeOffParking"
_Action="From Parking Area"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.hot then
_Type="TakeOffParkingHot"
_Action="From Parking Area Hot"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.runway then
_Type="TakeOff"
_Action="From Parking Area"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.air then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.climb then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.cruise then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.descent then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.holding then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
elseif Type==RAT.wp.landing then
_Type="Land"
_Action="Landing"
_Altitude=10
_alttype="RADIO"
elseif Type==RAT.wp.finalwp then
_Type="Turning Point"
_Action="Turning Point"
_alttype="BARO"
else
self:E(self.lid.."ERROR: Unknown waypoint type in RAT:Waypoint() function!")
_Type="Turning Point"
_Action="Turning Point"
_alttype="RADIO"
end
local text=string.format("\n******************************************************\n")
text=text..string.format("Waypoint =  %d\n",index)
text=text..string.format("Template =  %s\n",self.SpawnTemplatePrefix)
text=text..string.format("Alias    =  %s\n",self.alias)
text=text..string.format("Type: %i - %s\n",Type,_Type)
text=text..string.format("Action: %s\n",_Action)
text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n",Coord.x/1000,Coord.z/1000,Coord.y)
text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n",Speed,Speed*3.6,Speed*1.94384)
text=text..string.format("Land     = %6.1f m ASL\n",Hland)
text=text..string.format("Altitude = %6.1f m (%s)\n",_Altitude,_alttype)
if Airport then
if Type==RAT.wp.air then
text=text..string.format("Zone = %s\n",Airport:GetName())
else
text=text..string.format("Airport = %s\n",Airport:GetName())
end
else
text=text..string.format("No airport/zone specified\n")
end
text=text.."******************************************************\n"
self:T2(self.lid..text)
local RoutePoint={}
RoutePoint.x=Coord.x
RoutePoint.y=Coord.z
RoutePoint.alt=_Altitude
RoutePoint.alt_type=_alttype
RoutePoint.type=_Type
RoutePoint.action=_Action
RoutePoint.speed=Speed
RoutePoint.speed_locked=true
RoutePoint.ETA=nil
RoutePoint.ETA_locked=false
RoutePoint.name=description
if(Airport~=nil)and(Type~=RAT.wp.air)then
local AirbaseID=Airport:GetID()
local AirbaseCategory=Airport:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.SHIP then
RoutePoint.linkUnit=AirbaseID
RoutePoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.HELIPAD then
RoutePoint.linkUnit=AirbaseID
RoutePoint.helipadId=AirbaseID
elseif AirbaseCategory==Airbase.Category.AIRDROME then
RoutePoint.airdromeId=AirbaseID
else
self:T(self.lid.."Unknown Airport category in _Waypoint()!")
end
end
return RoutePoint
end
function RAT:_Routeinfo(waypoints,comment,waypointdescriptions)
local text=string.format("\n******************************************************\n")
text=text..string.format("Template =  %s\n",self.SpawnTemplatePrefix)
if comment then
text=text..comment.."\n"
end
text=text..string.format("Number of waypoints = %i\n",#waypoints)
for i=1,#waypoints do
local p=waypoints[i]
text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m  %s\n",i-1,p.x/1000,p.y/1000,p.alt,waypointdescriptions[i])
end
local total=0.0
for i=1,#waypoints-1 do
local point1=waypoints[i]
local point2=waypoints[i+1]
local x1=point1.x
local y1=point1.y
local x2=point2.x
local y2=point2.y
local d=math.sqrt((x1-x2)^2+(y1-y2)^2)
local heading=self:_Course(point1,point2)
total=total+d
text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d :  %s - %s\n",i-1,i,d/1000,heading,waypointdescriptions[i],waypointdescriptions[i+1])
end
text=text..string.format("Total distance = %6.1f km\n",total/1000)
text=text..string.format("******************************************************\n")
self:T2(self.lid..text)
return total
end
function RAT:_AnticipatedGroupName(index)
local index=index or self.SpawnIndex+1
return string.format("%s#%03d",self.alias,index)
end
function RAT:_ActivateUncontrolled()
local idx={}
local rat={}
local nactive=0
for spawnindex,_ratcraft in pairs(self.ratcraft)do
local ratcraft=_ratcraft
local group=ratcraft.group
if group and group:IsAlive()then
local text=string.format("Uncontrolled: Group = %s (spawnindex = %d), active = %s",ratcraft.group:GetName(),spawnindex,tostring(ratcraft.active))
self:T2(self.lid..text)
if ratcraft.active then
nactive=nactive+1
else
table.insert(idx,spawnindex)
end
end
end
local text=string.format("Uncontrolled: Ninactive = %d,  Nactive = %d (of max %d)",#idx,nactive,self.activate_max)
self:T(self.lid..text)
if#idx>0 and nactive<self.activate_max then
local index=idx[math.random(#idx)]
local ratcraft=self.ratcraft[index]
local group=ratcraft.group
local departureAirbase=self:_GetDepartureAirbase(ratcraft)
local departureFlightControl=nil
if departureAirbase then
departureFlightControl=_DATABASE:GetFlightControl(departureAirbase:GetName())
end
if departureFlightControl then
self:T(self.lid..string.format("RAT group %s is ready for takeoff",group:GetName()))
ratcraft.flightgroup:SetReadyForTakeoff(true)
else
self:T(self.lid..string.format("RAT group %s is switching to controlled now",group:GetName()))
ratcraft.flightgroup:StartUncontrolled()
end
ratcraft.active=true
self:_SetStatus(group,RAT.status.EventBirth)
end
end
function RAT:_CommandInvisible(group,switch)
local SetInvisible={id='SetInvisible',params={value=switch}}
group:SetCommand(SetInvisible)
end
function RAT:_CommandImmortal(group,switch)
local SetImmortal={id='SetImmortal',params={value=switch}}
group:SetCommand(SetImmortal)
end
function RAT:_GetObjectSize(unit)
local DCSunit=unit:GetDCSObject()
if DCSunit then
local DCSdesc=DCSunit:getDesc()
local length=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x)
local height=DCSdesc.box.max.y+math.abs(DCSdesc.box.max.y)
local width=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z)
return math.max(length,width)
end
return nil
end
function RAT:_CheckOnTop(group,distmin)
distmin=distmin or 2
for i,uniti in pairs(group:GetUnits())do
local uniti=uniti
if uniti then
local namei=uniti:GetName()
for j,unitj in pairs(_DATABASE.UNITS)do
if unitj then
local unitj=unitj
local namej=unitj:GetName()
if namei~=namej then
local DCSuniti=uniti:GetDCSObject()
local DCSunitj=unitj:GetDCSObject()
if DCSuniti and DCSuniti:isExist()and DCSunitj and DCSunitj:isExist()then
local _dist=uniti:GetCoordinate():Get3DDistance(unitj:GetCoordinate())
if _dist<distmin then
if(not uniti:InAir())and(not unitj:InAir())then
self:T(self.lid..string.format("Unit %s is on top of unit %s. Distance %.2f m.",namei,namej,_dist))
return true
end
end
end
end
end
end
end
end
return false
end
function RAT:_MinDistance(alpha,beta,ha,hb)
local d1=ha/math.tan(alpha)
local d2=hb/math.tan(beta)
return d1,d2,d1+d2
end
function RAT:_AddFriendlyAirports(ports)
for _,airport in pairs(self.airports)do
if not self:_NameInList(ports,airport:GetName())then
table.insert(ports,airport:GetName())
end
end
end
function RAT:_NameInList(liste,name)
for _,item in pairs(liste)do
if item==name then
return true
end
end
return false
end
function RAT:_GetDestinationAirbase(ratcraft)
if ratcraft and ratcraft.destination then
local airbase=AIRBASE:FindByName(ratcraft.destination)
return airbase
end
return nil
end
function RAT:_GetDepartureAirbase(ratcraft)
if ratcraft and ratcraft.departure then
local name=ratcraft.departure:GetName()
local airbase=AIRBASE:FindByName(name)
return airbase
end
return nil
end
function RAT:_AirportExists(name)
for _,airport in pairs(self.airports_map)do
if airport:GetName()==name then
return true
end
end
return false
end
function RAT:_ZoneExists(name)
local z=ZONE:FindByName(name)
if z then
return true
end
return false
end
function RAT:_SetROE(flightgroup,roe)
roe=roe or self.roe
self:T(self.lid.."Setting ROE to "..roe.." for group "..flightgroup:GetName())
local _roe=ENUMS.ROE.WeaponHold
if roe==RAT.ROE.returnfire then
_roe=ENUMS.ROE.ReturnFire
elseif roe==RAT.ROE.weaponfree then
_roe=ENUMS.ROE.OpenFireWeaponFree
else
end
flightgroup:SetDefaultROE(_roe)
flightgroup:SwitchROE(_roe)
end
function RAT:_SetROT(flightgroup,rot)
rot=rot or self.rot
self:T(self.lid.."Setting ROT to "..rot.." for group "..flightgroup:GetName())
local _rot=ENUMS.ROT.NoReaction
if rot==RAT.ROT.passive then
_rot=ENUMS.ROT.PassiveDefense
elseif rot==RAT.ROT.evade then
_rot=ENUMS.ROT.EvadeFire
else
end
flightgroup:SetDefaultROT(_rot)
flightgroup:SwitchROT(_rot)
end
function RAT:_SetCoalitionTable()
if self.friendly==RAT.coal.neutral then
self.ctable={coalition.side.NEUTRAL}
elseif self.friendly==RAT.coal.same then
self.ctable={self.coalition,coalition.side.NEUTRAL}
elseif self.friendly==RAT.coal.sameonly then
self.ctable={self.coalition}
else
self:E(self.lid.."ERROR: Unknown friendly coalition in _SetCoalitionTable(). Defaulting to NEUTRAL.")
self.ctable={self.coalition,coalition.side.NEUTRAL}
end
end
function RAT:_Course(a,b)
local dx=b.x-a.x
local ay
if a.alt then
ay=a.y
else
ay=a.z
end
local by
if b.alt then
by=b.y
else
by=b.z
end
local dy=by-ay
local angle=math.deg(math.atan2(dy,dx))
if angle<0 then
angle=360+angle
end
return angle
end
function RAT:_Heading(course)
local h
if course<=180 then
h=math.rad(course)
else
h=-math.rad(360-course)
end
return h
end
function RAT:_Randomize(value,fac,lower,upper)
local min
if lower then
min=math.max(value-value*fac,lower)
else
min=value-value*fac
end
local max
if upper then
max=math.min(value+value*fac,upper)
else
max=value+value*fac
end
local r=math.random(min,max)
if self.Debug then
local text=string.format("Random: value = %6.2f, fac = %4.2f, min = %6.2f, max = %6.2f, r = %6.2f",value,fac,min,max,r)
self:T3(self.lid..text)
end
return r
end
function RAT:_PlaceMarkers(waypoints,waypointdescriptions,index)
for i=1,#waypoints do
self:_SetMarker(waypointdescriptions[i],waypoints[i],index)
if self.Debug then
local text=string.format("Marker at waypoint #%d: %s for flight #%d",i,waypointdescriptions[i],index)
self:T2(self.lid..text)
end
end
end
function RAT:_SetMarker(text,wp,index)
RAT.markerid=RAT.markerid+1
self.markerids[#self.markerids+1]=RAT.markerid
if self.Debug then
local text2=string.format("%s: placing marker with ID %d and text %s",self.alias,RAT.markerid,text)
self:T2(self.lid..text2)
end
local vec={x=wp.x,y=wp.alt,z=wp.y}
local flight=self:GetGroupFromIndex(index):GetName()
local text1=string.format("%s:\n%s",flight,text)
trigger.action.markToAll(RAT.markerid,text1,vec,false,"")
end
function RAT:_DeleteMarkers()
for k,v in ipairs(self.markerids)do
trigger.action.removeMark(v)
end
for k,v in ipairs(self.markerids)do
self.markerids[k]=nil
end
end
function RAT:_ModifySpawnTemplate(waypoints,livery,spawnplace,departure,takeoff,parkingdata,uncontrolled)
self:F2({waypoints=waypoints,livery=livery,spawnplace=spawnplace,departure=departure,takeoff=takeoff,parking=parkingdata})
local PointVec3=COORDINATE:New(waypoints[1].x,waypoints[1].alt,waypoints[1].y)
if spawnplace then
PointVec3=COORDINATE:NewFromCoordinate(spawnplace)
end
local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix)
local TemplateUnit=TemplateGroup:GetUnit(1)
local spawnonground=takeoff==RAT.wp.cold or takeoff==RAT.wp.hot or takeoff==RAT.wp.runway
local spawnonship=false
local spawnonfarp=false
local spawnonrunway=false
local spawnonairport=false
if spawnonground then
local AirbaseCategory=departure:GetAirbaseCategory()
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==RAT.wp.runway
end
local automatic=false
if automatic and spawnonground then
PointVec3=PointVec3:GetClosestParkingSpot(true,departure)
end
local course=self:_Course(waypoints[1],waypoints[2])
local heading=self:_Heading(course)
if self:_GetSpawnIndex(self.SpawnIndex+1)then
local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate
if SpawnTemplate then
self:T(SpawnTemplate)
if self.uncontrolled or uncontrolled then
SpawnTemplate.uncontrolled=true
end
local nunits=#SpawnTemplate.units
local parkingspots={}
local parkingindex={}
local spots
if spawnonground then
local nfree=0
local termtype=self.termtype
if spawnonrunway then
termtype=AIRBASE.TerminalType.Runway
end
local scanradius=self.parkingscanradius
local scanunits=true
local scanstatics=true
local scanscenery=self.parkingscanscenery
local verysafe=self.parkingverysafe
if spawnonship or spawnonfarp or spawnonrunway then
self:T(self.lid..string.format("Group %s is spawned on farp/ship/runway %s.",self.alias,departure:GetName()))
nfree=departure:GetFreeParkingSpotsNumber(termtype,true)
spots=departure:GetFreeParkingSpotsTable(termtype,true)
self:T(self.lid..string.format("Free nfree=%d nspots=%d",nfree,#spots))
elseif parkingdata~=nil then
self:T2("Spawning with explicit parking data")
nfree=#parkingdata
spots=parkingdata
else
if self.category==RAT.cat.heli then
if termtype==nil then
self:T(self.lid..string.format("Helo group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.HelicopterOnly))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
if nfree<nunits then
self:T(self.lid..string.format("Helo group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.HelicopterUsable))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterUsable,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
self:T(self.lid..string.format("Helo group %s is at %s using terminal type %d.",self.alias,departure:GetName(),termtype))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
if termtype==nil then
local bomber=TemplateUnit:HasAttribute("Bombers")
local transport=TemplateUnit:HasAttribute("Transports")
if bomber or transport then
self:T(self.lid..string.format("Transport/bomber group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.OpenBig))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenBig,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
if nfree<nunits then
self:T(self.lid..string.format("Transport/bomber group %s is at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.OpenMed))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.OpenMed,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
self:T(self.lid..string.format("Fighter group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),AIRBASE.TerminalType.FighterAircraft))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.FighterAircraft,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
else
self:T(self.lid..string.format("Plane group %s is spawned at %s using terminal type %d.",self.alias,departure:GetName(),termtype))
spots=departure:FindFreeParkingSpotForAircraft(TemplateGroup,termtype,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits)
nfree=#spots
end
end
end
if false then
local parkingdata=departure:GetParkingSpotsTable(termtype)
self:T2(self.lid..string.format("Parking at %s, terminal type %s:",departure:GetName(),tostring(termtype)))
for _,_spot in pairs(parkingdata)do
self:T2(self.lid..string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d",
departure:GetName(),_spot.TerminalID,_spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
end
self:T(self.lid..string.format("%s at %s: free parking spots = %d - number of units = %d",self.alias,departure:GetName(),nfree,nunits))
end
local _notenough=false
if spawnonship or spawnonfarp or spawnonrunway then
if nfree>=1 then
for i=1,nunits do
table.insert(parkingspots,spots[1].Coordinate)
table.insert(parkingindex,spots[1].TerminalID)
end
PointVec3=spots[1].Coordinate
else
_notenough=true
end
elseif spawnonairport then
if nfree>=nunits then
for i=1,nunits do
table.insert(parkingspots,spots[i].Coordinate)
table.insert(parkingindex,spots[i].TerminalID)
end
else
_notenough=true
end
end
if _notenough then
if self.respawn_inair and not self.SpawnUnControlled then
self:E(self.lid..string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,departure:GetName()))
spawnonground=false
spawnonship=false
spawnonfarp=false
spawnonrunway=false
waypoints[1].type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1]
waypoints[1].action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2]
PointVec3.x=PointVec3.x+math.random(-1500,1500)
PointVec3.z=PointVec3.z+math.random(-1500,1500)
if self.category==RAT.cat.heli then
PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000)
else
PointVec3.y=PointVec3:GetLandHeight()+math.random(500,3000)
end
else
self:E(self.lid..string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,departure:GetName()))
return nil
end
end
else
end
for UnitID=1,nunits do
local UnitTemplate=SpawnTemplate.units[UnitID]
local SX=UnitTemplate.x
local SY=UnitTemplate.y
local BX=SpawnTemplate.route.points[1].x
local BY=SpawnTemplate.route.points[1].y
local TX=PointVec3.x+(SX-BX)
local TY=PointVec3.z+(SY-BY)
if spawnonground then
if spawnonship or spawnonfarp or spawnonrunway or automatic then
self:T(self.lid..string.format("RAT group %s spawning at farp, ship or runway %s.",self.alias,departure:GetName()))
SpawnTemplate.units[UnitID].x=PointVec3.x
SpawnTemplate.units[UnitID].y=PointVec3.z
SpawnTemplate.units[UnitID].alt=PointVec3.y
else
self:T(self.lid..string.format("RAT group %s spawning at airbase %s on parking spot id %d",self.alias,departure:GetName(),parkingindex[UnitID]))
SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x
SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z
SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y
end
else
self:T(self.lid..string.format("RAT group %s spawning in air at %s.",self.alias,departure:GetName()))
SpawnTemplate.units[UnitID].x=TX
SpawnTemplate.units[UnitID].y=TY
SpawnTemplate.units[UnitID].alt=PointVec3.y
end
if self.Debug then
local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x,SpawnTemplate.units[UnitID].alt,SpawnTemplate.units[UnitID].y)
unitspawn:MarkToAll(string.format("RAT %s Spawnplace unit #%d",self.alias,UnitID))
end
UnitTemplate.parking=nil
UnitTemplate.parking_id=nil
if parkingindex[UnitID]and not automatic then
UnitTemplate.parking=parkingindex[UnitID]
end
self:T2(self.lid..string.format("RAT group %s unit number %d: Parking    = %s",self.alias,UnitID,tostring(UnitTemplate.parking)))
self:T2(self.lid..string.format("RAT group %s unit number %d: Parking ID = %s",self.alias,UnitID,tostring(UnitTemplate.parking_id)))
SpawnTemplate.units[UnitID].heading=heading
SpawnTemplate.units[UnitID].psi=-heading
if livery then
SpawnTemplate.units[UnitID].livery_id=livery
end
if self.actype then
SpawnTemplate.units[UnitID]["type"]=self.actype
end
SpawnTemplate.units[UnitID]["skill"]=self.skill
if self.onboardnum then
SpawnTemplate.units[UnitID]["onboard_num"]=string.format("%s%d%02d",self.onboardnum,(self.SpawnIndex-1)%10,(self.onboardnum0-1)+UnitID)
end
SpawnTemplate.CoalitionID=self.coalition
if self.country then
SpawnTemplate.CountryID=self.country
end
end
for i,wp in ipairs(waypoints)do
SpawnTemplate.route.points[i]=wp
end
SpawnTemplate.x=PointVec3.x
SpawnTemplate.y=PointVec3.z
if self.radio then
SpawnTemplate.communication=self.radio
end
if self.frequency then
SpawnTemplate.frequency=self.frequency
end
if self.modulation then
SpawnTemplate.modulation=self.modulation
end
self:T(SpawnTemplate)
end
end
return true
end
function RAT._ATCInit(airports_map)
if not RAT.ATC.init then
local text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay        = "..RAT.ATC.delay
BASE:I(RAT.id..text)
for _,ap in pairs(airports_map)do
local airbase=ap
local name=airbase:GetName()
local fc=_DATABASE:GetFlightControl(name)
if not fc then
local airport={}
airport.queue={}
airport.busy=false
airport.onfinal={}
airport.Nonfinal=0
airport.traffic=0
airport.Tlastclearance=nil
RAT.ATC.airport[name]=airport
end
end
SCHEDULER:New(nil,RAT._ATCCheck,{},5,15)
SCHEDULER:New(nil,RAT._ATCStatus,{},5,60)
RAT.ATC.T0=timer.getTime()
end
RAT.ATC.init=true
end
function RAT:_ATCAddFlight(name,dest)
BASE:I(RAT.id..string.format("ATC %s: Adding flight %s with destination %s.",dest,name,dest))
local flight={}
flight.destination=dest
flight.Tarrive=-1
flight.holding=-1
flight.Tarrive=-1
RAT.ATC.flight[name]=flight
end
function RAT._ATCDelFlight(t,entry)
for k,_ in pairs(t)do
if k==entry then
BASE:I(RAT.id..string.format("Removing flight %s from queue",entry))
t[entry]=nil
end
end
end
function RAT._ATCRegisterFlight(name,time)
BASE:I(RAT.id..string.format("Flight %s registered at ATC for landing clearance.",name))
RAT.ATC.flight[name].Tarrive=time
RAT.ATC.flight[name].holding=0
end
function RAT._ATCStatus()
local Tnow=timer.getTime()
for name,_flight in pairs(RAT.ATC.flight)do
local flight=_flight
local hold=RAT.ATC.flight[name].holding
local dest=RAT.ATC.flight[name].destination
local airport=RAT.ATC.airport[dest]
if airport then
if hold>=0 then
local busy="Runway state is unknown"
if airport.Nonfinal>0 then
busy="Runway is occupied by "..airport.Nonfinal
else
busy="Runway is currently clear"
end
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.",dest,name,hold/60,hold%60,busy)
BASE:I(RAT.id..text)
elseif hold==RAT.ATC.onfinal then
local Tfinal=Tnow-flight.Tonfinal
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.",dest,name,Tfinal/60,Tfinal%60)
BASE:I(RAT.id..text)
elseif hold==RAT.ATC.unregistered then
else
BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
end
else
end
end
end
function RAT._ATCCheck()
RAT._ATCQueue()
local Tnow=timer.getTime()
for airportname,_airport in pairs(RAT.ATC.airport)do
local airport=_airport
for qID,flightname in pairs(airport.queue)do
local flight=RAT.ATC.flight[flightname]
local nqueue=#airport.queue
local landing1=false
if airport.Tlastclearance then
landing1=(Tnow-airport.Tlastclearance>RAT.ATC.delay)and airport.Nonfinal<RAT.ATC.Nclearance
end
local landing2=airport.Nonfinal==0
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)
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)
RAT._ATCClearForLanding(airportname,flightname)
end
end
end
RAT._ATCQueue()
end
function RAT._ATCClearForLanding(airportname,flightname)
local flightgroup=_DATABASE:FindOpsGroup(flightname)
if flightgroup then
flightgroup:ClearToLand()
local flight=RAT.ATC.flight[flightname]
flight.holding=RAT.ATC.onfinal
flight.Tonfinal=timer.getTime()
local airport=RAT.ATC.airport[airportname]
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))
if string.find(flightname,"#")then
flightname=string.match(flightname,"^(.+)#")
end
local text=string.format("ATC %s: Flight %s you are cleared for landing.",airportname,flightname)
MESSAGE:New(text,10):ToAllIf(RAT.ATC.messages)
else
BASE:E("Could not clear flight for landing!")
end
end
function RAT._ATCFlightLanded(name)
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 airport=RAT.ATC.airport[dest]
airport.busy=false
airport.onfinal[name]=nil
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))
if string.find(name,"#")then
name=string.match(name,"^(.+)#")
end
local text=string.format("ATC %s: Flight %s landed. Welcome to %s.",dest,name,dest)
MESSAGE:New(text,10):ToAllIf(RAT.ATC.messages)
end
end
function RAT._ATCQueue()
local Tnow=timer.getTime()
for airport,_ in pairs(RAT.ATC.airport)do
local _queue={}
for name,_flight in pairs(RAT.ATC.flight)do
local flight=_flight
if flight.holding>=0 then
flight.holding=Tnow-flight.Tarrive
end
local hold=flight.holding
local dest=flight.destination
if hold>=0 and airport==dest then
_queue[#_queue+1]={name,hold}
end
end
local function compare(a,b)
return a[2]>b[2]
end
table.sort(_queue,compare)
RAT.ATC.airport[airport].queue={}
for k,v in ipairs(_queue)do
table.insert(RAT.ATC.airport[airport].queue,v[1])
end
end
end
RATMANAGER={
ClassName="RATMANAGER",
Debug=false,
rat={},
name={},
alive={},
planned={},
min={},
nrat=0,
ntot=nil,
Tcheck=60,
dTspawn=1.0,
manager=nil,
managerid=nil,
}
RATMANAGER.id="RATMANAGER | "
function RATMANAGER:New(ntot)
local self=BASE:Inherit(self,BASE:New())
self.ntot=ntot or 1
self:I(RATMANAGER.id..string.format("Creating manager for %d groups",ntot))
return self
end
function RATMANAGER:Add(ratobject,min)
ratobject.norespawn=true
ratobject.f10menu=false
self.nrat=self.nrat+1
self.rat[self.nrat]=ratobject
self.alive[self.nrat]=0
self.planned[self.nrat]=0
self.name[self.nrat]=ratobject.alias
self.min[self.nrat]=min or 1
self:T(RATMANAGER.id..string.format("Adding ratobject %s with min flights = %d",self.name[self.nrat],self.min[self.nrat]))
ratobject:Spawn(0)
return self
end
function RATMANAGER:Start(delay)
delay=delay or 5
if delay and delay>0 then
local text=string.format(RATMANAGER.id.."RAT manager will be started in %d seconds.\n",delay)
text=text..string.format("Managed groups:\n")
for i=1,self.nrat do
text=text..string.format("- %s with min groups %d\n",self.name[i],self.min[i])
end
text=text..string.format("Number of constantly alive groups %d",self.ntot)
self:E(text)
self:ScheduleOnce(delay,RATMANAGER.Start,self,0)
else
local n=0
for i=1,self.nrat do
n=n+self.min[i]
end
self.ntot=math.max(self.ntot,n)
local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive)
local time=0.0
for i=1,self.nrat do
for j=1,N[i]do
time=time+self.dTspawn
self:ScheduleOnce(time,RAT._SpawnWithRoute,self.rat[i])
end
end
for i=1,self.nrat do
local rat=self.rat[i]
if rat.uncontrolled and rat.activate_uncontrolled then
local Tactivate=math.max(time+1,rat.activate_delay)
self:ScheduleRepeat(Tactivate,rat.activate_delta,rat.activate_frand,nil,rat._ActivateUncontrolled,rat)
end
end
local TstartManager=math.max(time+1,self.Tcheck)
self.manager,self.managerid=SCHEDULER:New(self,self._Manage,{self},TstartManager,self.Tcheck)
local text=string.format(RATMANAGER.id.."Starting RAT manager with scheduler ID %s in %d seconds. Repeat interval %d seconds.",self.managerid,TstartManager,self.Tcheck)
self:I(text)
end
return self
end
function RATMANAGER:Stop(delay)
delay=delay or 1
if delay and delay>0 then
self:I(RATMANAGER.id..string.format("Manager will be stopped in %d seconds.",delay))
self:ScheduleOnce(delay,RATMANAGER.Stop,self,0)
else
self:I(RATMANAGER.id..string.format("Stopping manager with scheduler ID %s",self.managerid))
self.manager:Stop(self.managerid)
end
return self
end
function RATMANAGER:SetTcheck(dt)
self.Tcheck=dt or 60
return self
end
function RATMANAGER:SetTspawn(dt)
self.dTspawn=dt or 1.0
return self
end
function RATMANAGER:_Manage()
local ntot=self:_Count()
self:T(RATMANAGER.id..string.format("Number of alive groups %d. New groups to be spawned %d.",ntot,self.ntot-ntot))
local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive)
local time=0.0
for i=1,self.nrat do
for j=1,N[i]do
time=time+self.dTspawn
self.planned[i]=self.planned[i]+1
self:ScheduleOnce(time,RATMANAGER._Spawn,self,i)
end
end
end
function RATMANAGER:_Spawn(i)
local rat=self.rat[i]
rat:_SpawnWithRoute()
self.planned[i]=self.planned[i]-1
end
function RATMANAGER:_Count()
local ntotal=0
for i=1,self.nrat do
local n=0
local ratobject=self.rat[i]
for spawnindex,ratcraft in pairs(ratobject.ratcraft)do
local group=ratcraft.group
if group and group:IsAlive()then
n=n+1
end
end
self.alive[i]=n
ntotal=ntotal+n
local text=string.format("Number of alive groups of %s = %d, planned=%d",self.name[i],n,self.planned[i])
self:T(RATMANAGER.id..text)
end
return ntotal
end
function RATMANAGER:_RollDice(nrat,ntot,min,alive)
local function sum(A,index)
local summe=0
for _,i in ipairs(index)do
summe=summe+A[i]
end
return summe
end
local N={}
local M={}
local P={}
for i=1,nrat do
local a=alive[i]+self.planned[i]
N[#N+1]=0
M[#M+1]=math.max(a,min[i])
P[#P+1]=math.max(min[i]-a,0)
end
local mini={}
local maxi={}
local rattab={}
for i=1,nrat do
table.insert(rattab,i)
end
local done={}
local nnew=ntot
for i=1,nrat do
nnew=nnew-alive[i]-self.planned[i]
end
for i=1,nrat-1 do
local r=math.random(#rattab)
local j=rattab[r]
table.remove(rattab,r)
table.insert(done,j)
local sN=sum(N,done)
local sP=sum(P,rattab)
maxi[j]=nnew-sN-sP
mini[j]=P[j]
if maxi[j]>=mini[j]then
N[j]=math.random(mini[j],maxi[j])
else
N[j]=0
end
self:T3(string.format("RATMANAGER: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d",j,alive[j],self.planned[i],min[j],mini[j],maxi[j],N[j],sN,sP))
end
local j=rattab[1]
N[j]=nnew-sum(N,done)
mini[j]=nnew-sum(N,done)
maxi[j]=nnew-sum(N,done)
table.remove(rattab,1)
table.insert(done,j)
local text=RATMANAGER.id.."\n"
for i=1,nrat do
text=text..string.format("%s: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d\n",self.name[i],i,alive[i],self.planned[i],min[i],mini[i],maxi[i],N[i])
end
text=text..string.format("Total # of groups to add = %d",sum(N,done))
self:T(text)
return N
end
RANGE={
ClassName="RANGE",
Debug=false,
verbose=0,
id=nil,
rangename=nil,
location=nil,
messages=true,
rangeradius=5000,
rangezone=nil,
strafeTargets={},
bombingTargets={},
nbombtargets=0,
nstrafetargets=0,
MenuAddedTo={},
planes={},
strafeStatus={},
strafePlayerResults={},
bombPlayerResults={},
PlayerSettings={},
dtBombtrack=0.005,
BombtrackThreshold=25000,
Tmsg=30,
examinergroupname=nil,
examinerexclusive=nil,
strafemaxalt=914,
ndisplayresult=10,
BombSmokeColor=SMOKECOLOR.Red,
StrafeSmokeColor=SMOKECOLOR.Green,
StrafePitSmokeColor=SMOKECOLOR.White,
illuminationminalt=500,
illuminationmaxalt=1000,
scorebombdistance=1000,
TdelaySmoke=3.0,
trackbombs=true,
trackrockets=true,
trackmissiles=true,
defaultsmokebomb=true,
autosave=false,
instructorfreq=nil,
instructor=nil,
rangecontrolfreq=nil,
rangecontrol=nil,
soundpath="Range Soundfiles/",
targetsheet=nil,
targetpath=nil,
targetprefix=nil,
Coalition=nil,
ceilingaltitude=20000,
ceilingenabled=false,
}
RANGE.Defaults={
goodhitrange=25,
strafemaxalt=914,
dtBombtrack=0.005,
Tmsg=30,
ndisplayresult=10,
rangeradius=5000,
TdelaySmoke=3.0,
boxlength=3000,
boxwidth=300,
goodpass=20,
foulline=610
}
RANGE.TargetType={
UNIT="Unit",
STATIC="Static",
COORD="Coordinate",
SCENERY="Scenery"
}
RANGE.Sound={
RC0={filename="RC-0.ogg",duration=0.60},
RC1={filename="RC-1.ogg",duration=0.47},
RC2={filename="RC-2.ogg",duration=0.43},
RC3={filename="RC-3.ogg",duration=0.50},
RC4={filename="RC-4.ogg",duration=0.58},
RC5={filename="RC-5.ogg",duration=0.54},
RC6={filename="RC-6.ogg",duration=0.61},
RC7={filename="RC-7.ogg",duration=0.53},
RC8={filename="RC-8.ogg",duration=0.34},
RC9={filename="RC-9.ogg",duration=0.54},
RCAccuracy={filename="RC-Accuracy.ogg",duration=0.67},
RCDegrees={filename="RC-Degrees.ogg",duration=0.59},
RCExcellentHit={filename="RC-ExcellentHit.ogg",duration=0.76},
RCExcellentPass={filename="RC-ExcellentPass.ogg",duration=0.89},
RCFeet={filename="RC-Feet.ogg",duration=0.49},
RCFor={filename="RC-For.ogg",duration=0.64},
RCGoodHit={filename="RC-GoodHit.ogg",duration=0.52},
RCGoodPass={filename="RC-GoodPass.ogg",duration=0.62},
RCHitsOnTarget={filename="RC-HitsOnTarget.ogg",duration=0.88},
RCImpact={filename="RC-Impact.ogg",duration=0.61},
RCIneffectiveHit={filename="RC-IneffectiveHit.ogg",duration=0.86},
RCIneffectivePass={filename="RC-IneffectivePass.ogg",duration=0.99},
RCInvalidHit={filename="RC-InvalidHit.ogg",duration=2.97},
RCLeftStrafePitTooQuickly={filename="RC-LeftStrafePitTooQuickly.ogg",duration=3.09},
RCPercent={filename="RC-Percent.ogg",duration=0.56},
RCPoorHit={filename="RC-PoorHit.ogg",duration=0.54},
RCPoorPass={filename="RC-PoorPass.ogg",duration=0.68},
RCRollingInOnStrafeTarget={filename="RC-RollingInOnStrafeTarget.ogg",duration=1.38},
RCTotalRoundsFired={filename="RC-TotalRoundsFired.ogg",duration=1.22},
RCWeaponImpactedTooFar={filename="RC-WeaponImpactedTooFar.ogg",duration=3.73},
IR0={filename="IR-0.ogg",duration=0.55},
IR1={filename="IR-1.ogg",duration=0.41},
IR2={filename="IR-2.ogg",duration=0.37},
IR3={filename="IR-3.ogg",duration=0.41},
IR4={filename="IR-4.ogg",duration=0.37},
IR5={filename="IR-5.ogg",duration=0.43},
IR6={filename="IR-6.ogg",duration=0.55},
IR7={filename="IR-7.ogg",duration=0.43},
IR8={filename="IR-8.ogg",duration=0.38},
IR9={filename="IR-9.ogg",duration=0.55},
IRDecimal={filename="IR-Decimal.ogg",duration=0.54},
IRMegaHertz={filename="IR-MegaHertz.ogg",duration=0.87},
IREnterRange={filename="IR-EnterRange.ogg",duration=4.83},
IRExitRange={filename="IR-ExitRange.ogg",duration=3.10},
}
RANGE.Names={}
RANGE.MenuF10={}
RANGE.MenuF10Root=nil
RANGE.version="2.8.1"
function RANGE:New(RangeName,Coalition)
local self=BASE:Inherit(self,FSM:New())
self.rangename=RangeName or"Practice Range"
self.Coalition=Coalition
self.lid=string.format("RANGE %s | ",self.rangename)
local text=string.format("Script version %s - creating new RANGE object %s.",RANGE.version,self.rangename)
self:I(self.lid..text)
self:SetDefaultPlayerSmokeBomb()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Impact","*")
self:AddTransition("*","RollingIn","*")
self:AddTransition("*","StrafeResult","*")
self:AddTransition("*","EnterRange","*")
self:AddTransition("*","ExitRange","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Load","*")
return self
end
function RANGE:onafterStart()
local _location=nil
local _count=0
for _,_target in pairs(self.bombingTargets)do
_count=_count+1
if _location==nil then
_location=self:_GetBombTargetCoordinate(_target)
end
end
self.nbombtargets=_count
_count=0
for _,_target in pairs(self.strafeTargets)do
_count=_count+1
for _,_unit in pairs(_target.targets)do
if _location==nil then
_location=_unit:GetCoordinate()
end
end
end
self.nstrafetargets=_count
if self.location==nil then
self.location=_location
end
if self.location==nil then
local text=string.format("ERROR! No range location found. Number of strafe targets = %d. Number of bomb targets = %d.",self.nstrafetargets,self.nbombtargets)
self:E(self.lid..text)
return
end
if self.rangezone==nil then
self.rangezone=ZONE_RADIUS:New(self.rangename,{x=self.location.x,y=self.location.z},self.rangeradius)
end
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.",self.rangename,self.nstrafetargets,self.nbombtargets)
self:I(self.lid..text)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Shot)
for _,_target in pairs(self.bombingTargets)do
local target=_target
if target.move and target.type==RANGE.TargetType.UNIT and target.speed>1 then
target.target:PatrolZones({self.rangezone},target.speed*0.75,ENUMS.Formation.Vehicle.OffRoad)
end
end
if self.rangecontrolfreq and not self.useSRS then
self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq,nil,self.rangename)
self.rangecontrol.schedonce=true
self.rangecontrol:SetDigit(0,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,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)
end
end
if self.autosave then
self:Load()
end
if self.Debug then
self:_MarkTargetsOnMap()
self:_SmokeBombTargets()
self:_SmokeStrafeTargets()
self:_SmokeStrafeTargetBoxes()
self.rangezone:SmokeZone(SMOKECOLOR.White)
end
self:__Status(-10)
end
function RANGE:SetMenuRoot(menu)
self.menuF10root=menu
return self
end
function RANGE:SetMaxStrafeAlt(maxalt)
self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt
return self
end
function RANGE:SetBombtrackTimestep(dt)
self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack
return self
end
function RANGE:SetMessageTimeDuration(time)
self.Tmsg=time or RANGE.Defaults.Tmsg
return self
end
function RANGE:SetAutosaveOn()
self.autosave=true
return self
end
function RANGE:SetAutosaveOff()
self.autosave=false
return self
end
function RANGE:SetTargetSheet(path,prefix)
if io then
self.targetsheet=true
self.targetpath=path
self.targetprefix=prefix
else
self:E(self.lid.."ERROR: io is not desanitized. Cannot save target sheet.")
end
return self
end
function RANGE:SetFunkManOn(Port,Host)
self.funkmanSocket=SOCKET:New(Port,Host)
return self
end
function RANGE:SetMessageToExaminer(examinergroupname,exclusively)
self.examinergroupname=examinergroupname
self.examinerexclusive=exclusively
return self
end
function RANGE:SetDisplayedMaxPlayerResults(nmax)
self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult
return self
end
function RANGE:SetRangeRadius(radius)
self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius
return self
end
function RANGE:SetDefaultPlayerSmokeBomb(switch)
if switch==true or switch==nil then
self.defaultsmokebomb=true
else
self.defaultsmokebomb=false
end
return self
end
function RANGE:SetBombtrackThreshold(distance)
self.BombtrackThreshold=(distance or 25)*1000
return self
end
function RANGE:SetRangeLocation(coordinate)
self.location=coordinate
return self
end
function RANGE:SetRangeZone(zone)
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
end
function RANGE:SetScoreBombDistance(distance)
self.scorebombdistance=distance or 1000
return self
end
function RANGE:SetStrafeTargetSmokeColor(colorid)
self.StrafeSmokeColor=colorid or SMOKECOLOR.Green
return self
end
function RANGE:SetStrafePitSmokeColor(colorid)
self.StrafePitSmokeColor=colorid or SMOKECOLOR.White
return self
end
function RANGE:SetSmokeTimeDelay(delay)
self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke
return self
end
function RANGE:DebugON()
self.Debug=true
return self
end
function RANGE:DebugOFF()
self.Debug=false
return self
end
function RANGE:SetMessagesOFF()
self.messages=false
return self
end
function RANGE:SetMessagesON()
self.messages=true
return self
end
function RANGE:TrackBombsON()
self.trackbombs=true
return self
end
function RANGE:TrackBombsOFF()
self.trackbombs=false
return self
end
function RANGE:TrackRocketsON()
self.trackrockets=true
return self
end
function RANGE:TrackRocketsOFF()
self.trackrockets=false
return self
end
function RANGE:TrackMissilesON()
self.trackmissiles=true
return self
end
function RANGE:TrackMissilesOFF()
self.trackmissiles=false
return self
end
function RANGE:SetSRS(PathToSRS,Port,Coalition,Frequency,Modulation,Volume,PathToGoogleKey)
if PathToSRS or MSRS.path then
self.useSRS=true
self.controlmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 256,Modulation or radio.modulation.AM)
self.controlmsrs:SetPort(Port or MSRS.port)
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.controlmsrs:SetLabel("RANGEC")
self.controlmsrs:SetVolume(Volume or 1.0)
self.controlsrsQ=MSRSQUEUE:New("CONTROL")
self.instructmsrs=MSRS:New(PathToSRS or MSRS.path,Frequency or 305,Modulation or radio.modulation.AM)
self.instructmsrs:SetPort(Port or MSRS.port)
self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.instructmsrs:SetLabel("RANGEI")
self.instructmsrs:SetVolume(Volume or 1.0)
self.instructsrsQ=MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then
self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
self.controlmsrs:SetProvider(MSRS.Provider.GOOGLE)
self.instructmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
self.instructmsrs:SetProvider(MSRS.Provider.GOOGLE)
end
else
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end
return self
end
function RANGE:SetSRSRangeControl(frequency,modulation,voice,culture,gender,relayunitname)
if not self.instructmsrs then
self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!")
return self
end
self.rangecontrolfreq=frequency or 256
self.controlmsrs:SetFrequencies(self.rangecontrolfreq)
self.controlmsrs:SetModulations(modulation or radio.modulation.AM)
self.controlmsrs:SetVoice(voice)
self.controlmsrs:SetCulture(culture or"en-US")
self.controlmsrs:SetGender(gender or"female")
self.rangecontrol=true
if relayunitname then
local unit=UNIT:FindByName(relayunitname)
local Coordinate=unit:GetCoordinate()
self.rangecontrolrelayname=relayunitname
end
return self
end
function RANGE:SetSRSRangeInstructor(frequency,modulation,voice,culture,gender,relayunitname)
if not self.instructmsrs then
self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!")
return self
end
self.instructorfreq=frequency or 305
self.instructmsrs:SetFrequencies(self.instructorfreq)
self.instructmsrs:SetModulations(modulation or radio.modulation.AM)
self.instructmsrs:SetVoice(voice)
self.instructmsrs:SetCulture(culture or"en-US")
self.instructmsrs:SetGender(gender or"male")
self.instructor=true
if relayunitname then
local unit=UNIT:FindByName(relayunitname)
local Coordinate=unit:GetCoordinate()
self.instructmsrs:SetCoordinate(Coordinate)
self.instructorrelayname=relayunitname
end
return self
end
function RANGE:SetRangeControl(frequency,relayunitname)
self.rangecontrolfreq=frequency or 256
self.rangecontrolrelayname=relayunitname
return self
end
function RANGE:SetInstructorRadio(frequency,relayunitname)
self.instructorfreq=frequency or 305
self.instructorrelayname=relayunitname
return self
end
function RANGE:SetSoundfilesPath(path)
self.soundpath=tostring(path or"Range Soundfiles/")
self: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)
self:F({targetnames=targetnames,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline})
if type(targetnames)~="table"then
targetnames={targetnames}
end
local _targets={}
local center=nil
local ntargets=0
for _i,_name in ipairs(targetnames)do
local _isstatic=self:_CheckStatic(_name)
local unit=nil
if _isstatic==true then
self:T(self.lid..string.format("Adding STATIC object %s as strafe target #%d.",_name,_i))
unit=STATIC:FindByName(_name,false)
elseif _isstatic==false then
self:T(self.lid..string.format("Adding UNIT object %s as strafe target #%d.",_name,_i))
unit=UNIT:FindByName(_name)
else
local text=string.format("ERROR! Could not find ANY strafe target object with name %s.",_name)
self:E(self.lid..text)
end
if unit then
table.insert(_targets,unit)
if center==nil then
center=unit
end
ntargets=ntargets+1
end
end
if ntargets==0 then
local text=string.format("ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s",self.rangename)
self:E(self.lid..text)
return
end
local l=boxlength or RANGE.Defaults.boxlength
local w=(boxwidth or RANGE.Defaults.boxwidth)/2
local heading=heading or center:GetHeading()
if inverseheading~=nil then
if inverseheading then
heading=heading-180
end
end
if heading<0 then
heading=heading+360
end
if heading>360 then
heading=heading-360
end
goodpass=goodpass or RANGE.Defaults.goodpass
foulline=foulline or RANGE.Defaults.foulline
local Ccenter=center:GetCoordinate()
local _name=center:GetName()
local p={}
p[#p+1]=Ccenter:Translate(w,heading+90)
p[#p+1]=p[#p]:Translate(l,heading)
p[#p+1]=p[#p]:Translate(2*w,heading-90)
p[#p+1]=p[#p]:Translate(-l,heading)
local pv2={}
for i,p in ipairs(p)do
pv2[i]={x=p.x,y=p.z}
end
local _polygon=ZONE_POLYGON_BASE:New(_name,pv2)
local st={}
st.name=_name
st.polygon=_polygon
st.coordinate=Ccenter
st.goodPass=goodpass
st.targets=_targets
st.foulline=foulline
st.smokepoints=p
st.heading=heading
table.insert(self.strafeTargets,st)
local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f",_name,ntargets,heading,l,w,goodpass,foulline)
self:T(self.lid..text)
return self
end
function RANGE:AddStrafePitGroup(group,boxlength,boxwidth,heading,inverseheading,goodpass,foulline)
self:F({group=group,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline})
if group and group:IsAlive()then
local _units=group:GetUnits()
local _names={}
for _,_unit in ipairs(_units)do
local _unit=_unit
if _unit and _unit:IsAlive()then
local _name=_unit:GetName()
table.insert(_names,_name)
end
end
self:AddStrafePit(_names,boxlength,boxwidth,heading,inverseheading,goodpass,foulline)
end
return self
end
function RANGE:AddBombingTargets(targetnames,goodhitrange,randommove)
self:F({targetnames=targetnames,goodhitrange=goodhitrange,randommove=randommove})
if type(targetnames)~="table"then
targetnames={targetnames}
end
goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange
for _,name in pairs(targetnames)do
local _isstatic=self:_CheckStatic(name)
if _isstatic==true then
local _static=STATIC:FindByName(name)
self:T2(self.lid..string.format("Adding static bombing target %s with hit range %d.",name,goodhitrange,false))
self:AddBombingTargetUnit(_static,goodhitrange)
elseif _isstatic==false then
local _unit=UNIT:FindByName(name)
self:T2(self.lid..string.format("Adding unit bombing target %s with hit range %d.",name,goodhitrange,randommove))
self:AddBombingTargetUnit(_unit,goodhitrange,randommove)
else
self:E(self.lid..string.format("ERROR! Could not find bombing target %s.",name))
end
end
return self
end
function RANGE:AddBombingTargetUnit(unit,goodhitrange,randommove)
self:F({unit=unit,goodhitrange=goodhitrange,randommove=randommove})
local name=unit:GetName()
local _isstatic=self:_CheckStatic(name)
goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange
if randommove==nil or _isstatic==true then
randommove=false
end
if _isstatic==true then
self: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: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
local speed=0
if _isstatic==false then
speed=self:_GetSpeed(unit)
end
local target={}
target.name=name
target.target=unit
target.goodhitrange=goodhitrange
target.move=randommove
target.speed=speed
target.coordinate=unit:GetCoordinate()
if _isstatic then
target.type=RANGE.TargetType.STATIC
else
target.type=RANGE.TargetType.UNIT
end
table.insert(self.bombingTargets,target)
return self
end
function RANGE:AddBombingTargetCoordinate(coord,name,goodhitrange)
local target={}
target.name=name or"Bomb Target"
target.target=nil
target.goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange
target.move=false
target.speed=0
target.coordinate=coord
target.type=RANGE.TargetType.COORD
table.insert(self.bombingTargets,target)
return self
end
function RANGE:AddBombingTargetScenery(scenery,goodhitrange)
local name=scenery:GetName()
goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange
if name then
self: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
local target={}
target.name=name
target.target=scenery
target.goodhitrange=goodhitrange
target.move=false
target.speed=0
target.coordinate=scenery:GetCoordinate()
target.type=RANGE.TargetType.SCENERY
table.insert(self.bombingTargets,target)
return self
end
function RANGE:AddBombingTargetGroup(group,goodhitrange,randommove)
self:F({group=group,goodhitrange=goodhitrange,randommove=randommove})
if group and type(group)=="string"then
group=GROUP:FindByName(group)
end
if group then
local _units=group:GetUnits()
for _,_unit in pairs(_units)do
if _unit and _unit:IsAlive()then
self:AddBombingTargetUnit(_unit,goodhitrange,randommove)
end
end
end
return self
end
function RANGE:GetFoullineDistance(namepit,namefoulline)
self:F({namepit=namepit,namefoulline=namefoulline})
local _staticpit=self:_CheckStatic(namepit)
local _staticfoul=self:_CheckStatic(namefoulline)
local pit=nil
if _staticpit==true then
pit=STATIC:FindByName(namepit,false)
elseif _staticpit==false then
pit=UNIT:FindByName(namepit)
else
self:E(self.lid..string.format("ERROR! Pit object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namepit))
end
local foul=nil
if _staticfoul==true then
foul=STATIC:FindByName(namefoulline,false)
elseif _staticfoul==false then
foul=UNIT:FindByName(namefoulline)
else
self:E(self.lid..string.format("ERROR! Foul line object %s could not be found in GetFoullineDistance function. Check the name in the ME.",namefoulline))
end
local fouldist=0
if pit~=nil and foul~=nil then
fouldist=pit:GetCoordinate():Get2DDistance(foul:GetCoordinate())
else
self:E(self.lid..string.format("ERROR! Foul line distance could not be determined. Check pit object name %s and foul line object name %s in the ME.",namepit,namefoulline))
end
self:T(self.lid..string.format("Foul line distance = %.1f m.",fouldist))
return fouldist
end
function RANGE:OnEventBirth(EventData)
self:F({eventbirth=EventData})
if not EventData.IniPlayerName then return end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName,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))
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _gid=_group:GetID()
local _callsign=_unit:GetCallsign()
local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)",_playername,_callsign,_unitName,_uid,_group:GetName(),_gid)
self:T(self.lid..text)
self.strafeStatus[_uid]=nil
if self.Coalition then
if EventData.IniCoalition==self.Coalition then
self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName)
end
else
self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName)
end
self.PlayerSettings[_playername]={}
self.PlayerSettings[_playername].smokebombimpact=self.defaultsmokebomb
self.PlayerSettings[_playername].flaredirecthits=false
self.PlayerSettings[_playername].smokecolor=SMOKECOLOR.Blue
self.PlayerSettings[_playername].flarecolor=FLARECOLOR.Red
self.PlayerSettings[_playername].delaysmoke=true
self.PlayerSettings[_playername].messages=true
self.PlayerSettings[_playername].client=CLIENT:FindByName(_unitName,nil,true)
self.PlayerSettings[_playername].unitname=_unitName
self.PlayerSettings[_playername].unit=_unit
self.PlayerSettings[_playername].playername=_playername
self.PlayerSettings[_playername].airframe=EventData.IniUnit:GetTypeName()
self.PlayerSettings[_playername].inzone=false
if self.planes[_uid]~=true then
self.timerCheckZone=TIMER:New(self._CheckInZone,self,EventData.IniUnitName):Start(1,1)
self.planes[_uid]=true
end
end
end
function RANGE:OnEventHit(EventData)
self:F({eventhit=EventData})
self:T3(self.lid.."HIT: Ini unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."HIT: Ini group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."HIT: Tgt target = "..tostring(EventData.TgtUnitName))
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit==nil or _playername==nil then
return
end
local _unitID=_unit:GetID()
local target=EventData.TgtUnit
local targetname=EventData.TgtUnitName
local _currentTarget=self.strafeStatus[_unitID]
if _currentTarget and target and target:IsAlive()then
local playerPos=_unit:GetCoordinate()
local targetPos=target:GetCoordinate()
for _,_target in pairs(_currentTarget.zone.targets)do
if _target and _target:IsAlive()and _target:GetName()==targetname then
local dist=playerPos:Get2DDistance(targetPos)
if dist>_currentTarget.zone.foulline then
_currentTarget.hits=_currentTarget.hits+1
if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then
targetPos:Flare(self.PlayerSettings[_playername].flarecolor)
end
else
if _currentTarget.pastfoulline==false and _unit and _playername then
local _d=_currentTarget.zone.foulline
local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.",self:_myname(_unitName),_d,targetname)
if self.useSRS then
local ttstext=string.format("%s, Invalid hit! You already passed foul line distance of %d meters for target %s.",self:_myname(_unitName),_d,targetname)
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
end
self:_DisplayMessageToGroup(_unit,text)
self:T2(self.lid..text)
_currentTarget.pastfoulline=true
end
end
end
end
end
for _,_bombtarget in pairs(self.bombingTargets)do
local _target=_bombtarget.target
if _target and _target:IsAlive()and _bombtarget.name==targetname then
if _unit and _playername then
if self.PlayerSettings[_playername].flaredirecthits then
local targetPos=_target:GetCoordinate()
targetPos:Flare(self.PlayerSettings[_playername].flarecolor)
end
end
end
end
end
function RANGE._OnImpact(weapon,self,playerData,attackHdg,attackAlt,attackVel)
if not playerData then return end
local _closetTarget=nil
local _distance=nil
local _closeCoord=nil
local _hitquality="POOR"
local _callsign=self:_myname(playerData.unitname)
local _playername=playerData.playername
local _unit=playerData.unit
local impactcoord=weapon:GetImpactCoordinate()
local insidezone=self.rangezone:IsCoordinateInZone(impactcoord)
if playerData and playerData.smokebombimpact and insidezone then
if playerData.delaysmoke then
impactcoord:Smoke(playerData.smokecolor,30,self.TdelaySmoke)
else
impactcoord:Smoke(playerData.smokecolor,30)
end
end
for _,_bombtarget in pairs(self.bombingTargets)do
local bombtarget=_bombtarget
local targetcoord=self:_GetBombTargetCoordinate(_bombtarget)
if targetcoord then
local _temp=impactcoord:Get2DDistance(targetcoord)
if _distance==nil or _temp<_distance then
_distance=_temp
_closetTarget=bombtarget
_closeCoord=targetcoord
if _distance<=1.53 then
_hitquality="SHACK"
elseif _distance<=0.5*bombtarget.goodhitrange then
_hitquality="EXCELLENT"
elseif _distance<=bombtarget.goodhitrange then
_hitquality="GOOD"
elseif _distance<=2*bombtarget.goodhitrange then
_hitquality="INEFFECTIVE"
else
_hitquality="POOR"
end
end
end
end
if _distance and _distance<=self.scorebombdistance then
if not self.bombPlayerResults[_playername]then
self.bombPlayerResults[_playername]={}
end
local _results=self.bombPlayerResults[_playername]
local result={}
result.command=SOCKET.DataType.BOMBRESULT
result.name=_closetTarget.name or"unknown"
result.distance=_distance
result.radial=_closeCoord:HeadingTo(impactcoord)
result.weapon=weapon:GetTypeName()or"unknown"
result.quality=_hitquality
result.player=playerData.playername
result.time=timer.getAbsTime()
result.clock=UTILS.SecondsToClock(result.time,true)
result.midate=UTILS.GetDCSMissionDate()
result.theatre=env.mission.theatre
result.airframe=playerData.airframe
result.roundsFired=0
result.roundsHit=0
result.roundsQuality="N/A"
result.rangename=self.rangename
result.attackHdg=attackHdg
result.attackVel=attackVel
result.attackAlt=attackAlt
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
local _message=string.format("%s, weapon impacted too far from nearest range target (>%.1f km). No score!",_callsign,self.scorebombdistance/1000)
if self.useSRS then
local ttstext=string.format("%s, weapon impacted too far from nearest range target, mor than %.1f kilometer. No score!",_callsign,self.scorebombdistance/1000)
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
end
self:_DisplayMessageToGroup(_unit,_message,nil,false)
if self.rangecontrol then
if self.useSRS then
self.controlsrsQ:NewTransmission(_message,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(self.Sound.RCWeaponImpactedTooFar.filename,self.Sound.RCWeaponImpactedTooFar.duration,self.soundpath,nil,nil,_message,self.subduration)
end
end
else
self:T(self.lid.."Weapon impacted outside range zone.")
end
end
function RANGE:OnEventShot(EventData)
self:F({eventshot=EventData})
if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.IniPlayerName==nil then
return
end
local weapon=WEAPON:New(EventData.weapon)
local _track=(weapon:IsBomb()and self.trackbombs)or(weapon:IsRocket()and self.trackrockets)or(weapon:IsMissile()and self.trackmissiles)
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName,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 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)
local attackVel=_unit:GetVelocityKNOTS()
self:T(self.lid..string.format("RANGE %s: Tracking %s - %s.",self.rangename,weapon:GetTypeName(),weapon:GetName()))
weapon:SetFuncImpact(RANGE._OnImpact,self,playerData,attackHdg,attackAlt,attackVel)
self:T(self.lid..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.",self.rangename,_playername))
weapon:StartTrack(0.1)
end
end
function RANGE:onafterStatus(From,Event,To)
if self.verbose>0 then
local fsmstate=self:GetState()
local text=string.format("Range status: %s",fsmstate)
if self.instructor then
local alive="N/A"
if self.instructorrelayname then
local relay=UNIT:FindByName(self.instructorrelayname)
if relay then
alive=tostring(relay:IsAlive())
end
end
text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)",self.instructorfreq,tostring(self.instructorrelayname),alive)
end
if self.rangecontrol then
local alive="N/A"
if self.rangecontrolrelayname then
local relay=UNIT:FindByName(self.rangecontrolrelayname)
if relay then
alive=tostring(relay:IsAlive())
end
end
text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)",self.rangecontrolfreq,tostring(self.rangecontrolrelayname),alive)
end
self:T(self.lid..text)
end
self:_CheckPlayers()
self:__Status(-10)
end
function RANGE:onafterEnterRange(From,Event,To,player)
if self.instructor and self.rangecontrol then
if self.useSRS then
local text=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz",self.rangecontrolfreq)
local ttstext=string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.",self.rangecontrolfreq)
local group=player.client:GetGroup()
self.instructsrsQ:NewTransmission(ttstext,nil,self.instructmsrs,nil,1,{group},text,10)
else
local RF=UTILS.Split(string.format("%.3f",self.rangecontrolfreq),".")
self.instructor:NewTransmission(self.Sound.IREnterRange.filename,self.Sound.IREnterRange.duration,self.soundpath)
self.instructor:Number2Transmission(RF[1])
if tonumber(RF[2])>0 then
self.instructor:NewTransmission(self.Sound.IRDecimal.filename,self.Sound.IRDecimal.duration,self.soundpath)
self.instructor:Number2Transmission(RF[2])
end
self.instructor:NewTransmission(self.Sound.IRMegaHertz.filename,self.Sound.IRMegaHertz.duration,self.soundpath)
end
end
end
function RANGE:onafterExitRange(From,Event,To,player)
if self.instructor then
if self.useSRS then
local text="You left the bombing range zone. "
local r=math.random(5)
if r==1 then
text=text.."Have a nice day!"
elseif r==2 then
text=text.."Take care and bye bye!"
elseif r==3 then
text=text.."Talk to you soon!"
elseif r==4 then
text=text.."See you in two weeks!"
elseif r==5 then
text=text.."!"
end
self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{player.client:GetGroup()},text,10)
else
self.instructor:NewTransmission(self.Sound.IRExitRange.filename,self.Sound.IRExitRange.duration,self.soundpath)
end
end
end
function RANGE:onafterImpact(From,Event,To,result,player)
local targetname=nil
if#self.bombingTargets>1 then
targetname=result.name
end
local text=string.format("%s, impact %03d° for %d ft (%d m)",player.playername,result.radial,UTILS.MetersToFeet(result.distance),result.distance)
if targetname then
text=text..string.format(" from bulls of target %s.",targetname)
else
text=text.."."
end
text=text..string.format(" %s hit.",result.quality)
if self.rangecontrol then
if self.useSRS then
local group=player.client:GetGroup()
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10)
else
self.rangecontrol:NewTransmission(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(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(self.Sound.RCFeet.filename,self.Sound.RCFeet.duration,self.soundpath)
if result.quality=="POOR"then
self.rangecontrol:NewTransmission(self.Sound.RCPoorHit.filename,self.Sound.RCPoorHit.duration,self.soundpath,nil,0.5)
elseif result.quality=="INEFFECTIVE"then
self.rangecontrol:NewTransmission(self.Sound.RCIneffectiveHit.filename,self.Sound.RCIneffectiveHit.duration,self.soundpath,nil,0.5)
elseif result.quality=="GOOD"then
self.rangecontrol:NewTransmission(self.Sound.RCGoodHit.filename,self.Sound.RCGoodHit.duration,self.soundpath,nil,0.5)
elseif result.quality=="EXCELLENT"then
self.rangecontrol:NewTransmission(self.Sound.RCExcellentHit.filename,self.Sound.RCExcellentHit.duration,self.soundpath,nil,0.5)
end
end
end
if player.unitname and not self.useSRS then
local unit=UNIT:FindByName(player.unitname)
self:_DisplayMessageToGroup(unit,text,nil,true)
self:T(self.lid..text)
end
if self.autosave then
self:Save()
end
if self.funkmanSocket then
self.funkmanSocket:SendTable(result)
end
end
function RANGE:onafterStrafeResult(From,Event,To,player,result)
if self.funkmanSocket then
self.funkmanSocket:SendTable(result)
end
end
function RANGE:onbeforeSave(From,Event,To)
if io and lfs then
return true
else
self:E(self.lid..string.format("WARNING: io and/or lfs not desanitized. Cannot save player results."))
return false
end
end
function RANGE:onafterSave(From,Event,To)
local function _savefile(filename,data)
local f=io.open(filename,"wb")
if f then
f:write(data)
f:close()
self: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=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
for i,_result in pairs(results)do
local result=_result
local distance=result.distance
local weapon=result.weapon
local target=result.name
local radial=result.radial
local quality=result.quality
local time=UTILS.SecondsToClock(result.time,true)
local airframe=result.airframe
local date=result.date or"n/a"
scores=scores..string.format("\n%s,%d,%s,%.2f,%03d,%s,%s,%s,%s,%s",playername,i,target,distance,radial,quality,weapon,airframe,time,date)
end
end
_savefile(filename,scores)
end
function RANGE:onbeforeLoad(From,Event,To)
if io and lfs then
return true
else
self:E(self.lid..string.format("WARNING: io and/or lfs not desanitized. Cannot load player results."))
return false
end
end
function RANGE:onafterLoad(From,Event,To)
local function _loadfile(filename)
local f=io.open(filename,"rb")
if f then
local data=f:read("*all")
f:close()
return data
else
self:E(self.lid..string.format("WARNING: Could not load player results from file %s. File might not exist just yet.",tostring(filename)))
return nil
end
end
local path=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:T(self.lid..text)
local data=_loadfile(filename)
if data then
local results=UTILS.Split(data,"\n")
table.remove(results,1)
self.bombPlayerResults={}
for _,_result in pairs(results)do
local resultdata=UTILS.Split(_result,",")
local result={}
local playername=resultdata[1]
result.player=playername
result.name=tostring(resultdata[3])
result.distance=tonumber(resultdata[4])
result.radial=tonumber(resultdata[5])
result.quality=tostring(resultdata[6])
result.weapon=tostring(resultdata[7])
result.airframe=tostring(resultdata[8])
result.time=UTILS.ClockToSeconds(resultdata[9]or"00:00:00")
result.date=resultdata[10]or"n/a"
self.bombPlayerResults[playername]=self.bombPlayerResults[playername]or{}
table.insert(self.bombPlayerResults[playername],result)
end
end
end
function RANGE:_SaveTargetSheet(_playername,result)
local function _savefile(filename,data)
local f=io.open(filename,"wb")
if f then
f:write(data)
f:close()
else
env.info("RANGEBOSS EDIT - could not save target sheet to file")
end
end
local path=self.targetpath
if lfs then
path=path or lfs.writedir()..[[Logs\]]
end
local filename=nil
for i=1,9999 do
if self.targetprefix then
filename=string.format("%s_%s-%04d.csv",self.targetprefix,result.airframe,i)
else
local name=UTILS.ReplaceIllegalCharacters(_playername,"_")
filename=string.format("RANGERESULTS-%s_Targetsheet-%s-%04d.csv",self.rangename,name,i)
end
if path~=nil then
filename=path.."\\"..filename
end
local _exists=UTILS.FileExists(filename)
if not _exists then
break
end
end
local data="Name,Target,Rounds Fired,Rounds Hit,Rounds Quality,Airframe,Mission Time,OS Time\n"
local target=result.name
local airframe=result.airframe
local roundsFired=result.roundsFired
local roundsHit=result.roundsHit
local strafeResult=result.roundsQuality
local time=UTILS.SecondsToClock(result.time)
local date="n/a"
if os then
date=os.date()
end
data=data..string.format("%s,%s,%d,%d,%s,%s,%s,%s",_playername,target,roundsFired,roundsHit,strafeResult,airframe,time,date)
_savefile(filename,data)
end
function RANGE:_DisplayMyStrafePitResults(_unitName)
self:F(_unitName)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _message=string.format("My Top %d Strafe Pit Results:\n",self.ndisplayresult)
local _results=self.strafePlayerResults[_playername]
if _results==nil then
_message=string.format("%s: No Score yet.",_playername)
else
local _sort=function(a,b)
return a.roundsHit>b.roundsHit
end
table.sort(_results,_sort)
local _bestMsg=""
local _count=1
for _,_result in pairs(_results)do
local result=_result
_message=_message..string.format("\n[%d] Hits %d - %s - %s",_count,result.roundsHit,result.name,result.roundsQuality)
if _bestMsg==""then
_bestMsg=string.format("Hits %d - %s - %s",result.roundsHit,result.name,result.roundsQuality)
end
if _count==self.ndisplayresult then
break
end
_count=_count+1
end
_message=_message.."\n\nBEST: ".._bestMsg
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayStrafePitResults(_unitName)
self:F(_unitName)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _playerResults={}
local _message=string.format("Strafe Pit Results - Top %d Players:\n",self.ndisplayresult)
for _playerName,_results in pairs(self.strafePlayerResults)do
local _best=nil
for _,_result in pairs(_results)do
if _best==nil or _result.roundsHit>_best.roundsHit then
_best=_result
end
end
if _best~=nil then
local text=string.format("%s: Hits %i - %s - %s",_playerName,_best.roundsHit,_best.name,_best.roundsQuality)
table.insert(_playerResults,{msg=text,hits=_best.roundsHit})
end
end
local _sort=function(a,b)
return a.hits>b.hits
end
table.sort(_playerResults,_sort)
for _i=1,math.min(#_playerResults,self.ndisplayresult)do
_message=_message..string.format("\n[%d] %s",_i,_playerResults[_i].msg)
end
if#_playerResults<1 then
_message=_message.."No player scored yet."
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayMyBombingResults(_unitName)
self:F(_unitName)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _message=string.format("My Top %d Bombing Results:\n",self.ndisplayresult)
local _results=self.bombPlayerResults[_playername]
if _results==nil then
_message=_playername..": No Score yet."
else
local _sort=function(a,b)
return a.distance<b.distance
end
table.sort(_results,_sort)
local _bestMsg=""
for i,_result in pairs(_results)do
local result=_result
_message=_message.."\n"..string.format("[%d] %d m %03d° - %s - %s - %s hit",i,result.distance,result.radial,result.name,result.weapon,result.quality)
if _bestMsg==""then
_bestMsg=string.format("%d m %03d° - %s - %s - %s hit",result.distance,result.radial,result.name,result.weapon,result.quality)
end
if i==self.ndisplayresult then
break
end
end
_message=_message.."\n\nBEST: ".._bestMsg
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayBombingResults(_unitName)
self:F(_unitName)
local _playerResults={}
local _unit,_player,_multiplayer=self:_GetPlayerUnitAndName(_unitName)
if _unit and _player then
local _message=string.format("Bombing Results - Top %d Players:\n",self.ndisplayresult)
for _playerName,_results in pairs(self.bombPlayerResults)do
local _best=nil
for _,_result in pairs(_results)do
if _best==nil or _result.distance<_best.distance then
_best=_result
end
end
if _best~=nil then
local bestres=string.format("%s: %d m - %s - %s - %s hit",_playerName,_best.distance,_best.name,_best.weapon,_best.quality)
table.insert(_playerResults,{msg=bestres,distance=_best.distance})
end
end
local _sort=function(a,b)
return a.distance<b.distance
end
table.sort(_playerResults,_sort)
for _i=1,math.min(#_playerResults,self.ndisplayresult)do
_message=_message..string.format("\n[%d] %s",_i,_playerResults[_i].msg)
end
if#_playerResults<1 then
_message=_message.."No player scored yet."
end
self:_DisplayMessageToGroup(_unit,_message,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayRangeInfo(_unitname)
self:F(_unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local text=""
local coord=unit:GetCoordinate()
if self.location then
local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS
local position=self.location
local bulls=position:ToStringBULLS(unit:GetCoalition(),settings)
local lldms=position:ToStringLLDMS(settings)
local llddm=position:ToStringLLDDM(settings)
local rangealt=position:GetLandHeight()
local vec3=coord:GetDirectionVec3(position)
local angle=coord:GetAngleDegrees(vec3)
local range=coord:Get2DDistance(position)
local Bs=string.format('%03d°',angle)
local texthit
if self.PlayerSettings[playername].flaredirecthits then
texthit=string.format("Flare direct hits: ON (flare color %s)\n",self:_flarecolor2text(self.PlayerSettings[playername].flarecolor))
else
texthit=string.format("Flare direct hits: OFF\n")
end
local textbomb
if self.PlayerSettings[playername].smokebombimpact then
textbomb=string.format("Smoke bomb impact points: ON (smoke color %s)\n",self:_smokecolor2text(self.PlayerSettings[playername].smokecolor))
else
textbomb=string.format("Smoke bomb impact points: OFF\n")
end
local textdelay
if self.PlayerSettings[playername].delaysmoke then
textdelay=string.format("Smoke bomb delay: ON (delay %.1f seconds)",self.TdelaySmoke)
else
textdelay=string.format("Smoke bomb delay: OFF")
end
local trange=string.format("%.1f km",range/1000)
local trangealt=string.format("%d m",rangealt)
local tstrafemaxalt=string.format("%d m",self.strafemaxalt)
if settings:IsImperial()then
trange=string.format("%.1f NM",UTILS.MetersToNM(range))
trangealt=string.format("%d feet",UTILS.MetersToFeet(rangealt))
tstrafemaxalt=string.format("%d feet",UTILS.MetersToFeet(self.strafemaxalt))
end
text=text..string.format("Information on %s:\n",self.rangename)
text=text..string.format("-------------------------------------------------------\n")
text=text..string.format("Bearing %s, Range %s\n",Bs,trange)
text=text..string.format("%s\n",bulls)
text=text..string.format("%s\n",lldms)
text=text..string.format("%s\n",llddm)
text=text..string.format("Altitude ASL: %s\n",trangealt)
text=text..string.format("Max strafing alt AGL: %s\n",tstrafemaxalt)
text=text..string.format("# of strafe targets: %d\n",self.nstrafetargets)
text=text..string.format("# of bomb targets: %d\n",self.nbombtargets)
if self.instructor then
local alive="N/A"
if self.instructorrelayname then
local relay=UNIT:FindByName(self.instructorrelayname)
if relay then
alive=relay:IsAlive()and"ok"or"N/A"
end
end
text=text..string.format("Instructor %.3f MHz (Relay=%s)\n",self.instructorfreq,alive)
end
if self.rangecontrol then
local alive="N/A"
if self.rangecontrolrelayname then
local relay=UNIT:FindByName(self.rangecontrolrelayname)
if relay then
alive=tostring(relay:IsAlive())
alive=relay:IsAlive()and"ok"or"N/A"
end
end
text=text..string.format("Control %.3f MHz (Relay=%s)\n",self.rangecontrolfreq,alive)
end
text=text..texthit
text=text..textbomb
text=text..textdelay
self:_DisplayMessageToGroup(unit,text,nil,true,true,_multiplayer)
self:T2(self.lid..text)
end
end
end
function RANGE:_DisplayBombTargets(_unitname)
self:F(_unitname)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if _unit and _playername then
local _settings=_DATABASE:GetPlayerSettings(_playername)or _SETTINGS
local _text="Bomb Target Locations:"
for _,_bombtarget in pairs(self.bombingTargets)do
local bombtarget=_bombtarget
local coord=self:_GetBombTargetCoordinate(bombtarget)
if coord then
local elevation=coord:GetLandHeight()
local eltxt=string.format("%d m",elevation)
if not _settings:IsMetric()then
elevation=UTILS.MetersToFeet(elevation)
eltxt=string.format("%d ft",elevation)
end
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,150,true,true,_multiplayer)
end
end
function RANGE:_DisplayStrafePits(_unitname)
self:F(_unitname)
local _unit,_playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if _unit and _playername then
local _settings=_DATABASE:GetPlayerSettings(_playername)or _SETTINGS
local _text="Strafe Target Locations:"
for _,_strafepit in pairs(self.strafeTargets)do
local _target=_strafepit
local coord=_strafepit.coordinate
local heading=_strafepit.heading
if heading>180 then
heading=heading-180
else
heading=heading+180
end
local mycoord=coord:ToStringA2G(_unit,_settings)
_text=_text..string.format("\n- %s: heading %03d°\n%s",_strafepit.name,heading,mycoord)
end
self:_DisplayMessageToGroup(_unit,_text,nil,true,true,_multiplayer)
end
end
function RANGE:_DisplayRangeWeather(_unitname)
self:F(_unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local text=""
local coord=unit:GetCoordinate()
if self.location then
local position=self.location
local T=position:GetTemperature()
local P=position:GetPressure()
local Wd,Ws=position:GetWind()
local Bn,Bd=UTILS.BeaufortScale(Ws)
local WD=string.format('%03d°',Wd)
local Ts=string.format("%d°C",T)
local hPa2inHg=0.0295299830714
local hPa2mmHg=0.7500615613030
local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS
local tT=string.format("%d°C",T)
local tW=string.format("%.1f m/s",Ws)
local tP=string.format("%.1f mmHg",P*hPa2mmHg)
if settings:IsImperial()then
tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws))
tP=string.format("%.2f inHg",P*hPa2inHg)
end
text=text..string.format("Weather Report at %s:\n",self.rangename)
text=text..string.format("--------------------------------------------------\n")
text=text..string.format("Temperature %s\n",tT)
text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd)
text=text..string.format("QFE %.1f hPa = %s",P,tP)
else
text=string.format("No range location defined for range %s.",self.rangename)
end
self:_DisplayMessageToGroup(unit,text,nil,true,true,_multiplayer)
self:T2(self.lid..text)
else
self:T(self.lid..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s",_unitname))
end
end
function RANGE:_CheckPlayers()
for playername,_playersettings in pairs(self.PlayerSettings)do
local playersettings=_playersettings
local unitname=playersettings.unitname
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive()then
local unitalt=unit:GetAltitude(false)
local unitaltinfeet=UTILS.MetersToFeet(unitalt)
if unit:IsInZone(self.rangezone)and(not self.ceilingenabled or unitaltinfeet<self.ceilingaltitude)then
if not playersettings.inzone then
playersettings.inzone=true
self:EnterRange(playersettings)
end
else
if playersettings.inzone==true then
playersettings.inzone=false
self:ExitRange(playersettings)
end
end
end
end
end
function RANGE:_CheckInZone(_unitName)
self:F2(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local unitheading=0
if _unit and _playername then
local playerData=self.PlayerSettings[_playername]
local function checkme(targetheading,_zone)
local zone=_zone
local unitheading=_unit:GetHeading()
local pitheading=targetheading-180
local deltaheading=unitheading-pitheading
local towardspit=math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90
if towardspit then
local vec3=_unit:GetVec3()
local vec2={x=vec3.x,y=vec3.z}
local landheight=land.getHeight(vec2)
local unitalt=vec3.y-landheight
if unitalt<=self.strafemaxalt then
local unitinzone=zone:IsVec2InZone(vec2)
return unitinzone
end
end
return false
end
local _unitID=_unit:GetID()
local _currentStrafeRun=self.strafeStatus[_unitID]
if _currentStrafeRun then
local zone=_currentStrafeRun.zone.polygon
local unitinzone=checkme(_currentStrafeRun.zone.heading,zone)
if unitinzone then
_currentStrafeRun.time=_currentStrafeRun.time+1
else
_currentStrafeRun.time=_currentStrafeRun.time+1
if _currentStrafeRun.time<=3 then
self.strafeStatus[_unitID]=nil
local _msg=string.format("%s left strafing zone %s too quickly. No Score.",_playername,_currentStrafeRun.zone.name)
self:_DisplayMessageToGroup(_unit,_msg,nil,true)
if self.rangecontrol then
if self.useSRS then
local group=_unit:GetGroup()
local text="You left the strafing zone too quickly! No score!"
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(self.Sound.RCLeftStrafePitTooQuickly.filename,self.Sound.RCLeftStrafePitTooQuickly.duration,self.soundpath)
end
end
else
local _ammo=self:_GetAmmo(_unitName)
local _result=self.strafeStatus[_unitID]
local _sound=nil
local shots=_result.ammo-_ammo
local accur=0
if shots>0 then
accur=_result.hits/shots*100
if accur>100 then
accur=100
end
end
local resulttext=""
if _result.pastfoulline==true then
resulttext="* INVALID - PASSED FOUL LINE *"
_sound=self.Sound.RCPoorPass
else
if accur>=90 then
resulttext="DEADEYE PASS"
_sound=self.Sound.RCExcellentPass
elseif accur>=75 then
resulttext="EXCELLENT PASS"
_sound=self.Sound.RCExcellentPass
elseif accur>=50 then
resulttext="GOOD PASS"
_sound=self.Sound.RCGoodPass
elseif accur>=25 then
resulttext="INEFFECTIVE PASS"
_sound=self.Sound.RCIneffectivePass
else
resulttext="POOR PASS"
_sound=self.Sound.RCPoorPass
end
end
local _text=string.format("%s, hits on target %s: %d",self:_myname(_unitName),_result.zone.name,_result.hits)
local ttstext=string.format("%s, hits on target %s: %d.",self:_myname(_unitName),_result.zone.name,_result.hits)
if shots and accur then
_text=_text..string.format("\nTotal rounds fired %d. Accuracy %.1f %%.",shots,accur)
ttstext=ttstext..string.format(". Total rounds fired %d. Accuracy %.1f percent.",shots,accur)
end
_text=_text..string.format("\n%s",resulttext)
ttstext=ttstext..string.format(" %s",resulttext)
self:_DisplayMessageToGroup(_unit,_text)
local result={}
result.command=SOCKET.DataType.STRAFERESULT
result.player=_playername
result.name=_result.zone.name or"unknown"
result.time=timer.getAbsTime()
result.clock=UTILS.SecondsToClock(result.time)
result.midate=UTILS.GetDCSMissionDate()
result.theatre=env.mission.theatre
result.roundsFired=shots
result.roundsHit=_result.hits
result.roundsQuality=resulttext
result.strafeAccuracy=accur
result.rangename=self.rangename
result.airframe=playerData.airframe
result.invalid=_result.pastfoulline
self:StrafeResult(playerData,result)
if playerData and playerData.targeton and self.targetsheet then
self:_SaveTargetSheet(_playername,result)
end
if self.rangecontrol then
if self.useSRS then
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(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(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(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(self.Sound.RCPercent.filename,self.Sound.RCPercent.duration,self.soundpath)
end
self.rangecontrol:NewTransmission(_sound.filename,_sound.duration,self.soundpath,nil,0.5)
end
end
self.strafeStatus[_unitID]=nil
local _stats=self.strafePlayerResults[_playername]or{}
table.insert(_stats,result)
self.strafePlayerResults[_playername]=_stats
end
end
else
for _,_targetZone in pairs(self.strafeTargets)do
local target=_targetZone
local zone=target.polygon
local unitinzone=checkme(target.heading,zone)
if unitinzone then
local _ammo=self:_GetAmmo(_unitName)
self.strafeStatus[_unitID]={hits=0,zone=target,time=1,ammo=_ammo,pastfoulline=false}
local _msg=string.format("%s, rolling in on strafe pit %s.",self:_myname(_unitName),target.name)
if self.rangecontrol then
if self.useSRS then
self.controlsrsQ:NewTransmission(_msg,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission(self.Sound.RCRollingInOnStrafeTarget.filename,self.Sound.RCRollingInOnStrafeTarget.duration,self.soundpath)
end
end
self:_DisplayMessageToGroup(_unit,_msg,10,true)
self:RollingIn(playerData,target)
break
end
end
end
end
end
function RANGE:_AddF10Commands(_unitName)
self:F(_unitName)
local _unit,playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and playername then
local group=_unit:GetGroup()
local _gid=group:GetID()
if group and _gid then
if not self.MenuAddedTo[_gid]then
self.MenuAddedTo[_gid]=true
local _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
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
_rootMenu=RANGE.MenuF10[_gid]or MENU_GROUP:New(group,"On the Range")
end
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)
local _IllRng=MENU_GROUP_COMMAND:New(group,"Illuminate Range",_markPath,self._IlluminateBombTargets,self,_unitName)
local _SSpit=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Pits",_markPath,self._SmokeStrafeTargetBoxes,self,_unitName)
local _SStgts=MENU_GROUP_COMMAND:New(group,"Smoke Strafe Tgts",_markPath,self._SmokeStrafeTargets,self,_unitName)
local _SBtgts=MENU_GROUP_COMMAND:New(group,"Smoke Bomb Tgts",_markPath,self._SmokeBombTargets,self,_unitName)
local _AllSR=MENU_GROUP_COMMAND:New(group,"All Strafe Results",_statsPath,self._DisplayStrafePitResults,self,_unitName)
local _AllBR=MENU_GROUP_COMMAND:New(group,"All Bombing Results",_statsPath,self._DisplayBombingResults,self,_unitName)
local _MySR=MENU_GROUP_COMMAND:New(group,"My Strafe Results",_statsPath,self._DisplayMyStrafePitResults,self,_unitName)
local _MyBR=MENU_GROUP_COMMAND:New(group,"My Bomb Results",_statsPath,self._DisplayMyBombingResults,self,_unitName)
local _ResetST=MENU_GROUP_COMMAND:New(group,"Reset All Stats",_statsPath,self._ResetRangeStats,self,_unitName)
local _BlueSM=MENU_GROUP_COMMAND:New(group,"Blue Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Blue)
local _GrSM=MENU_GROUP_COMMAND:New(group,"Green Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Green)
local _OrSM=MENU_GROUP_COMMAND:New(group,"Orange Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Orange)
local _ReSM=MENU_GROUP_COMMAND:New(group,"Red Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Red)
local _WhSm=MENU_GROUP_COMMAND:New(group,"White Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.White)
local _GrFl=MENU_GROUP_COMMAND:New(group,"Green Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Green)
local _ReFl=MENU_GROUP_COMMAND:New(group,"Red Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Red)
local _WhFl=MENU_GROUP_COMMAND:New(group,"White Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.White)
local _YeFl=MENU_GROUP_COMMAND:New(group,"Yellow Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Yellow)
local _SmDe=MENU_GROUP_COMMAND:New(group,"Smoke Delay On/Off",_settingsPath,self._SmokeBombDelayOnOff,self,_unitName)
local _SmIm=MENU_GROUP_COMMAND:New(group,"Smoke Impact On/Off",_settingsPath,self._SmokeBombImpactOnOff,self,_unitName)
local _FlHi=MENU_GROUP_COMMAND:New(group,"Flare Hits On/Off",_settingsPath,self._FlareDirectHitsOnOff,self,_unitName)
local _AlMeA=MENU_GROUP_COMMAND:New(group,"All Messages On/Off",_settingsPath,self._MessagesToPlayerOnOff,self,_unitName)
local _TrpSh=MENU_GROUP_COMMAND:New(group,"Targetsheet On/Off",_settingsPath,self._TargetsheetOnOff,self,_unitName)
local _WeIn=MENU_GROUP_COMMAND:New(group,"General Info",_infoPath,self._DisplayRangeInfo,self,_unitName)
local _WeRe=MENU_GROUP_COMMAND:New(group,"Weather Report",_infoPath,self._DisplayRangeWeather,self,_unitName)
local _BoTgtgs=MENU_GROUP_COMMAND:New(group,"Bombing Targets",_infoPath,self._DisplayBombTargets,self,_unitName)
local _StrPits=MENU_GROUP_COMMAND:New(group,"Strafe Pits",_infoPath,self._DisplayStrafePits,self,_unitName):Refresh()
end
else
self:E(self.lid.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName or"N/A")
end
else
self:E(self.lid.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName or"N/A")
end
end
function RANGE:_GetBombTargetCoordinate(target)
local coord=nil
if target.type==RANGE.TargetType.UNIT then
if target.target and target.target:IsAlive()then
coord=target.target:GetCoordinate()
target.coordinate=coord
else
coord=target.coordinate
end
elseif target.type==RANGE.TargetType.STATIC then
coord=target.coordinate
elseif target.type==RANGE.TargetType.COORD then
coord=target.coordinate
elseif target.type==RANGE.TargetType.SCENERY then
coord=target.coordinate
else
self:E(self.lid.."ERROR: Unknown target type.")
end
return coord
end
function RANGE:_GetAmmo(unitname)
self:F2(unitname)
local ammo=0
local unit,playername=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local has_ammo=false
local ammotable=unit:GetAmmo()
self:T2({ammotable=ammotable})
if ammotable~=nil then
local weapons=#ammotable
self:T2(self.lid..string.format("Number of weapons %d.",weapons))
for w=1,weapons do
local Nammo=ammotable[w]["count"]
local Tammo=ammotable[w]["desc"]["typeName"]
if string.match(Tammo,"shell")then
ammo=ammo+Nammo
local text=string.format("Player %s has %d rounds ammo of type %s",playername,Nammo,Tammo)
self:T(self.lid..text)
else
local text=string.format("Player %s has %d ammo of type %s",playername,Nammo,Tammo)
self:T(self.lid..text)
end
end
end
end
return ammo
end
function RANGE:_MarkTargetsOnMap(_unitName)
self:F(_unitName)
local group=nil
if _unitName then
group=UNIT:FindByName(_unitName):GetGroup()
end
for _,_bombtarget in pairs(self.bombingTargets)do
local bombtarget=_bombtarget
local coord=self:_GetBombTargetCoordinate(_bombtarget)
if group then
coord:MarkToGroup(string.format("Bomb target %s:\n%s\n%s",bombtarget.name,coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group)
else
coord:MarkToAll(string.format("Bomb target %s",bombtarget.name))
end
end
for _,_strafepit in pairs(self.strafeTargets)do
for _,_target in pairs(_strafepit.targets)do
local _target=_target
if _target and _target:IsAlive()then
local coord=_target:GetCoordinate()
if group then
coord:MarkToGroup(string.format("Strafe target %s:\n%s\n%s",_target:GetName(),coord:ToStringLLDMS(),coord:ToStringBULLS(group:GetCoalition())),group)
else
coord:MarkToAll("Strafe target ".._target:GetName())
end
end
end
end
if _unitName then
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local text=string.format("%s, %s, range targets are now marked on F10 map.",self.rangename,_playername)
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_IlluminateBombTargets(_unitName)
self:F(_unitName)
local bomb={}
for _,_bombtarget in pairs(self.bombingTargets)do
local _target=_bombtarget.target
local coord=self:_GetBombTargetCoordinate(_bombtarget)
if coord then
table.insert(bomb,coord)
end
end
if#bomb>0 then
local coord=bomb[math.random(#bomb)]
local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z)
c:IlluminationBomb()
end
local strafe={}
for _,_strafepit in pairs(self.strafeTargets)do
for _,_target in pairs(_strafepit.targets)do
local _target=_target
if _target and _target:IsAlive()then
local coord=_target:GetCoordinate()
table.insert(strafe,coord)
end
end
end
if#strafe>0 then
local coord=strafe[math.random(#strafe)]
local c=COORDINATE:New(coord.x,coord.y+math.random(self.illuminationminalt,self.illuminationmaxalt),coord.z)
c:IlluminationBomb()
end
if _unitName then
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local text=string.format("%s, %s, range targets are illuminated.",self.rangename,_playername)
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_ResetRangeStats(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self.strafePlayerResults[_playername]=nil
self.bombPlayerResults[_playername]=nil
local text=string.format("%s, %s, your range stats were cleared.",self.rangename,_playername)
self:DisplayMessageToGroup(_unit,text,5,false,true)
end
end
function RANGE:_DisplayMessageToGroup(_unit,_text,_time,_clear,display,_togroup)
self:F({unit=_unit,text=_text,time=_time,clear=_clear})
_time=_time or self.Tmsg
if _clear==nil or _clear==false then
_clear=false
else
_clear=true
end
if self.messages==false then
return
end
if _unit and _unit:IsAlive()then
local _gid=_unit:GetGroup():GetID()
local _grp=_unit:GetGroup()
local _,playername=self:_GetPlayerUnitAndName(_unit:GetName())
local playermessage=self.PlayerSettings[playername].messages
if _gid and(playermessage==true or display)and(not self.examinerexclusive)then
if _togroup and _grp then
local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp)
else
local m=MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit)
end
end
if self.examinergroupname~=nil then
local _examinerid=GROUP:FindByName(self.examinergroupname)
if _examinerid then
local m=MESSAGE:New(_text,_time,nil,_clear):ToGroup(_examinerid)
end
end
end
end
function RANGE:_SmokeBombImpactOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].smokebombimpact==true then
self.PlayerSettings[playername].smokebombimpact=false
text=string.format("%s, %s, smoking impact points of bombs is now OFF.",self.rangename,playername)
else
self.PlayerSettings[playername].smokebombimpact=true
text=string.format("%s, %s, smoking impact points of bombs is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
end
end
function RANGE:_SmokeBombDelayOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].delaysmoke==true then
self.PlayerSettings[playername].delaysmoke=false
text=string.format("%s, %s, delayed smoke of bombs is now OFF.",self.rangename,playername)
else
self.PlayerSettings[playername].delaysmoke=true
text=string.format("%s, %s, delayed smoke of bombs is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
end
end
function RANGE:_MessagesToPlayerOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].messages==true then
text=string.format("%s, %s, display of ALL messages is now OFF.",self.rangename,playername)
else
text=string.format("%s, %s, display of ALL messages is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
self.PlayerSettings[playername].messages=not self.PlayerSettings[playername].messages
end
end
function RANGE:_TargetsheetOnOff(_unitname)
self:F2(_unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.PlayerSettings[playername]
if playerData then
local text=""
if self.targetsheet then
playerData.targeton=not playerData.targeton
if playerData and playerData.targeton==true then
text=string.format("Roger, your targetsheets are now SAVED.")
else
text=string.format("Affirm, your targetsheets are NOT SAVED.")
end
else
text="Negative, target sheet data recorder is broken on this range."
end
self:_DisplayMessageToGroup(unit,text,5,false,false)
end
end
end
function RANGE:_FlareDirectHitsOnOff(unitname)
self:F(unitname)
local unit,playername,_multiplayer=self:_GetPlayerUnitAndName(unitname)
if unit and playername then
local text
if self.PlayerSettings[playername].flaredirecthits==true then
self.PlayerSettings[playername].flaredirecthits=false
text=string.format("%s, %s, flaring direct hits is now OFF.",self.rangename,playername)
else
self.PlayerSettings[playername].flaredirecthits=true
text=string.format("%s, %s, flaring direct hits is now ON.",self.rangename,playername)
end
self:_DisplayMessageToGroup(unit,text,5,false,true)
end
end
function RANGE:_SmokeBombTargets(unitname)
self:F(unitname)
for _,_bombtarget in pairs(self.bombingTargets)do
local _target=_bombtarget.target
local coord=self:_GetBombTargetCoordinate(_bombtarget)
if coord then
coord:Smoke(self.BombSmokeColor)
end
end
if unitname then
local unit,playername=self:_GetPlayerUnitAndName(unitname)
local text=string.format("%s, %s, bombing targets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.BombSmokeColor))
self:_DisplayMessageToGroup(unit,text,5)
end
end
function RANGE:_SmokeStrafeTargets(unitname)
self:F(unitname)
for _,_target in pairs(self.strafeTargets)do
_target.coordinate:Smoke(self.StrafeSmokeColor)
end
if unitname then
local unit,playername=self:_GetPlayerUnitAndName(unitname)
local text=string.format("%s, %s, strafing tragets are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafeSmokeColor))
self:_DisplayMessageToGroup(unit,text,5)
end
end
function RANGE:_SmokeStrafeTargetBoxes(unitname)
self:F(unitname)
for _,_target in pairs(self.strafeTargets)do
local zone=_target.polygon
zone:SmokeZone(self.StrafePitSmokeColor,4)
for _,_point in pairs(_target.smokepoints)do
_point:SmokeOrange()
end
end
if unitname then
local unit,playername=self:_GetPlayerUnitAndName(unitname)
local text=string.format("%s, %s, strafing pit approach boxes are now marked with %s smoke.",self.rangename,playername,self:_smokecolor2text(self.StrafePitSmokeColor))
self:_DisplayMessageToGroup(unit,text,5)
end
end
function RANGE:_playersmokecolor(_unitName,color)
self:F({unitname=_unitName,color=color})
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self.PlayerSettings[_playername].smokecolor=color
local text=string.format("%s, %s, your bomb impacts are now smoked in %s.",self.rangename,_playername,self:_smokecolor2text(color))
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_playerflarecolor(_unitName,color)
self:F({unitname=_unitName,color=color})
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
self.PlayerSettings[_playername].flarecolor=color
local text=string.format("%s, %s, your direct hits are now flared in %s.",self.rangename,_playername,self:_flarecolor2text(color))
self:_DisplayMessageToGroup(_unit,text,5)
end
end
function RANGE:_smokecolor2text(color)
self:F(color)
local txt=""
if color==SMOKECOLOR.Blue then
txt="blue"
elseif color==SMOKECOLOR.Green then
txt="green"
elseif color==SMOKECOLOR.Orange then
txt="orange"
elseif color==SMOKECOLOR.Red then
txt="red"
elseif color==SMOKECOLOR.White then
txt="white"
else
txt=string.format("unknown color (%s)",tostring(color))
end
return txt
end
function RANGE:_flarecolor2text(color)
self:F(color)
local txt=""
if color==FLARECOLOR.Green then
txt="green"
elseif color==FLARECOLOR.Red then
txt="red"
elseif color==FLARECOLOR.White then
txt="white"
elseif color==FLARECOLOR.Yellow then
txt="yellow"
else
txt=string.format("unknown color (%s)",tostring(color))
end
return txt
end
function RANGE:_CheckStatic(name)
self:F2(name)
local _DCSstatic=StaticObject.getByName(name)
if _DCSstatic and _DCSstatic:isExist()then
local _MOOSEstatic=STATIC:FindByName(name,false)
if not _MOOSEstatic then
self:T(self.lid..string.format("Adding DCS static to MOOSE database. Name = %s.",name))
_DATABASE:AddStatic(name)
end
return true
else
self:T3(self.lid..string.format("No static object with name %s exists.",name))
end
if UNIT:FindByName(name)then
return false
else
self:T3(self.lid..string.format("No unit object with name %s exists.",name))
end
return nil
end
function RANGE:_GetSpeed(controllable)
self:F2(controllable)
local desc=controllable:GetDesc()
local speed=0
if desc then
speed=desc.speedMax*3.6
self:T({speed=speed})
end
return speed
end
function RANGE:_GetPlayerUnitAndName(_unitName,PlayerName)
if _unitName~=nil then
local multiplayer=false
local DCSunit=Unit.getByName(_unitName)
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
self:F2(playername)
local grp=unit:GetGroup()
if grp and grp:CountAliveUnits()>1 then
multiplayer=true
end
return unit,playername,multiplayer
end
end
end
return nil,nil,nil
end
function RANGE:_myname(unitname)
self:F2(unitname)
local pname="Ghost 1 1"
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive()then
local grp=unit:GetGroup()
if grp and grp:IsAlive()then
pname=grp:GetCustomCallSign(true,true)
end
end
return pname
end
do
ZONE_GOAL={
ClassName="ZONE_GOAL",
Goal=nil,
SmokeTime=nil,
SmokeScheduler=nil,
SmokeColor=nil,
SmokeZone=nil,
}
function ZONE_GOAL:New(Zone)
BASE:I({Zone=Zone})
local self=BASE:Inherit(self,BASE:New())
if type(Zone)=="string"then
self=BASE:Inherit(self,ZONE_POLYGON:NewFromGroupName(Zone))
else
self=BASE:Inherit(self,ZONE_RADIUS:New(Zone:GetName(),Zone:GetVec2(),Zone:GetRadius()))
self:F({Zone=Zone})
end
self.Goal=GOAL:New()
self.SmokeTime=nil
self:SetSmokeZone(true)
self:AddTransition("*","DestroyedUnit","*")
return self
end
function ZONE_GOAL:GetZone()
return self
end
function ZONE_GOAL:GetZoneName()
return self:GetName()
end
function ZONE_GOAL:SetSmokeZone(switch)
self.SmokeZone=switch
return self
end
function ZONE_GOAL:Smoke(SmokeColor)
self:F({SmokeColor=SmokeColor})
self.SmokeColor=SmokeColor
end
function ZONE_GOAL:Flare(FlareColor)
self:FlareZone(FlareColor,30)
end
function ZONE_GOAL:onafterGuard()
self:F("Guard")
if self.SmokeZone and not self.SmokeScheduler then
self.SmokeScheduler=self:ScheduleRepeat(1,1,0.1,nil,self.StatusSmoke,self)
end
end
function ZONE_GOAL:StatusSmoke()
self:F({self.SmokeTime,self.SmokeColor})
if self.SmokeZone then
local CurrentTime=timer.getTime()
if self.SmokeTime==nil or self.SmokeTime+300<=CurrentTime then
if self.SmokeColor then
self:GetCoordinate():Smoke(self.SmokeColor)
self.SmokeTime=CurrentTime
end
end
end
end
function ZONE_GOAL:__Destroyed(EventData)
self:F({"EventDead",EventData})
self:F({EventData.IniUnit})
if EventData.IniDCSUnit then
local Vec3=EventData.IniDCSUnit:getPosition().p
self:F({Vec3=Vec3})
if Vec3 and self:IsVec3InZone(Vec3)then
local PlayerHits=_DATABASE.HITS[EventData.IniUnitName]
if PlayerHits then
for PlayerName,PlayerHit in pairs(PlayerHits.Players or{})do
self.Goal:AddPlayerContribution(PlayerName)
self:DestroyedUnit(EventData.IniUnitName,PlayerName)
end
end
end
end
end
function ZONE_GOAL:MonitorDestroyedUnits()
self:HandleEvent(EVENTS.Dead,self.__Destroyed)
self:HandleEvent(EVENTS.Crash,self.__Destroyed)
end
end
do
ZONE_GOAL_COALITION={
ClassName="ZONE_GOAL_COALITION",
Coalition=nil,
PreviousCoalition=nil,
UnitCategories=nil,
ObjectCategories=nil,
}
ZONE_GOAL_COALITION.States={}
function ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories)
if not Zone then
BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITION!")
return nil
end
local self=BASE:Inherit(self,ZONE_GOAL:New(Zone))
self:F({Zone=Zone,Coalition=Coalition})
self:SetCoalition(Coalition or coalition.side.NEUTRAL)
self:SetUnitCategories(UnitCategories)
self:SetObjectCategories()
return self
end
function ZONE_GOAL_COALITION:SetCoalition(Coalition)
self.PreviousCoalition=self.Coalition or Coalition
self.Coalition=Coalition
return self
end
function ZONE_GOAL_COALITION:SetUnitCategories(UnitCategories)
if UnitCategories and type(UnitCategories)~="table"then
UnitCategories={UnitCategories}
end
self.UnitCategories=UnitCategories or{Unit.Category.GROUND_UNIT}
return self
end
function ZONE_GOAL_COALITION:SetObjectCategories(ObjectCategories)
if ObjectCategories and type(ObjectCategories)~="table"then
ObjectCategories={ObjectCategories}
end
self.ObjectCategories=ObjectCategories or{Object.Category.UNIT,Object.Category.STATIC}
return self
end
function ZONE_GOAL_COALITION:GetCoalition()
return self.Coalition
end
function ZONE_GOAL_COALITION:GetPreviousCoalition()
return self.PreviousCoalition
end
function ZONE_GOAL_COALITION:GetCoalitionName()
return UTILS.GetCoalitionName(self.Coalition)
end
function ZONE_GOAL_COALITION:StatusZone()
local State=self:GetState()
local text=string.format("Zone state=%s, Owner=%s, Scanning...",State,self:GetCoalitionName())
self:F(text)
self:Scan(self.ObjectCategories,self.UnitCategories)
return self
end
end
do
ZONE_CAPTURE_COALITION={
ClassName="ZONE_CAPTURE_COALITION",
MarkBlue=nil,
MarkRed=nil,
StartInterval=nil,
RepeatInterval=nil,
HitsOn=nil,
HitTimeLast=nil,
HitTimeAttackOver=nil,
MarkOn=nil,
}
function ZONE_CAPTURE_COALITION:New(Zone,Coalition,UnitCategories,ObjectCategories)
local self=BASE:Inherit(self,ZONE_GOAL_COALITION:New(Zone,Coalition,UnitCategories))
self:F({Zone=Zone,Coalition=Coalition,UnitCategories=UnitCategories,ObjectCategories=ObjectCategories})
self:SetObjectCategories(ObjectCategories)
self:SetSmokeZone(false)
self:SetMarkZone(true)
self:SetStartState("Empty")
do
end
do
end
do
end
do
end
self:AddTransition("*","Guard","Guarded")
self:AddTransition("*","Empty","Empty")
self:AddTransition({"Guarded","Empty"},"Attack","Attacked")
self:AddTransition({"Guarded","Attacked","Empty"},"Capture","Captured")
_EVENTDISPATCHER:CreateEventNewZoneGoal(self)
return self
end
function ZONE_CAPTURE_COALITION:Start(StartInterval,RepeatInterval)
self.StartInterval=StartInterval or 1
self.RepeatInterval=RepeatInterval or 15
if self.ScheduleStatusZone then
self:ScheduleStop(self.ScheduleStatusZone)
end
self.ScheduleStatusZone=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusZone,self)
self:HandleEvent(EVENTS.Hit,self.OnEventHit)
self:Mark()
return self
end
function ZONE_CAPTURE_COALITION:Stop()
if self.ScheduleStatusZone then
self:ScheduleStop(self.ScheduleStatusZone)
end
if self.SmokeScheduler then
self:ScheduleStop(self.SmokeScheduler)
end
self:UnHandleEvent(EVENTS.Hit)
end
function ZONE_CAPTURE_COALITION:SetMonitorHits(Switch,TimeAttackOver)
self.HitsOn=Switch
self.HitTimeAttackOver=TimeAttackOver or 5*60
return self
end
function ZONE_CAPTURE_COALITION:SetMarkZone(Switch)
if Switch==nil or Switch==true then
self.MarkOn=true
else
self.MarkOn=false
end
return self
end
function ZONE_CAPTURE_COALITION:OnEventHit(EventData)
if self.HitsOn then
local UnitHit=EventData.TgtUnit
if UnitHit and UnitHit.ClassName~="SCENERY"then
if UnitHit and UnitHit:IsInZone(self)and UnitHit:GetCoalition()==self.Coalition then
self.HitTimeLast=timer.getTime()
if self:GetState()~="Attacked"then
self:F2("Hit ==> Attack")
self:Attack()
end
end
end
end
end
function ZONE_CAPTURE_COALITION:onafterGuard()
self:F2("After Guard")
if self.SmokeZone and not self.SmokeScheduler then
self.SmokeScheduler=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusSmoke,self)
end
end
function ZONE_CAPTURE_COALITION:onenterGuarded()
self:F2("Enter Guarded")
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterCaptured()
self:F2("Enter Captured")
local NewCoalition=self:GetScannedCoalition()
self:F({NewCoalition=NewCoalition})
self:SetCoalition(NewCoalition)
self:Mark()
self.Goal:Achieved()
end
function ZONE_CAPTURE_COALITION:onenterEmpty()
self:F2("Enter Empty")
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterAttacked()
self:F2("Enter Attacked")
self:Mark()
end
function ZONE_CAPTURE_COALITION:IsEmpty()
local IsEmpty=self:IsNoneInZone()
self:F({IsEmpty=IsEmpty})
return IsEmpty
end
function ZONE_CAPTURE_COALITION:IsGuarded()
local IsGuarded=self:IsAllInZoneOfCoalition(self.Coalition)
self:F({IsGuarded=IsGuarded})
return IsGuarded
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured=self:IsAllInZoneOfOtherCoalition(self.Coalition)
self:F({IsCaptured=IsCaptured})
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked=self:IsSomeInZoneOfCoalition(self.Coalition)
self:F({IsAttacked=IsAttacked})
return IsAttacked
end
function ZONE_CAPTURE_COALITION:StatusZone()
local State=self:GetState()
self:GetParent(self,ZONE_CAPTURE_COALITION).StatusZone(self)
local Tnow=timer.getTime()
if State~="Guarded"and self:IsGuarded()then
if self.HitTimeLast==nil or Tnow>=self.HitTimeLast+self.HitTimeAttackOver then
self:Guard()
self.HitTimeLast=nil
end
end
if State~="Empty"and self:IsEmpty()then
self:Empty()
end
if State~="Attacked"and self:IsAttacked()then
self:Attack()
end
if State~="Captured"and self:IsCaptured()then
self:Capture()
end
local unitset=self:GetScannedSetUnit()
local nRed=0
local nBlue=0
for _,object in pairs(unitset:GetSet())do
local coal=object:GetCoalition()
if object:IsAlive()then
if coal==coalition.side.RED then
nRed=nRed+1
elseif coal==coalition.side.BLUE then
nBlue=nBlue+1
end
end
end
if false then
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s",self:GetZoneName(),self:GetCoalitionName(),UTILS.GetCoalitionName(self:GetPreviousCoalition()),nBlue,nRed,State)
local NewState=self:GetState()
if NewState~=State then
text=text..string.format(" --> %s",NewState)
end
self:I(text)
end
end
function ZONE_CAPTURE_COALITION:Mark()
if self.MarkOn then
local Coord=self:GetCoordinate()
local ZoneName=self:GetZoneName()
local State=self:GetState()
if self.MarkRed then
Coord:RemoveMark(self.MarkRed)
end
if self.MarkBlue then
Coord:RemoveMark(self.MarkBlue)
end
if self.Coalition==coalition.side.BLUE then
self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Blue\nGuard Zone: "..ZoneName.."\nStatus: "..State)
self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Blue\nCapture Zone: "..ZoneName.."\nStatus: "..State)
elseif self.Coalition==coalition.side.RED then
self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Red\nGuard Zone: "..ZoneName.."\nStatus: "..State)
self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Red\nCapture Zone: "..ZoneName.."\nStatus: "..State)
else
self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State)
self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State)
end
end
end
end
ARTY={
ClassName="ARTY",
lid=nil,
Debug=false,
targets={},
moves={},
currentTarget=nil,
currentMove=nil,
Nammo0=0,
Nshells0=0,
Nrockets0=0,
Nmissiles0=0,
Nukes0=0,
Nillu0=0,
Nsmoke0=0,
StatusInterval=10,
WaitForShotTime=300,
DCSdesc=nil,
Type=nil,
DisplayName=nil,
groupname=nil,
alias=nil,
clusters={},
ismobile=true,
iscargo=false,
cargogroup=nil,
IniGroupStrength=0,
IsArtillery=nil,
RearmingDistance=100,
RearmingGroup=nil,
RearmingGroupSpeed=nil,
RearmingGroupOnRoad=false,
RearmingGroupCoord=nil,
RearmingPlaceCoord=nil,
RearmingArtyOnRoad=false,
InitialCoord=nil,
report=true,
ammoshells={},
ammorockets={},
ammomissiles={},
Nshots=0,
minrange=300,
maxrange=1000000,
nukewarhead=75000,
Nukes=nil,
nukefire=false,
nukefires=nil,
nukerange=nil,
Nillu=nil,
illuPower=1000000,
illuMinalt=500,
illuMaxalt=1000,
Nsmoke=nil,
smokeColor=SMOKECOLOR.Red,
relocateafterfire=false,
relocateRmin=300,
relocateRmax=800,
markallow=false,
markkey=nil,
markreadonly=false,
autorelocate=false,
autorelocatemaxdist=50000,
autorelocateonroad=false,
coalition=nil,
respawnafterdeath=false,
respawndelay=nil
}
ARTY.WeaponType={
Auto=1073741822,
Cannon=805306368,
Rockets=30720,
CruiseMissile=2097152,
TacticalNukes=666,
IlluminationShells=667,
SmokeShells=668,
}
ARTY.db={
["LeFH_18-40-105"]={
displayname="FH LeFH-18 105mm",
minrange=500,
maxrange=10500,
reloadtime=nil,
},
["M2A1-105"]={
displayname="FH M2A1 105mm",
minrange=500,
maxrange=11500,
reloadtime=nil,
},
["Pak40"]={
displayname="FH Pak 40 75mm",
minrange=500,
maxrange=3000,
reloadtime=nil,
},
["L118_Unit"]={
displayname="L118 Light Artillery Gun",
minrange=500,
maxrange=17500,
reloadtime=nil,
},
["Smerch"]={
displayname="MLRS 9A52 Smerch CM 300mm",
minrange=20000,
maxrange=70000,
reloadtime=2160,
},
["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.3"
function ARTY:New(group,alias)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
if type(group)=="string"then
self.groupname=group
group=GROUP:FindByName(group)
if not group then
self:E(string.format("ERROR: Requested ARTY group %s does not exist! (Has to be a MOOSE group.)",self.groupname))
return nil
end
end
if group then
self:T(string.format("ARTY script version %s. Added group %s.",ARTY.version,group:GetName()))
else
self:E("ERROR: Requested ARTY group does not exist! (Has to be a MOOSE group.)")
return nil
end
if not(group:IsGround()or group:IsShip())then
self:E(string.format("ERROR: ARTY group %s has to be a GROUND or SHIP group!",group:GetName()))
return nil
end
self:SetControllable(group)
self.groupname=group:GetName()
self.coalition=group:GetCoalition()
if alias~=nil then
self.alias=tostring(alias)
else
self.alias=self.groupname
end
self.lid=string.format("ARTY %s | ",self.alias)
self.InitialCoord=group:GetCoordinate()
local DCSgroup=Group.getByName(group:GetName())
local DCSunit=DCSgroup:getUnit(1)
self.DCSdesc=DCSunit:getDesc()
self:T3(self.lid.."DCS descriptors for group "..group:GetName())
for id,desc in pairs(self.DCSdesc)do
self:T3({id=id,desc=desc})
end
self.SpeedMax=group:GetSpeedMax()
if self.SpeedMax>3.6 then
self.ismobile=true
else
self.ismobile=false
end
self.dtTrack=0.2
self.Speed=self.SpeedMax*0.7
self.DisplayName=self.DCSdesc.displayName
self.IsArtillery=DCSunit:hasAttribute("Artillery")
self.Type=group:GetTypeName()
self.IniGroupStrength=#group:GetUnits()
self:AddTransition("*","Start","CombatReady")
self:AddTransition("CombatReady","OpenFire","Firing")
self:AddTransition("Firing","CeaseFire","CombatReady")
self:AddTransition("CombatReady","Winchester","OutOfAmmo")
self:AddTransition({"CombatReady","OutOfAmmo"},"Rearm","Rearming")
self:AddTransition("Rearming","Rearmed","Rearmed")
self:AddTransition("*","Move","Moving")
self:AddTransition("Moving","Arrived","Arrived")
self:AddTransition("*","NewTarget","*")
self:AddTransition("*","CombatReady","CombatReady")
self:AddTransition("*","Status","*")
self:AddTransition("*","NewMove","*")
self:AddTransition("*","Dead","*")
self:AddTransition("*","Respawn","CombatReady")
self:AddTransition("*","Loaded","InTransit")
self:AddTransition("InTransit","UnLoaded","CombatReady")
self:AddTransition("Rearming","Arrived","Rearming")
self:AddTransition("Rearming","Move","Rearming")
return self
end
function ARTY:NewFromCargoGroup(cargogroup,alias)
if cargogroup then
BASE:T(string.format("ARTY script version %s. Added CARGO group %s.",ARTY.version,cargogroup:GetName()))
else
BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
return nil
end
local group=cargogroup:GetObject()
local arty=ARTY:New(group,alias)
arty.iscargo=true
arty.cargogroup=cargogroup
return arty
end
function ARTY:AssignTargetCoord(coord,prio,radius,nshells,maxengage,time,weapontype,name,unique)
self:F({coord=coord,prio=prio,radius=radius,nshells=nshells,maxengage=maxengage,time=time,weapontype=weapontype,name=name,unique=unique})
nshells=nshells or 5
radius=radius or 100
maxengage=maxengage or 1
prio=prio or 50
prio=math.max(1,prio)
prio=math.min(100,prio)
if unique==nil then
unique=false
end
weapontype=weapontype or ARTY.WeaponType.Auto
local text=nil
if coord:IsInstanceOf("GROUP")then
text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a GROUP. Converting to COORDINATE..."
coord=coord:GetCoordinate()
elseif coord:IsInstanceOf("UNIT")then
text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a UNIT. Converting to COORDINATE..."
coord=coord:GetCoordinate()
elseif coord:IsInstanceOf("POSITIONABLE")then
text="WARNING: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter - you gave a POSITIONABLE. Converting to COORDINATE..."
coord=coord:GetCoordinate()
elseif coord:IsInstanceOf("COORDINATE")then
else
text="ERROR: ARTY:AssignTargetCoordinate(coord, ...) needs a COORDINATE object as first parameter!"
MESSAGE:New(text,30):ToAll()
self:E(self.lid..text)
return nil
end
if text~=nil then
self:E(self.lid..text)
end
local _name=name or coord:ToStringLLDMS()
local _unique=true
_name,_unique=self:_CheckName(self.targets,_name,not unique)
if unique==true and _unique==false then
self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name))
return nil
end
local _time
if type(time)=="string"then
_time=self:_ClockToSeconds(time)
elseif type(time)=="number"then
_time=timer.getAbsTime()+time
else
_time=timer.getAbsTime()
end
local _target={name=_name,coord=coord,radius=radius,nshells=nshells,engaged=0,underfire=false,prio=prio,maxengage=maxengage,time=_time,weapontype=weapontype}
table.insert(self.targets,_target)
self:__NewTarget(1,_target)
return _name
end
function ARTY:AssignAttackGroup(group,prio,radius,nshells,maxengage,time,weapontype,name,unique)
nshells=nshells or 5
radius=radius or 100
maxengage=maxengage or 1
prio=prio or 50
prio=math.max(1,prio)
prio=math.min(100,prio)
if unique==nil then
unique=false
end
weapontype=weapontype or ARTY.WeaponType.Auto
if type(group)=="string"then
group=GROUP:FindByName(group)
end
if group and group:IsAlive()then
local coord=group:GetCoordinate()
local _name=group:GetName()
local _unique=true
_name,_unique=self:_CheckName(self.targets,_name,not unique)
if unique==true and _unique==false then
self:T(self.lid..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!",self.groupname,_name))
return nil
end
local _time
if type(time)=="string"then
_time=self:_ClockToSeconds(time)
elseif type(time)=="number"then
_time=timer.getAbsTime()+time
else
_time=timer.getAbsTime()
end
local target={}
target.attackgroup=true
target.name=_name
target.coord=coord
target.radius=radius
target.nshells=nshells
target.engaged=0
target.underfire=false
target.prio=prio
target.time=_time
target.maxengage=maxengage
target.weapontype=weapontype
table.insert(self.targets,target)
self:__NewTarget(1,target)
return _name
else
self:E("ERROR: Group does not exist!")
end
return nil
end
function ARTY:AssignMoveCoord(coord,time,speed,onroad,cancel,name,unique)
self:F({coord=coord,time=time,speed=speed,onroad=onroad,cancel=cancel,name=name,unique=unique})
if not self.ismobile then
self:T(self.lid..string.format("%s: group is immobile. Rejecting move request!",self.groupname))
return nil
end
if unique==nil then
unique=false
end
local _name=name or coord:ToStringLLDMS()
local _unique=true
_name,_unique=self:_CheckName(self.moves,_name,not unique)
if unique==true and _unique==false then
self:T(self.lid..string.format("%s: move %s should have a unique name but name was already given. Rejecting move!",self.groupname,_name))
return nil
end
if speed then
speed=math.min(speed,self.SpeedMax)
elseif self.Speed then
speed=self.Speed
else
speed=self.SpeedMax*0.7
end
if onroad==nil then
onroad=false
end
if cancel==nil then
cancel=false
end
local _time
if type(time)=="string"then
_time=self:_ClockToSeconds(time)
elseif type(time)=="number"then
_time=timer.getAbsTime()+time
else
_time=timer.getAbsTime()
end
local _move={name=_name,coord=coord,time=_time,speed=speed,onroad=onroad,cancel=cancel}
table.insert(self.moves,_move)
return _name
end
function ARTY:SetAlias(alias)
self:F({alias=alias})
self.alias=tostring(alias)
return self
end
function ARTY:AddToCluster(clusters)
self:F({clusters=clusters})
local names
if type(clusters)=="table"then
names=clusters
elseif type(clusters)=="string"then
names={clusters}
else
self:E(self.lid.."ERROR: Input parameter must be a string or a table in ARTY:AddToCluster()!")
return
end
for _,cluster in pairs(names)do
table.insert(self.clusters,cluster)
end
return self
end
function ARTY:SetMinFiringRange(range)
self:F({range=range})
self.minrange=range*1000 or 100
return self
end
function ARTY:SetMaxFiringRange(range)
self:F({range=range})
self.maxrange=range*1000 or 1000*1000
return self
end
function ARTY:SetStatusInterval(interval)
self:F({interval=interval})
self.StatusInterval=interval or 10
return self
end
function ARTY:SetTrackInterval(interval)
self.dtTrack=interval or 0.2
return self
end
function ARTY:SetWaitForShotTime(waittime)
self:F({waittime=waittime})
self.WaitForShotTime=waittime or 300
return self
end
function ARTY:SetRearmingDistance(distance)
self:F({distance=distance})
self.RearmingDistance=distance or 100
return self
end
function ARTY:SetRearmingGroup(group)
self:F({group=group})
self.RearmingGroup=group
return self
end
function ARTY:SetRearmingGroupSpeed(speed)
self:F({speed=speed})
self.RearmingGroupSpeed=speed
return self
end
function ARTY:SetRearmingGroupOnRoad(onroad)
self:F({onroad=onroad})
if onroad==nil then
onroad=true
end
self.RearmingGroupOnRoad=onroad
return self
end
function ARTY:SetRearmingArtyOnRoad(onroad)
self:F({onroad=onroad})
if onroad==nil then
onroad=true
end
self.RearmingArtyOnRoad=onroad
return self
end
function ARTY:SetRearmingPlace(coord)
self:F({coord=coord})
self.RearmingPlaceCoord=coord
return self
end
function ARTY:SetAutoRelocateToFiringRange(maxdistance,onroad)
self:F({distance=maxdistance,onroad=onroad})
self.autorelocate=true
self.autorelocatemaxdist=maxdistance or 50
self.autorelocatemaxdist=self.autorelocatemaxdist*1000
if onroad==nil then
onroad=false
end
self.autorelocateonroad=onroad
return self
end
function ARTY:SetAutoRelocateAfterEngagement(rmax,rmin)
self.relocateafterfire=true
self.relocateRmax=rmax or 800
self.relocateRmin=rmin or 300
self.relocateRmin=math.min(self.relocateRmin,self.relocateRmax)
return self
end
function ARTY:SetReportON()
self.report=true
return self
end
function ARTY:SetReportOFF()
self.report=false
return self
end
function ARTY:SetRespawnOnDeath(delay)
self.respawnafterdeath=true
self.respawndelay=delay
return self
end
function ARTY:SetDebugON()
self.Debug=true
return self
end
function ARTY:SetDebugOFF()
self.Debug=false
return self
end
function ARTY:SetSpeed(speed)
self.Speed=speed
return self
end
function ARTY:RemoveTarget(name)
self:F2(name)
local id=self:_GetTargetIndexByName(name)
if id then
self:T(self.lid..string.format("Group %s: Removing target %s (id=%d).",self.groupname,name,id))
table.remove(self.targets,id)
if self.markallow then
local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name)
if batteryname==self.groupname and markTargetID~=nil then
COORDINATE:RemoveMark(markTargetID)
end
end
end
self:T(self.lid..string.format("Group %s: Number of targets = %d.",self.groupname,#self.targets))
end
function ARTY:RemoveMove(name)
self:F2(name)
local id=self:_GetMoveIndexByName(name)
if id then
self:T(self.lid..string.format("Group %s: Removing move %s (id=%d).",self.groupname,name,id))
table.remove(self.moves,id)
if self.markallow then
local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name)
if batteryname==self.groupname and markMoveID~=nil then
COORDINATE:RemoveMark(markMoveID)
end
end
end
self:T(self.lid..string.format("Group %s: Number of moves = %d.",self.groupname,#self.moves))
end
function ARTY:RemoveAllTargets()
self:F2()
for _,target in pairs(self.targets)do
self:RemoveTarget(target.name)
end
end
function ARTY:SetShellTypes(tableofnames)
self:F2(tableofnames)
self.ammoshells={}
for _,_type in pairs(tableofnames)do
table.insert(self.ammoshells,_type)
end
return self
end
function ARTY:SetRocketTypes(tableofnames)
self:F2(tableofnames)
self.ammorockets={}
for _,_type in pairs(tableofnames)do
table.insert(self.ammorockets,_type)
end
return self
end
function ARTY:SetMissileTypes(tableofnames)
self:F2(tableofnames)
self.ammomissiles={}
for _,_type in pairs(tableofnames)do
table.insert(self.ammomissiles,_type)
end
return self
end
function ARTY:SetTacNukeShells(n)
self.Nukes=n
return self
end
function ARTY:SetTacNukeWarhead(strength)
self.nukewarhead=strength or 0.075
self.nukewarhead=self.nukewarhead*1000*1000
return self
end
function ARTY:SetIlluminationShells(n,power)
self.Nillu=n
self.illuPower=power or 1.0
self.illuPower=self.illuPower*1000000
return self
end
function ARTY:SetIlluminationMinMaxAlt(minalt,maxalt)
self.illuMinalt=minalt or 500
self.illuMaxalt=maxalt or 1000
if self.illuMinalt>self.illuMaxalt then
self.illuMinalt=self.illuMaxalt
end
return self
end
function ARTY:SetSmokeShells(n,color)
self.Nsmoke=n
self.smokeColor=color or SMOKECOLOR.Red
return self
end
function ARTY:SetTacNukeFires(nfires,range)
self.nukefire=true
self.nukefires=nfires
self.nukerange=range
return self
end
function ARTY:SetMarkAssignmentsOn(key,readonly)
self.markkey=key
self.markallow=true
if readonly==nil then
self.markreadonly=false
end
return self
end
function ARTY:SetMarkTargetsOff()
self.markallow=false
self.markkey=nil
return self
end
function ARTY:onafterStart(Controllable,From,Event,To)
self:_EventFromTo("onafterStart",Event,From,To)
local text=string.format("Started ARTY version %s for group %s.",ARTY.version,Controllable:GetName())
self:I(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self.Nammo0,self.Nshells0,self.Nrockets0,self.Nmissiles0,self.Narty0=self:GetAmmo(self.Debug)
if self.nukerange==nil then
self.nukerange=1500/75000*self.nukewarhead
end
if self.nukefires==nil then
self.nukefires=20/1000/1000*self.nukerange*self.nukerange
end
if self.Nukes~=nil then
self.Nukes0=math.min(self.Nukes,self.Nshells0)
else
self.Nukes=0
self.Nukes0=0
end
if self.Nillu~=nil then
self.Nillu0=math.min(self.Nillu,self.Nshells0)
else
self.Nillu=0
self.Nillu0=0
end
if self.Nsmoke~=nil then
self.Nsmoke0=math.min(self.Nsmoke,self.Nshells0)
else
self.Nsmoke=0
self.Nsmoke0=0
end
local _dbproperties=self:_CheckDB(self.Type)
self:T({dbproperties=_dbproperties})
if _dbproperties~=nil then
for property,value in pairs(_dbproperties)do
self:T({property=property,value=value})
self[property]=value
end
end
if not self.ismobile then
self.RearmingPlaceCoord=nil
self.relocateafterfire=false
self.autorelocate=false
end
self.Speed=math.min(self.Speed,self.SpeedMax)
if self.RearmingGroup then
local speedmax=self.RearmingGroup:GetSpeedMax()
self:T(self.lid..string.format("%s, rearming group %s max speed = %.1f km/h.",self.groupname,self.RearmingGroup:GetName(),speedmax))
if self.RearmingGroupSpeed==nil then
self.RearmingGroupSpeed=speedmax*0.5
else
self.RearmingGroupSpeed=math.min(self.RearmingGroupSpeed,self.RearmingGroup:GetSpeedMax())
end
else
self.RearmingGroupSpeed=23
end
local text=string.format("\n******************************************************\n")
text=text..string.format("Arty group          = %s\n",self.groupname)
text=text..string.format("Arty alias          = %s\n",self.alias)
text=text..string.format("Artillery attribute = %s\n",tostring(self.IsArtillery))
text=text..string.format("Type                = %s\n",self.Type)
text=text..string.format("Display Name        = %s\n",self.DisplayName)
text=text..string.format("Number of units     = %d\n",self.IniGroupStrength)
text=text..string.format("Speed max           = %.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)
text=text..string.format("Max range           = %.1f km\n",self.maxrange/1000)
text=text..string.format("Total ammo count    = %d\n",self.Nammo0)
text=text..string.format("Number of shells    = %d\n",self.Nshells0)
text=text..string.format("Number of rockets   = %d\n",self.Nrockets0)
text=text..string.format("Number of missiles  = %d\n",self.Nmissiles0)
text=text..string.format("Number of nukes     = %d\n",self.Nukes0)
text=text..string.format("Nuclear warhead     = %d tons TNT\n",self.nukewarhead/1000)
text=text..string.format("Nuclear demolition  = %d m\n",self.nukerange)
text=text..string.format("Nuclear fires       = %d (active=%s)\n",self.nukefires,tostring(self.nukefire))
text=text..string.format("Number of illum.    = %d\n",self.Nillu0)
text=text..string.format("Illuminaton Power   = %.3f mcd\n",self.illuPower/1000000)
text=text..string.format("Illuminaton Minalt  = %d m\n",self.illuMinalt)
text=text..string.format("Illuminaton Maxalt  = %d m\n",self.illuMaxalt)
text=text..string.format("Number of smoke     = %d\n",self.Nsmoke0)
text=text..string.format("Smoke color         = %d\n",self.smokeColor)
if self.RearmingGroup or self.RearmingPlaceCoord then
text=text..string.format("Rearming safe dist. = %d m\n",self.RearmingDistance)
end
if self.RearmingGroup then
text=text..string.format("Rearming group      = %s\n",self.RearmingGroup:GetName())
text=text..string.format("Rearming group speed= %d km/h\n",self.RearmingGroupSpeed)
text=text..string.format("Rearming group roads= %s\n",tostring(self.RearmingGroupOnRoad))
end
if self.RearmingPlaceCoord then
local dist=self.InitialCoord:Get2DDistance(self.RearmingPlaceCoord)
text=text..string.format("Rearming coord dist = %d m\n",dist)
text=text..string.format("Rearming ARTY roads = %s\n",tostring(self.RearmingArtyOnRoad))
end
text=text..string.format("Relocate after fire = %s\n",tostring(self.relocateafterfire))
text=text..string.format("Relocate min dist.  = %d m\n",self.relocateRmin)
text=text..string.format("Relocate max dist.  = %d m\n",self.relocateRmax)
text=text..string.format("Auto move in range  = %s\n",tostring(self.autorelocate))
text=text..string.format("Auto move dist. max = %.1f km\n",self.autorelocatemaxdist/1000)
text=text..string.format("Auto move on road   = %s\n",tostring(self.autorelocateonroad))
text=text..string.format("Marker assignments  = %s\n",tostring(self.markallow))
text=text..string.format("Marker auth. key    = %s\n",tostring(self.markkey))
text=text..string.format("Marker readonly     = %s\n",tostring(self.markreadonly))
text=text..string.format("Clusters:\n")
for _,cluster in pairs(self.clusters)do
text=text..string.format("- %s\n",tostring(cluster))
end
text=text..string.format("******************************************************\n")
text=text..string.format("Targets:\n")
for _,target in pairs(self.targets)do
text=text..string.format("- %s\n",self:_TargetInfo(target))
local possible=self:_CheckWeaponTypePossible(target)
if not possible then
self:E(self.lid..string.format("WARNING: Selected weapon type %s is not possible",self:_WeaponTypeName(target.weapontype)))
end
if self.Debug then
local zone=ZONE_RADIUS:New(target.name,target.coord:GetVec2(),target.radius)
zone:BoundZone(180,coalition.side.NEUTRAL)
end
end
text=text..string.format("Moves:\n")
for i=1,#self.moves do
text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i]))
end
text=text..string.format("******************************************************\n")
text=text..string.format("Shell types:\n")
for _,_type in pairs(self.ammoshells)do
text=text..string.format("- %s\n",_type)
end
text=text..string.format("Rocket types:\n")
for _,_type in pairs(self.ammorockets)do
text=text..string.format("- %s\n",_type)
end
text=text..string.format("Missile types:\n")
for _,_type in pairs(self.ammomissiles)do
text=text..string.format("- %s\n",_type)
end
text=text..string.format("******************************************************")
if self.Debug then
self:I(self.lid..text)
else
self:T(self.lid..text)
end
self.Controllable:OptionROEHoldFire()
self:HandleEvent(EVENTS.Shot)
self:HandleEvent(EVENTS.Dead)
if self.markallow then
world.addEventHandler(self)
end
self:__Status(self.StatusInterval)
end
function ARTY:_CheckDB(displayname)
for _type,_properties in pairs(ARTY.db)do
self:T({type=_type,properties=_properties})
if _type==displayname then
self:T({type=_type,properties=_properties})
return _properties
end
end
return nil
end
function ARTY:_StatusReport(display)
if display==nil then
display=false
end
local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo()
local Nnukes=self.Nukes
local Nillu=self.Nillu
local Nsmoke=self.Nsmoke
local Tnow=timer.getTime()
local Clock=self:_SecondsToClock(timer.getAbsTime())
local text=string.format("\n******************* STATUS ***************************\n")
text=text..string.format("ARTY group          = %s\n",self.groupname)
text=text..string.format("Clock               = %s\n",Clock)
text=text..string.format("FSM state           = %s\n",self:GetState())
text=text..string.format("Total ammo count    = %d\n",Nammo)
text=text..string.format("Number of shells    = %d\n",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)
text=text..string.format("Number of illum.    = %d\n",Nillu)
text=text..string.format("Number of smoke     = %d\n",Nsmoke)
if self.currentTarget then
text=text..string.format("Current Target      = %s\n",tostring(self.currentTarget.name))
text=text..string.format("Curr. Tgt assigned  = %d\n",Tnow-self.currentTarget.Tassigned)
else
text=text..string.format("Current Target      = %s\n","none")
end
text=text..string.format("Nshots curr. Target = %d\n",self.Nshots)
text=text..string.format("Targets:\n")
for i=1,#self.targets do
text=text..string.format("- %s\n",self:_TargetInfo(self.targets[i]))
end
if self.currentMove then
text=text..string.format("Current Move        = %s\n",tostring(self.currentMove.name))
else
text=text..string.format("Current Move        = %s\n","none")
end
text=text..string.format("Moves:\n")
for i=1,#self.moves do
text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i]))
end
text=text..string.format("******************************************************")
env.info(self.lid..text)
MESSAGE:New(text,20):Clear():ToCoalitionIf(self.coalition,display)
end
function ARTY._FuncTrack(weapon,self,target)
local _coord=weapon.coordinate
local _dist=_coord:Get2DDistance(target.coord)
local _destroyweapon=false
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m",self.groupname,_dist))
if target.weapontype==ARTY.WeaponType.IlluminationShells then
if _dist<target.radius then
local _cr=target.coord:GetRandomCoordinateInRadius(target.radius)
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt,self.illuMaxalt)
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
_ci:IlluminationBomb(self.illuPower)
_destroyweapon=true
end
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<target.radius then
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
_cr:Smoke(self.smokeColor)
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.",self.groupname))
weapon:Destroy()
weapon.tracking=false
end
end
function ARTY._FuncImpact(weapon,self,target)
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.",self.groupname))
local _impactcoord=weapon:GetImpactCoordinate()
if target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.",self.groupname))
self:ScheduleOnce(1.0,ARTY._NuclearBlast,self,_impactcoord)
end
end
function ARTY:OnEventShot(EventData)
self:F(EventData)
local _weapon=EventData.Weapon:getTypeName()
local _weaponStrArray=self:_split(_weapon,"%.")
local _weaponName=_weaponStrArray[#_weaponStrArray]
self:T3(self.lid.."EVENT SHOT: Ini unit    = "..EventData.IniUnitName)
self:T3(self.lid.."EVENT SHOT: Ini group   = "..EventData.IniGroupName)
self:T3(self.lid.."EVENT SHOT: Weapon type = ".._weapon)
self:T3(self.lid.."EVENT SHOT: Weapon name = ".._weaponName)
local group=EventData.IniGroup
if group and group:IsAlive()then
if EventData.IniGroupName==self.groupname then
if self.currentTarget then
self.Nshots=self.Nshots+1
local text=string.format("%s, fired shot %d of %d with weapon %s on target %s.",self.alias,self.Nshots,self.currentTarget.nshells,_weaponName,self.currentTarget.name)
self:T(self.lid..text)
MESSAGE:New(text,5):Clear():ToAllIf(self.report or self.Debug)
local _tracknuke=self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0
local _trackillu=self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0
local _tracksmoke=self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0
if _tracknuke or _trackillu or _tracksmoke then
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.",self.groupname))
local weapon=WEAPON:New(EventData.weapon)
weapon:SetTimeStepTrack(self.dtTrack)
local target=UTILS.DeepCopy(self.currentTarget)
weapon:SetFuncTrack(ARTY._FuncTrack,self,target)
weapon:SetFuncImpact(ARTY._FuncImpact,self,target)
weapon:StartTrack(2)
end
local _nammo,_nshells,_nrockets,_nmissiles,_narty=self:GetAmmo()
if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then
self.Nukes=self.Nukes-1
end
if self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells then
self.Nillu=self.Nillu-1
end
if self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells then
self.Nsmoke=self.Nsmoke-1
end
local _outofammo=false
if _nammo==0 then
self:T(self.lid..string.format("Group %s completely out of ammo.",self.groupname))
_outofammo=true
end
local _partlyoutofammo=self:_CheckOutOfAmmo({self.currentTarget})
local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype)
self:T(self.lid..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d",self.groupname,_nammo,_narty,_nrockets,_nmissiles))
self:T(self.lid..string.format("Group %s uses weapontype %s for current target.",self.groupname,_weapontype))
local _ceasefire=false
local _relocate=false
if self.Nshots>=self.currentTarget.nshells then
local text=string.format("Group %s stop firing on target %s.",self.groupname,self.currentTarget.name)
self:T(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
_ceasefire=true
_relocate=self.relocateafterfire
end
if _outofammo or _partlyoutofammo then
_ceasefire=true
end
if _relocate then
self:_Relocate()
end
if _ceasefire then
self:CeaseFire(self.currentTarget)
end
else
self:E(self.lid..string.format("WARNING: No current target for group %s?!",self.groupname))
end
end
end
end
function ARTY:onEvent(Event)
if Event==nil or Event.idx==nil then
self:T3("Skipping onEvent. Event or Event.idx unknown.")
return true
end
self:T2(string.format("Event captured  = %s",tostring(self.groupname)))
self:T2(string.format("Event id        = %s",tostring(Event.id)))
self:T2(string.format("Event time      = %s",tostring(Event.time)))
self:T2(string.format("Event idx       = %s",tostring(Event.idx)))
self:T2(string.format("Event coalition = %s",tostring(Event.coalition)))
self:T2(string.format("Event group id  = %s",tostring(Event.groupID)))
self:T2(string.format("Event text      = %s",tostring(Event.text)))
if Event.initiator~=nil then
local _unitname=Event.initiator:getName()
self:T2(string.format("Event ini unit name = %s",tostring(_unitname)))
end
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T2({event="S_EVENT_MARK_ADDED",battery=self.groupname,vec3=Event.pos})
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE",battery=self.groupname,vec3=Event.pos})
self:_OnEventMarkChange(Event)
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T2({event="S_EVENT_MARK_REMOVED",battery=self.groupname,vec3=Event.pos})
self:_OnEventMarkRemove(Event)
end
end
function ARTY:_OnEventMarkRemove(Event)
local batterycoalition=self.coalition
if Event.text~=nil and Event.text:find("BATTERY")then
local _cancelmove=false
local _canceltarget=false
local _name=""
local _id=nil
if Event.text:find("Marked Relocation")then
_cancelmove=true
_name=self:_MarkMoveName(Event.idx)
_id=self:_GetMoveIndexByName(_name)
elseif Event.text:find("Marked Target")then
_canceltarget=true
_name=self:_MarkTargetName(Event.idx)
_id=self:_GetTargetIndexByName(_name)
else
return
end
if _id==nil then
return
end
if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then
local _validkey=self:_MarkerKeyAuthentification(Event.text)
if _validkey then
if _cancelmove then
if self.currentMove and self.currentMove.name==_name then
self.Controllable:ClearTasks()
self:Arrived()
else
self:RemoveMove(_name)
end
elseif _canceltarget then
if self.currentTarget and self.currentTarget.name==_name then
self:CeaseFire(self.currentTarget)
self:RemoveTarget(_name)
else
self:RemoveTarget(_name)
end
end
end
end
end
end
function ARTY:_OnEventMarkChange(Event)
if Event.text~=nil and Event.text:lower():find("arty")then
local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z}
local _coord=COORDINATE:NewFromVec3(vec3)
_coord.y=_coord:GetLandHeight()
local batterycoalition=self.coalition
local batteryname=self.groupname
if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then
local _assign=self:_Markertext(Event.text)
if _assign==nil or not(_assign.engage or _assign.move or _assign.request or _assign.cancel or _assign.set)then
self:T(self.lid..string.format("WARNING: %s, no keyword ENGAGE, MOVE, REQUEST, CANCEL or SET in mark text! Command will not be executed. Text:\n%s",self.groupname,Event.text))
return
end
local _assigned=false
if _assign.everyone then
_assigned=true
else
for _,bat in pairs(_assign.battery)do
if self.groupname==bat then
_assigned=true
end
end
for _,alias in pairs(_assign.aliases)do
if self.alias==alias then
_assigned=true
end
end
for _,bat in pairs(_assign.cluster)do
for _,cluster in pairs(self.clusters)do
if cluster==bat then
_assigned=true
end
end
end
end
if not _assigned then
self:T3(self.lid..string.format("INFO: ARTY group %s was not addressed! Mark text:\n%s",self.groupname,Event.text))
return
else
if self.Controllable and self.Controllable:IsAlive()then
else
self:T3(self.lid..string.format("INFO: ARTY group %s was addressed but is NOT alive! Mark text:\n%s",self.groupname,Event.text))
return
end
end
if _assign.coord then
_coord=_assign.coord
end
local _validkey=self:_MarkerKeyAuthentification(Event.text)
if _assign.request and _validkey then
if _assign.requestammo then
self:_MarkRequestAmmo()
end
if _assign.requestmoves then
self:_MarkRequestMoves()
end
if _assign.requesttargets then
self:_MarkRequestTargets()
end
if _assign.requeststatus then
self:_MarkRequestStatus()
end
if _assign.requestrearming then
self:Rearm()
end
return
end
if _assign.cancel and _validkey then
if _assign.cancelmove and self.currentMove then
self.Controllable:ClearTasks()
self:Arrived()
elseif _assign.canceltarget and self.currentTarget then
self.currentTarget.engaged=self.currentTarget.engaged+1
self:CeaseFire(self.currentTarget)
elseif _assign.cancelrearm and self:is("Rearming")then
local nammo=self:GetAmmo()
if nammo>0 then
self:Rearmed()
else
self:Winchester()
end
end
return
end
if _assign.set and _validkey then
if _assign.setrearmingplace and self.ismobile then
self:SetRearmingPlace(_coord)
_coord:RemoveMark(Event.idx)
_coord:MarkToCoalition(string.format("Rearming place for battery %s",self.groupname),self.coalition,false,string.format("New rearming place for battery %s defined.",self.groupname))
if self.Debug then
_coord:SmokeOrange()
end
end
if _assign.setrearminggroup then
_coord:RemoveMark(Event.idx)
local rearminggroupcoord=_assign.setrearminggroup:GetCoordinate()
rearminggroupcoord:MarkToCoalition(string.format("Rearming group for battery %s",self.groupname),self.coalition,false,string.format("New rearming group for battery %s defined.",self.groupname))
self:SetRearmingGroup(_assign.setrearminggroup)
if self.Debug then
rearminggroupcoord:SmokeOrange()
end
end
return
end
if _validkey then
_coord:RemoveMark(Event.idx)
local _id=UTILS._MarkID+1
if _assign.move then
local _name=self:_MarkMoveName(_id)
local text=string.format("%s, received new relocation assignment.",self.alias)
text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS())
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
local _movename=self:AssignMoveCoord(_coord,_assign.time,_assign.speed,_assign.onroad,_assign.movecanceltarget,_name,true)
if _movename~=nil then
local _mid=self:_GetMoveIndexByName(_movename)
local _move=self.moves[_mid]
local clock=tostring(self:_SecondsToClock(_move.time))
local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.",clock,_move.speed,tostring(_move.onroad))
local _randomcoord=_coord:GetRandomCoordinateInRadius(100)
_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly)
else
local text=string.format("%s, relocation not possible.",self.alias)
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
end
else
local _name=self:_MarkTargetName(_id)
local text=string.format("%s, received new target assignment.",self.alias)
text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS())
if _assign.time then
text=text..string.format("\nTime %s",_assign.time)
end
if _assign.prio then
text=text..string.format("\nPrio %d",_assign.prio)
end
if _assign.radius then
text=text..string.format("\nRadius %d m",_assign.radius)
end
if _assign.nshells then
text=text..string.format("\nShots %d",_assign.nshells)
end
if _assign.maxengage then
text=text..string.format("\nEngagements %d",_assign.maxengage)
end
if _assign.weapontype then
text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype))
end
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype,_name,true)
if _targetname~=nil then
local _tid=self:_GetTargetIndexByName(_targetname)
local _target=self.targets[_tid]
local clock=tostring(self:_SecondsToClock(_target.time))
local weapon=self:_WeaponTypeName(_target.weapontype)
local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s",_target.prio,_target.radius,_target.nshells,_target.maxengage,weapon,clock)
local _randomcoord=_coord:GetRandomCoordinateInRadius(250)
_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly)
end
end
end
end
end
end
function ARTY:OnEventDead(EventData)
self:F(EventData)
local _name=self.groupname
if EventData and EventData.IniGroupName and EventData.IniGroupName==_name then
local unitname=tostring(EventData.IniUnitName)
self:T(self.lid..string.format("%s: Captured dead event for unit %s.",_name,unitname))
self:Dead(unitname)
end
end
function ARTY:onafterStatus(Controllable,From,Event,To)
self:_EventFromTo("onafterStatus",Event,From,To)
local nammo,nshells,nrockets,nmissiles,narty=self:GetAmmo()
if self.iscargo and self.cargogroup then
if self.cargogroup:IsLoaded()and not self:is("InTransit")then
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.",self.alias))
self:Loaded()
elseif self.cargogroup:IsUnLoaded()then
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.",self.alias))
self:UnLoaded()
end
end
local fsmstate=self:GetState()
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d",fsmstate,nammo,narty,self.Nsmoke,self.Nillu,self.Nukes,self.nukewarhead/1000000,nrockets,nmissiles))
if self.Controllable and self.Controllable:IsAlive()then
if self.Debug then
self:_StatusReport()
end
if self:is("Moving")then
self:T2(self.lid..string.format("%s: Moving",Controllable:GetName()))
end
if self:is("Rearming")then
local _rearmed=self:_CheckRearmed()
if _rearmed then
self:T2(self.lid..string.format("%s: Rearming ==> Rearmed",Controllable:GetName()))
self:Rearmed()
end
end
if self:is("Rearmed")then
local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord)
self:T2(self.lid..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m",Controllable:GetName(),distance))
if distance<=self.RearmingDistance then
self:T2(self.lid..string.format("%s: Rearmed ==> CombatReady",Controllable:GetName()))
self:CombatReady()
end
end
if self:is("Arrived")then
self:T2(self.lid..string.format("%s: Arrived ==> CombatReady",Controllable:GetName()))
self:CombatReady()
end
if self:is("Firing")then
self:_CheckShootingStarted()
end
self:_CheckTargetsInRange()
local notpossible={}
for i=1,#self.targets do
local _target=self.targets[i]
local possible=self:_CheckWeaponTypePossible(_target)
if not possible then
table.insert(notpossible,_target.name)
end
end
for _,targetname in pairs(notpossible)do
self:E(self.lid..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.",self.groupname,targetname))
self:RemoveTarget(targetname)
end
local _timedTarget=self:_CheckTimedTargets()
local _normalTarget=self:_CheckNormalTargets()
local _move=self:_CheckMoves()
if _move then
self:Move(_move)
elseif _timedTarget then
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
if self:is("CombatReady")then
self:OpenFire(_timedTarget)
end
elseif _normalTarget then
if self:is("CombatReady")then
self:OpenFire(_normalTarget)
end
end
local gotsome=false
if#self.targets>0 then
for i=1,#self.targets do
local _target=self.targets[i]
if self:_CheckWeaponTypeAvailable(_target)>0 then
gotsome=true
end
end
else
gotsome=true
end
if(nammo==0 or not gotsome)and not(self:is("Moving")or self:is("Rearming")or self:is("OutOfAmmo"))then
self:Winchester()
end
if self:is("OutOfAmmo")then
self:T2(self.lid..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming",Controllable:GetName()))
self:Rearm()
end
self:__Status(self.StatusInterval)
elseif self.iscargo then
if self.cargogroup and self.cargogroup:IsAlive()then
if self:is("InTransit")then
self:__Status(-5)
end
end
else
self:E(self.lid..string.format("Arty group %s is not alive!",self.groupname))
end
end
function ARTY:onbeforeLoaded(Controllable,From,Event,To)
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
return true
end
function ARTY:onafterUnLoaded(Controllable,From,Event,To)
self:CombatReady()
end
function ARTY:onenterCombatReady(Controllable,From,Event,To)
self:_EventFromTo("onenterCombatReady",Event,From,To)
self:T3(self.lid..string.format("onenterComabReady, from=%s, event=%s, to=%s",From,Event,To))
end
function ARTY:onbeforeOpenFire(Controllable,From,Event,To,target)
self:_EventFromTo("onbeforeOpenFire",Event,From,To)
if self.currentTarget then
self:E(self.lid..string.format("ERROR: Group %s already has a target %s!",self.groupname,self.currentTarget.name))
return false
end
if not self:_TargetInRange(target)then
self:E(self.lid..string.format("ERROR: Group %s, target %s is out of range!",self.groupname,self.currentTarget.name))
return false
end
local nfire=self:_CheckWeaponTypeAvailable(target)
target.nshells=math.min(target.nshells,nfire)
if target.nshells<1 then
local text=string.format("%s, no ammo left to engage target %s with selected weapon type %s.")
return false
end
return true
end
function ARTY:onafterOpenFire(Controllable,From,Event,To,target)
self:_EventFromTo("onafterOpenFire",Event,From,To)
local id=self:_GetTargetIndexByName(target.name)
if id then
self.targets[id].underfire=true
self.currentTarget=target
self.currentTarget.Tassigned=timer.getTime()
end
local range=Controllable:GetCoordinate():Get2DDistance(target.coord)
local Nammo,Nshells,Nrockets,Nmissiles,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=Narty
_type="shells"
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
nfire=self.Nukes
_type="nuclear shells"
elseif target.weapontype==ARTY.WeaponType.IlluminationShells then
nfire=self.Nillu
_type="illumination shells"
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
nfire=self.Nsmoke
_type="smoke shells"
elseif target.weapontype==ARTY.WeaponType.Rockets then
nfire=Nrockets
_type="rockets"
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
nfire=Nmissiles
_type="cruise missiles"
end
target.nshells=math.min(target.nshells,nfire)
local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.",Controllable:GetName(),target.name,target.nshells,_type,range/1000)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report)
if target.attackgroup then
self:_AttackGroup(target)
else
self:_FireAtCoord(target.coord,target.radius,target.nshells,target.weapontype)
end
end
function ARTY:onafterCeaseFire(Controllable,From,Event,To,target)
self:_EventFromTo("onafterCeaseFire",Event,From,To)
if target then
local text=string.format("%s, ceasing fire on target %s.",Controllable:GetName(),target.name)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report)
local id=self:_GetTargetIndexByName(target.name)
if id then
if self.Nshots>0 then
self.targets[id].engaged=self.targets[id].engaged+1
self.targets[id].time=nil
end
self.targets[id].underfire=false
end
if target.engaged>=target.maxengage then
self:RemoveTarget(target.name)
end
self.Controllable:OptionROEHoldFire()
self.Controllable:ClearTasks()
else
self:E(self.lid..string.format("ERROR: No target in cease fire for group %s.",self.groupname))
end
self.Nshots=0
self.currentTarget=nil
end
function ARTY:onafterWinchester(Controllable,From,Event,To)
self:_EventFromTo("onafterWinchester",Event,From,To)
local text=string.format("%s, winchester!",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
end
function ARTY:onbeforeRearm(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRearm",Event,From,To)
local _rearmed=self:_CheckRearmed()
if _rearmed then
self:T(self.lid..string.format("%s, group is already armed to the teeth. Rearming request denied!",self.groupname))
return false
else
self:T(self.lid..string.format("%s, group might be rearmed.",self.groupname))
end
if self.RearmingGroup and self.RearmingGroup:IsAlive()then
return true
elseif self.RearmingPlaceCoord then
return true
else
return false
end
end
function ARTY:onafterRearm(Controllable,From,Event,To)
self:_EventFromTo("onafterRearm",Event,From,To)
local coordARTY=self.Controllable:GetCoordinate()
self.InitialCoord=coordARTY
local coordRARM=nil
if self.RearmingGroup then
coordRARM=self.RearmingGroup:GetCoordinate()
self.RearmingGroupCoord=coordRARM
end
if self.RearmingGroup and self.RearmingPlaceCoord and self.ismobile then
local text=string.format("%s, %s, request rearming at rearming place.",Controllable:GetName(),self.RearmingGroup:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord)
local dR=coordRARM:Get2DDistance(self.RearmingPlaceCoord)
if dA>self.RearmingDistance then
local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2)
self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true)
end
if dR>self.RearmingDistance then
local ToCoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2)
self:_Move(self.RearmingGroup,ToCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad)
end
elseif self.RearmingGroup then
local text=string.format("%s, %s, request rearming.",Controllable:GetName(),self.RearmingGroup:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
local distance=coordARTY:Get2DDistance(coordRARM)
if distance>self.RearmingDistance then
self:_Move(self.RearmingGroup,self:_VicinityCoord(coordARTY),self.RearmingGroupSpeed,self.RearmingGroupOnRoad)
end
elseif self.RearmingPlaceCoord then
local text=string.format("%s, moving to rearming place.",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord)
if dA>self.RearmingDistance then
local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord)
self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true)
end
end
end
function ARTY:onafterRearmed(Controllable,From,Event,To)
self:_EventFromTo("onafterRearmed",Event,From,To)
local text=string.format("%s, rearming complete.",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
self.Nukes=self.Nukes0
self.Nillu=self.Nillu0
self.Nsmoke=self.Nsmoke0
local dist=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord)
if dist>self.RearmingDistance then
self:AssignMoveCoord(self.InitialCoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE REARMING COMPLETE",true)
end
if self.RearmingGroup and self.RearmingGroup:IsAlive()then
local d=self.RearmingGroup:GetCoordinate():Get2DDistance(self.RearmingGroupCoord)
if d>self.RearmingDistance then
self:_Move(self.RearmingGroup,self.RearmingGroupCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad)
else
self.RearmingGroup:ClearTasks()
end
end
end
function ARTY:_CheckRearmed()
self:F2()
local nammo,nshells,nrockets,nmissiles,narty=self:GetAmmo()
local units=self.Controllable:GetUnits()
local nunits=0
if units then
nunits=#units
end
local FullAmmo=self.Nammo0*nunits/self.IniGroupStrength
local _rearmpc=nammo/FullAmmo*100
if _rearmpc>1 then
local text=string.format("%s, rearming %d %% complete.",self.alias,_rearmpc)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
end
if nammo>=FullAmmo then
return true
else
return false
end
end
function ARTY:onbeforeMove(Controllable,From,Event,To,move)
self:_EventFromTo("onbeforeMove",Event,From,To)
if not self.ismobile then
return false
end
if self.currentTarget then
if move.cancel then
self:CeaseFire(self.currentTarget)
else
return false
end
end
return true
end
function ARTY:onafterMove(Controllable,From,Event,To,move)
self:_EventFromTo("onafterMove",Event,From,To)
self.Controllable:OptionAlarmStateGreen()
self.Controllable:OptionROEHoldFire()
local _Speed=math.min(move.speed,self.SpeedMax)
if self.Debug then
move.coord:SmokeRed()
end
self.currentMove=move
self:_Move(self.Controllable,move.coord,move.speed,move.onroad)
end
function ARTY:onafterArrived(Controllable,From,Event,To)
self:_EventFromTo("onafterArrived",Event,From,To)
self.Controllable:OptionAlarmStateAuto()
local text=string.format("%s, arrived at destination.",Controllable:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
if self.currentMove then
self:RemoveMove(self.currentMove.name)
self.currentMove=nil
end
end
function ARTY:onafterNewTarget(Controllable,From,Event,To,target)
self:_EventFromTo("onafterNewTarget",Event,From,To)
local text=string.format("Adding new target %s.",target.name)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self:T(self.lid..text)
end
function ARTY:onafterNewMove(Controllable,From,Event,To,move)
self:_EventFromTo("onafterNewTarget",Event,From,To)
local text=string.format("Adding new move %s.",move.name)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self:T(self.lid..text)
end
function ARTY:onafterDead(Controllable,From,Event,To,Unitname)
self:_EventFromTo("onafterDead",Event,From,To)
local nunits=self.Controllable:CountAliveUnits()
local text=string.format("%s, our unit %s just died! %d units left.",self.groupname,Unitname,nunits)
MESSAGE:New(text,5):ToAllIf(self.Debug)
self:I(self.lid..text)
if nunits==0 then
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
if self.respawnafterdeath then
if not self.respawning then
self.respawning=true
self:__Respawn(self.respawndelay or 1)
end
else
self:Stop()
end
end
end
function ARTY:onafterRespawn(Controllable,From,Event,To)
self:_EventFromTo("onafterRespawn",Event,From,To)
self:I("Respawning arty group")
local group=self.Controllable
self.Controllable=group:Respawn()
self.respawning=false
self:__Status(-1)
end
function ARTY:onafterStop(Controllable,From,Event,To)
self:_EventFromTo("onafterStop",Event,From,To)
self:I(self.lid..string.format("Stopping ARTY FSM for group %s.",tostring(Controllable:GetName())))
if self.currentTarget then
self:CeaseFire(self.currentTarget)
end
self:UnHandleEvent(EVENTS.Shot)
self:UnHandleEvent(EVENTS.Dead)
end
function ARTY:_FireAtCoord(coord,radius,nshells,weapontype)
self:F({coord=coord,radius=radius,nshells=nshells})
local group=self.Controllable
if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then
weapontype=ARTY.WeaponType.Cannon
end
if group:HasTask()then
group:ClearTasks()
end
group:OptionROEOpenFire()
local vec2=coord:GetVec2()
local fire=group:TaskFireAtPoint(vec2,radius,nshells,weapontype)
group:SetTask(fire,1)
end
function ARTY:_AttackGroup(target)
local group=self.Controllable
local weapontype=target.weapontype
if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then
weapontype=ARTY.WeaponType.Cannon
end
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,1)
end
function ARTY:_NuclearBlast(_coord)
local S0=self.nukewarhead
local R0=self.nukerange
local N0=self.nukefires
_coord:Explosion(S0)
_coord:BigSmokeAndFireHuge()
local _fires={}
for i=1,N0 do
local _fire=_coord:GetRandomCoordinateInRadius(R0)
local _dist=_fire:Get2DDistance(_coord)
table.insert(_fires,{distance=_dist,coord=_fire})
end
local _sort=function(a,b)return a.distance<b.distance end
table.sort(_fires,_sort)
local function _explosion(R)
local alpha=math.log(100)
local strength=S0*math.exp(-alpha*R/R0)
self:T2(self.lid..string.format("Nuclear explosion strength s(%.1f m) = %.5f (s/s0=%.1f %%), alpha=%.3f",R,strength,strength/S0*100,alpha))
return strength
end
local function ignite(_fires)
for _,fire in pairs(_fires)do
local _fire=fire.coord
local R=_fire:Get2DDistance(_coord)
local S=_explosion(R)
self:T2(self.lid..string.format("Explosion r=%.1f, s=%.3f",R,S))
local _preset=math.random(0,7)
local _density=S/S0
_fire:BigSmokeAndFire(_preset,_density)
_fire:Explosion(S)
end
end
if self.nukefire==true then
ignite(_fires)
end
end
function ARTY:_Move(group,ToCoord,Speed,OnRoad)
self:F2({group=group:GetName(),Speed=Speed,OnRoad=OnRoad})
group:ClearTasks()
group:OptionAlarmStateGreen()
group:OptionROEHoldFire()
local formation="Off Road"
local SpeedMax=group:GetSpeedMax()
Speed=Speed or SpeedMax*0.7
Speed=math.min(Speed,SpeedMax)
local cpini=group:GetCoordinate()
local dist=cpini:Get2DDistance(ToCoord)
local path={}
local task={}
path[#path+1]=cpini:WaypointGround(Speed,formation)
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,false)
if OnRoad then
local _pathonroad=cpini:GetPathOnRoad(ToCoord)
if _pathonroad then
local _first=_pathonroad[1]
local _last=_pathonroad[#_pathonroad]
if self.Debug then
_first:SmokeGreen()
_last:SmokeGreen()
end
path[#path+1]=_first:WaypointGround(Speed,"On Road")
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,false)
path[#path+1]=_last:WaypointGround(Speed,"On Road")
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,false)
end
end
path[#path+1]=ToCoord:WaypointGround(Speed,formation)
task[#task+1]=group:TaskFunction("ARTY._PassingWaypoint",self,#path-1,true)
local Waypoints={}
for i=1,#path do
table.insert(Waypoints,i,path[i])
end
for i=1,#Waypoints do
group:SetTaskWaypoint(Waypoints[i],task[i])
end
group:Route(Waypoints)
end
function ARTY._PassingWaypoint(group,arty,i,final)
if group and group:IsAlive()then
local groupname=tostring(group:GetName())
local text=string.format("%s, passing waypoint %d.",groupname,i)
if final then
text=string.format("%s, arrived at destination.",groupname)
end
arty:T(arty.lid..text)
if final and arty.groupname==groupname then
arty:Arrived()
end
end
end
function ARTY:_Relocate()
local _pos=self.Controllable:GetCoordinate()
local _new=nil
local _gotit=false
local _n=0
local _nmax=1000
repeat
_new=_pos:GetRandomCoordinateInRadius(self.relocateRmax,self.relocateRmin)
local _surface=_new:GetSurfaceType()
if _surface~=land.SurfaceType.WATER and _surface~=land.SurfaceType.SHALLOW_WATER then
_gotit=true
end
_n=_n+1
until _gotit or _n>_nmax
if _gotit then
self:AssignMoveCoord(_new,nil,nil,false,false,"RELOCATION MOVE AFTER FIRING")
end
end
function ARTY:GetAmmo(display)
self:F3({display=display})
if display==nil then
display=false
end
local nammo=0
local nshells=0
local nrockets=0
local nmissiles=0
local nartyshells=0
local units=self.Controllable:GetUnits()
if units==nil then
return nammo,nshells,nrockets,nmissiles
end
for _,_unit in pairs(units)do
local unit=_unit
if unit then
local text=string.format("ARTY group %s - unit %s:\n",self.groupname,unit:GetName())
local ammotable=unit:GetAmmo()
if ammotable~=nil then
local weapons=#ammotable
if display then
self:I(self.lid..string.format("Number of weapons %d.",weapons))
self:I({ammotable=ammotable})
self:I(self.lid.."Ammotable:")
for id,bla in pairs(ammotable)do
self:I({id=id,ammo=bla})
end
end
for w=1,weapons do
local Nammo=ammotable[w]["count"]
local Tammo=ammotable[w]["desc"]["typeName"]
local _weaponString=self:_split(Tammo,"%.")
local _weaponName=_weaponString[#_weaponString]
local Category=ammotable[w].desc.category
local MissileCategory=nil
if Category==Weapon.Category.MISSILE then
MissileCategory=ammotable[w].desc.missileCategory
end
local _gotshell=false
if#self.ammoshells>0 then
for _,_type in pairs(self.ammoshells)do
if string.match(Tammo,_type)and Category==Weapon.Category.SHELL then
_gotshell=true
end
end
else
if Category==Weapon.Category.SHELL then
_gotshell=true
end
end
local _gotrocket=false
if#self.ammorockets>0 then
for _,_type in pairs(self.ammorockets)do
if string.match(Tammo,_type)and Category==Weapon.Category.ROCKET then
_gotrocket=true
end
end
else
if Category==Weapon.Category.ROCKET then
_gotrocket=true
end
end
local _gotmissile=false
if#self.ammomissiles>0 then
for _,_type in pairs(self.ammomissiles)do
if string.match(Tammo,_type)and Category==Weapon.Category.MISSILE then
_gotmissile=true
end
end
else
if Category==Weapon.Category.MISSILE then
_gotmissile=true
end
end
if _gotshell then
nshells=nshells+Nammo
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
text=text..string.format("- %d rockets of type %s\n",Nammo,_weaponName)
elseif _gotmissile then
if MissileCategory==Weapon.MissileCategory.CRUISE then
nmissiles=nmissiles+Nammo
end
text=text..string.format("- %d %s missiles of type %s\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName)
else
text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory))
end
end
end
if display then
self:I(self.lid..text)
else
self:T3(self.lid..text)
end
MESSAGE:New(text,10):ToAllIf(display)
end
end
nammo=nshells+nrockets+nmissiles
return nammo,nshells,nrockets,nmissiles,nartyshells
end
function ARTY:_MissileCategoryName(categorynumber)
local cat="unknown"
if categorynumber==Weapon.MissileCategory.AAM then
cat="air-to-air"
elseif categorynumber==Weapon.MissileCategory.SAM then
cat="surface-to-air"
elseif categorynumber==Weapon.MissileCategory.BM then
cat="ballistic"
elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then
cat="anti-ship"
elseif categorynumber==Weapon.MissileCategory.CRUISE then
cat="cruise"
elseif categorynumber==Weapon.MissileCategory.OTHER then
cat="other"
end
return cat
end
function ARTY:_MarkerKeyAuthentification(text)
local batterycoalition=self.coalition
local mykey=nil
if self.markkey~=nil then
local keywords=self:_split(text,",")
for _,key in pairs(keywords)do
local s=self:_split(key," ")
local val=s[2]
if key:lower():find("key")then
mykey=tonumber(val)
self:T(self.lid..string.format("Authorisation Key=%s.",val))
end
end
end
local _validkey=true
if self.markkey~=nil then
_validkey=false
if mykey~=nil then
_validkey=self.markkey==mykey
end
self:T2(self.lid..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s",self.groupname,tostring(self.markkey),tostring(mykey),tostring(_validkey)))
local text=""
if mykey==nil then
text=string.format("%s, authorization required but did not receive a key!",self.alias)
elseif _validkey==false then
text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!",self.alias,tostring(mykey))
elseif _validkey==true then
text=string.format("%s, authentification successful!",self.alias)
end
MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug)
end
return _validkey
end
function ARTY:_Markertext(text)
self:F(text)
local assignment={}
assignment.battery={}
assignment.aliases={}
assignment.cluster={}
assignment.everyone=false
assignment.move=false
assignment.engage=false
assignment.request=false
assignment.cancel=false
assignment.set=false
assignment.readonly=false
assignment.movecanceltarget=false
assignment.cancelmove=false
assignment.canceltarget=false
assignment.cancelrearm=false
assignment.setrearmingplace=false
assignment.setrearminggroup=false
if text:lower():find("arty engage")or text:lower():find("arty attack")then
assignment.engage=true
elseif text:lower():find("arty move")or text:lower():find("arty relocate")then
assignment.move=true
elseif text:lower():find("arty request")then
assignment.request=true
elseif text:lower():find("arty cancel")then
assignment.cancel=true
elseif text:lower():find("arty set")then
assignment.set=true
else
self:E(self.lid..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" nor "ARTY CANCEL" nor "ARTY SET" keyword specified!')
return nil
end
local keywords=self:_split(text,",")
self:T({keywords=keywords})
for _,keyphrase in pairs(keywords)do
local str=self:_split(keyphrase," ")
local key=str[1]
local val=str[2]
self:T3(self.lid..string.format("%s, keyphrase = %s, key = %s, val = %s",self.groupname,tostring(keyphrase),tostring(key),tostring(val)))
if key:lower():find("battery")then
local v=self:_split(keyphrase,'"')
for i=2,#v,2 do
table.insert(assignment.battery,v[i])
self:T2(self.lid..string.format("Key Battery=%s.",v[i]))
end
elseif key:lower():find("alias")then
local v=self:_split(keyphrase,'"')
for i=2,#v,2 do
table.insert(assignment.aliases,v[i])
self:T2(self.lid..string.format("Key Aliases=%s.",v[i]))
end
elseif key:lower():find("cluster")then
local v=self:_split(keyphrase,'"')
for i=2,#v,2 do
table.insert(assignment.cluster,v[i])
self:T2(self.lid..string.format("Key Cluster=%s.",v[i]))
end
elseif keyphrase:lower():find("everyone")or keyphrase:lower():find("all batteries")or keyphrase:lower():find("allbatteries")then
assignment.everyone=true
self:T(self.lid..string.format("Key Everyone=true."))
elseif keyphrase:lower():find("irrevocable")or keyphrase:lower():find("readonly")then
assignment.readonly=true
self:T2(self.lid..string.format("Key Readonly=true."))
elseif(assignment.engage or assignment.move)and key:lower():find("time")then
if val:lower():find("now")then
assignment.time=self:_SecondsToClock(timer.getTime0()+2)
else
assignment.time=val
end
self:T2(self.lid..string.format("Key Time=%s.",val))
elseif assignment.engage and key:lower():find("shot")then
assignment.nshells=tonumber(val)
self:T(self.lid..string.format("Key Shot=%s.",val))
elseif assignment.engage and key:lower():find("prio")then
assignment.prio=tonumber(val)
self:T2(string.format("Key Prio=%s.",val))
elseif assignment.engage and key:lower():find("maxengage")then
assignment.maxengage=tonumber(val)
self:T2(self.lid..string.format("Key Maxengage=%s.",val))
elseif assignment.engage and key:lower():find("radius")then
assignment.radius=tonumber(val)
self:T2(self.lid..string.format("Key Radius=%s.",val))
elseif assignment.engage and key:lower():find("weapon")then
if val:lower():find("cannon")then
assignment.weapontype=ARTY.WeaponType.Cannon
elseif val:lower():find("rocket")then
assignment.weapontype=ARTY.WeaponType.Rockets
elseif val:lower():find("missile")then
assignment.weapontype=ARTY.WeaponType.CruiseMissile
elseif val:lower():find("nuke")then
assignment.weapontype=ARTY.WeaponType.TacticalNukes
elseif val:lower():find("illu")then
assignment.weapontype=ARTY.WeaponType.IlluminationShells
elseif val:lower():find("smoke")then
assignment.weapontype=ARTY.WeaponType.SmokeShells
else
assignment.weapontype=ARTY.WeaponType.Auto
end
self:T2(self.lid..string.format("Key Weapon=%s.",val))
elseif(assignment.move or assignment.set)and key:lower():find("speed")then
assignment.speed=tonumber(val)
self:T2(self.lid..string.format("Key Speed=%s.",val))
elseif(assignment.move or assignment.set)and(keyphrase:lower():find("on road")or keyphrase:lower():find("onroad")or keyphrase:lower():find("use road"))then
assignment.onroad=true
self:T2(self.lid..string.format("Key Onroad=true."))
elseif assignment.move and(keyphrase:lower():find("cancel target")or keyphrase:lower():find("canceltarget"))then
assignment.movecanceltarget=true
self:T2(self.lid..string.format("Key Cancel Target (before move)=true."))
elseif assignment.request and keyphrase:lower():find("rearm")then
assignment.requestrearming=true
self:T2(self.lid..string.format("Key Request Rearming=true."))
elseif assignment.request and keyphrase:lower():find("ammo")then
assignment.requestammo=true
self:T2(self.lid..string.format("Key Request Ammo=true."))
elseif assignment.request and keyphrase:lower():find("target")then
assignment.requesttargets=true
self:T2(self.lid..string.format("Key Request Targets=true."))
elseif assignment.request and keyphrase:lower():find("status")then
assignment.requeststatus=true
self:T2(self.lid..string.format("Key Request Status=true."))
elseif assignment.request and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then
assignment.requestmoves=true
self:T2(self.lid..string.format("Key Request Moves=true."))
elseif assignment.cancel and(keyphrase:lower():find("engagement")or keyphrase:lower():find("attack")or keyphrase:lower():find("target"))then
assignment.canceltarget=true
self:T2(self.lid..string.format("Key Cancel Target=true."))
elseif assignment.cancel and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then
assignment.cancelmove=true
self:T2(self.lid..string.format("Key Cancel Move=true."))
elseif assignment.cancel and keyphrase:lower():find("rearm")then
assignment.cancelrearm=true
self:T2(self.lid..string.format("Key Cancel Rearm=true."))
elseif assignment.set and keyphrase:lower():find("rearming place")then
assignment.setrearmingplace=true
self:T(self.lid..string.format("Key Set Rearming Place=true."))
elseif assignment.set and keyphrase:lower():find("rearming group")then
local v=self:_split(keyphrase,'"')
local groupname=v[2]
local group=GROUP:FindByName(groupname)
if group and group:IsAlive()then
assignment.setrearminggroup=group
end
self:T2(self.lid..string.format("Key Set Rearming Group = %s.",tostring(groupname)))
elseif key:lower():find("lldms")then
local _flat="%d+:%d+:%d+%s*[N,S]"
local _flon="%d+:%d+:%d+%s*[W,E]"
local _lat=keyphrase:match(_flat)
local _lon=keyphrase:match(_flon)
self:T2(self.lid..string.format("Key LLDMS: lat=%s, long=%s  format=DMS",_lat,_lon))
if _lat and _lon then
local _latitude,_longitude=self:_LLDMS2DD(_lat,_lon)
self:T2(self.lid..string.format("Key LLDMS: lat=%.3f, long=%.3f  format=DD",_latitude,_longitude))
if _latitude and _longitude then
assignment.coord=COORDINATE:NewFromLLDD(_latitude,_longitude)
end
end
end
end
return assignment
end
function ARTY:_MarkRequestAmmo()
self:GetAmmo(true)
end
function ARTY:_MarkRequestStatus()
self:_StatusReport(true)
end
function ARTY:_MarkRequestMoves()
local text=string.format("%s, relocations:",self.groupname)
if#self.moves>0 then
for _,move in pairs(self.moves)do
if self.currentMove and move.name==self.currentMove.name then
text=text..string.format("\n- %s (current)",self:_MoveInfo(move))
else
text=text..string.format("\n- %s",self:_MoveInfo(move))
end
end
else
text=text..string.format("\n- no queued relocations")
end
MESSAGE:New(text,20):Clear():ToCoalition(self.coalition)
end
function ARTY:_MarkRequestTargets()
local text=string.format("%s, targets:",self.groupname)
if#self.targets>0 then
for _,target in pairs(self.targets)do
if self.currentTarget and target.name==self.currentTarget.name then
text=text..string.format("\n- %s (current)",self:_TargetInfo(target))
else
text=text..string.format("\n- %s",self:_TargetInfo(target))
end
end
else
text=text..string.format("\n- no queued targets")
end
MESSAGE:New(text,20):Clear():ToCoalition(self.coalition)
end
function ARTY:_MarkTargetName(markerid)
return string.format("BATTERY=%s, Marked Target ID=%d",self.groupname,markerid)
end
function ARTY:_MarkMoveName(markerid)
return string.format("BATTERY=%s, Marked Relocation ID=%d",self.groupname,markerid)
end
function ARTY:_GetMarkIDfromName(name)
local keywords=self:_split(name,",")
local battery=nil
local markTID=nil
local markMID=nil
for _,key in pairs(keywords)do
local str=self:_split(key,"=")
local par=str[1]
local val=str[2]
if par:find("BATTERY")then
battery=val
end
if par:find("Marked Target ID")then
markTID=tonumber(val)
end
if par:find("Marked Relocation ID")then
markMID=tonumber(val)
end
end
return battery,markTID,markMID
end
function ARTY:_SortTargetQueuePrio()
self:F2()
local function _sort(a,b)
return(a.engaged<b.engaged)or(a.engaged==b.engaged and a.prio<b.prio)
end
table.sort(self.targets,_sort)
self:T3(self.lid.."Sorted targets wrt prio and number of engagements:")
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Target %s",self:_TargetInfo(_target)))
end
end
function ARTY:_SortQueueTime(queue)
self:F3({queue=queue})
local function _sort(a,b)
if a.time==nil and b.time==nil then
return false
end
if a.time==nil then
return false
end
if b.time==nil then
return true
end
return a.time<b.time
end
table.sort(queue,_sort)
self:T3(self.lid.."Sorted queue wrt time:")
for i=1,#queue do
local _queue=queue[i]
local _time=tostring(_queue.time)
local _clock=tostring(self:_SecondsToClock(_queue.time))
self:T3(self.lid..string.format("%s: time=%s, clock=%s",_queue.name,_time,_clock))
end
end
function ARTY:_GetHeading(a,b)
local dx=b.x-a.x
local dy=b.z-a.z
local angle=math.deg(math.atan2(dy,dx))
if angle<0 then
angle=360+angle
end
return angle
end
function ARTY:_CheckTargetsInRange()
local targets2delete={}
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Before: Target %s - in range = %s",_target.name,tostring(_target.inrange)))
local _inrange,_toofar,_tooclose,_remove=self:_TargetInRange(_target)
self:T3(self.lid..string.format("Inbetw: Target %s - in range = %s, toofar = %s, tooclose = %s",_target.name,tostring(_target.inrange),tostring(_toofar),tostring(_tooclose)))
if _remove then
table.insert(targets2delete,_target.name)
else
local _movetowards=false
local _moveaway=false
if _target.inrange==nil then
_target.inrange,_toofar,_tooclose=self:_TargetInRange(_target,self.report or self.Debug)
if _toofar then
_movetowards=true
elseif _tooclose then
_moveaway=true
end
elseif _target.inrange==true then
if _toofar then
_movetowards=true
elseif _tooclose then
_moveaway=true
end
elseif _target.inrange==false then
if _inrange then
local text=string.format("%s, target %s is now in range.",self.alias,_target.name)
self:T(self.lid..text)
MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug)
end
end
if self.autorelocate and(_movetowards or _moveaway)then
local _from=self.Controllable:GetCoordinate()
local _dist=_from:Get2DDistance(_target.coord)
if _dist<=self.autorelocatemaxdist then
local _tocoord
local _name=""
local _safetymargin=500
if _movetowards then
local _waytogo=_dist-self.maxrange+_safetymargin
local _heading=self:_GetHeading(_from,_target.coord)
_tocoord=_from:Translate(_waytogo,_heading)
_name=string.format("%s, relocation to within max firing range of target %s",self.alias,_target.name)
elseif _moveaway then
local _waytogo=_dist-self.minrange+_safetymargin
local _heading=self:_GetHeading(_target.coord,_from)
_tocoord=_from:Translate(_waytogo,_heading)
_name=string.format("%s, relocation to within min firing range of target %s",self.alias,_target.name)
end
MESSAGE:New(_name.." assigned.",10):ToCoalitionIf(self.coalition,self.report or self.Debug)
self:AssignMoveCoord(_tocoord,nil,nil,self.autorelocateonroad,false,_name,true)
end
end
_target.inrange=_inrange
self:T3(self.lid..string.format("After: Target %s - in range = %s",_target.name,tostring(_target.inrange)))
end
end
for _,targetname in pairs(targets2delete)do
self:RemoveTarget(targetname)
end
end
function ARTY:_CheckNormalTargets()
self:F3()
self:_SortTargetQueuePrio()
if self:is("Rearming")then
return nil
end
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Check NORMAL target %d: %s",i,self:_TargetInfo(_target)))
if _target.underfire==false and _target.time==nil and _target.maxengage>_target.engaged and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then
self:T2(self.lid..string.format("Found NORMAL target %s",self:_TargetInfo(_target)))
return _target
end
end
return nil
end
function ARTY:_CheckTimedTargets()
self:F3()
local Tnow=timer.getAbsTime()
self:_SortQueueTime(self.targets)
if self:is("Rearming")then
return nil
end
for i=1,#self.targets do
local _target=self.targets[i]
self:T3(self.lid..string.format("Check TIMED target %d: %s",i,self:_TargetInfo(_target)))
if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then
if self.currentTarget then
if self.currentTarget.prio>_target.prio then
self:T2(self.lid..string.format("Found TIMED HIGH PRIO target %s.",self:_TargetInfo(_target)))
return _target
end
else
self:T2(self.lid..string.format("Found TIMED target %s.",self:_TargetInfo(_target)))
return _target
end
end
end
return nil
end
function ARTY:_CheckMoves()
self:F3()
local Tnow=timer.getAbsTime()
self:_SortQueueTime(self.moves)
local firing=false
if self.currentTarget then
firing=true
end
for i=1,#self.moves do
local _move=self.moves[i]
if string.find(_move.name,"REARMING MOVE")and((self.currentMove and self.currentMove.name~=_move.name)or self.currentMove==nil)then
return _move
elseif(Tnow>=_move.time)and(firing==false or _move.cancel)and(not self.currentMove)and(not self:is("Rearming"))then
return _move
end
end
return nil
end
function ARTY:_CheckShootingStarted()
self:F2()
if self.currentTarget then
local Tnow=timer.getTime()
local name=self.currentTarget.name
local dt=Tnow-self.currentTarget.Tassigned
if self.Nshots==0 then
self:T(self.lid..string.format("%s, waiting for %d seconds for first shot on target %s.",self.groupname,dt,name))
end
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)
end
end
end
function ARTY:_GetTargetIndexByName(name)
self:F2(name)
for i=1,#self.targets do
local targetname=self.targets[i].name
self:T3(self.lid..string.format("Have target with name %s. Index = %d",targetname,i))
if targetname==name then
self:T2(self.lid..string.format("Found target with name %s. Index = %d",name,i))
return i
end
end
self:T2(self.lid..string.format("WARNING: Target with name %s could not be found. (This can happen.)",name))
return nil
end
function ARTY:_GetMoveIndexByName(name)
self:F2(name)
for i=1,#self.moves do
local movename=self.moves[i].name
self:T3(self.lid..string.format("Have move with name %s. Index = %d",movename,i))
if movename==name then
self:T2(self.lid..string.format("Found move with name %s. Index = %d",name,i))
return i
end
end
self:T2(self.lid..string.format("WARNING: Move with name %s could not be found. (This can happen.)",name))
return nil
end
function ARTY:_CheckOutOfAmmo(targets)
local _nammo,_nshells,_nrockets,_nmissiles,_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 _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
self:T(self.lid..string.format("Group %s, tactical nukes requested for target %s but nukes empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu<=0 then
self:T(self.lid..string.format("Group %s, illumination shells requested for target %s but illumination shells empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke<=0 then
self:T(self.lid..string.format("Group %s, smoke shells requested for target %s but smoke shells empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then
self:T(self.lid..string.format("Group %s, rockets requested for target %s but rockets empty.",self.groupname,Target.name))
_partlyoutofammo=true
elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then
self:T(self.lid..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.",self.groupname,Target.name))
_partlyoutofammo=true
end
end
return _partlyoutofammo
end
function ARTY:_CheckWeaponTypeAvailable(target)
local Nammo,Nshells,Nrockets,Nmissiles,Narty=self:GetAmmo()
local nfire=Nammo
if target.weapontype==ARTY.WeaponType.Auto then
nfire=Nammo
elseif target.weapontype==ARTY.WeaponType.Cannon then
nfire=Narty
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
nfire=self.Nukes
elseif target.weapontype==ARTY.WeaponType.IlluminationShells then
nfire=self.Nillu
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
nfire=self.Nsmoke
elseif target.weapontype==ARTY.WeaponType.Rockets then
nfire=Nrockets
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
nfire=Nmissiles
end
return nfire
end
function ARTY:_CheckWeaponTypePossible(target)
local possible=false
if target.weapontype==ARTY.WeaponType.Auto then
possible=self.Nammo0>0
elseif target.weapontype==ARTY.WeaponType.Cannon then
possible=self.Nshells0>0
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
possible=self.Nukes0>0
elseif target.weapontype==ARTY.WeaponType.IlluminationShells then
possible=self.Nillu0>0
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
possible=self.Nsmoke0>0
elseif target.weapontype==ARTY.WeaponType.Rockets then
possible=self.Nrockets0>0
elseif target.weapontype==ARTY.WeaponType.CruiseMissile then
possible=self.Nmissiles0>0
end
return possible
end
function ARTY:_CheckName(givennames,name,makeunique)
self:F2({givennames=givennames,name=name})
local newname=name
local counter=1
local n=1
local nmax=100
if makeunique==nil then
makeunique=true
end
repeat
local _unique=true
for _,_target in pairs(givennames)do
local _givenname=_target.name
if _givenname==newname then
_unique=false
end
self:T3(self.lid..string.format("%d: givenname = %s, newname=%s, unique = %s, makeunique = %s",n,tostring(_givenname),newname,tostring(_unique),tostring(makeunique)))
end
if _unique==false and makeunique==true then
newname=string.format("%s #%02d",name,counter)
counter=counter+1
end
if _unique==false and makeunique==false then
self:T3(self.lid..string.format("Name %s is not unique. Return false.",tostring(newname)))
return name,false
end
n=n+1
until(_unique or n==nmax)
self:T3(self.lid..string.format("Original name %s, new name = %s",name,newname))
return newname,true
end
function ARTY:_TargetInRange(target,message)
self:F3(target)
if message==nil then
message=false
end
self:T3({controllable=self.Controllable,targetcoord=target.coord})
local _dist=self.Controllable:GetCoordinate():Get2DDistance(target.coord)
local _inrange=true
local _tooclose=false
local _toofar=false
local text=""
if _dist<self.minrange then
_inrange=false
_tooclose=true
text=string.format("%s, target is out of range. Distance of %.1f km is below min range of %.1f km.",self.alias,_dist/1000,self.minrange/1000)
elseif _dist>self.maxrange then
_inrange=false
_toofar=true
text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.",self.alias,_dist/1000,self.maxrange/1000)
end
if not _inrange then
self:T(self.lid..text)
MESSAGE:New(text,5):ToCoalitionIf(self.coalition,(self.report and message)or(self.Debug and message))
end
local _remove=false
if not(self.ismobile or self.iscargo)and _inrange==false then
_remove=true
end
return _inrange,_toofar,_tooclose,_remove
end
function ARTY:_WeaponTypeName(tnumber)
self:F2(tnumber)
local name="unknown"
if tnumber==ARTY.WeaponType.Auto then
name="Auto"
elseif tnumber==ARTY.WeaponType.Cannon then
name="Cannons"
elseif tnumber==ARTY.WeaponType.Rockets then
name="Rockets"
elseif tnumber==ARTY.WeaponType.CruiseMissile then
name="Cruise Missiles"
elseif tnumber==ARTY.WeaponType.TacticalNukes then
name="Tactical Nukes"
elseif tnumber==ARTY.WeaponType.IlluminationShells then
name="Illumination Shells"
elseif tnumber==ARTY.WeaponType.SmokeShells then
name="Smoke Shells"
end
return name
end
function ARTY:_VicinityCoord(coord,rmin,rmax)
self:F2({coord=coord,rmin=rmin,rmax=rmax})
rmin=rmin or 20
rmax=rmax or 80
local vec2=coord:GetRandomVec2InRadius(rmax,rmin)
local pops=COORDINATE:NewFromVec2(vec2)
self:T3(self.lid..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)",pops:Get2DDistance(coord),rmin,rmax))
return pops
end
function ARTY:_EventFromTo(BA,Event,From,To)
local text=string.format("%s: %s EVENT %s: %s --> %s",BA,self.groupname,Event,From,To)
self:T3(self.lid..text)
end
function ARTY:_split(str,sep)
self:F3({str=str,sep=sep})
local result={}
local regex=("([^%s]+)"):format(sep)
for each in str:gmatch(regex)do
table.insert(result,each)
end
return result
end
function ARTY:_TargetInfo(target)
local clock=tostring(self:_SecondsToClock(target.time))
local weapon=self:_WeaponTypeName(target.weapontype)
local _underfire=tostring(target.underfire)
return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s, attackgroup=%s",
target.name,target.prio,target.radius,target.nshells,target.engaged,target.maxengage,weapon,clock,_underfire,tostring(target.attackgroup))
end
function ARTY:_MoveInfo(move)
self:F3(move)
local _clock=self:_SecondsToClock(move.time)
return string.format("%s: time=%s, speed=%d, onroad=%s, cancel=%s",move.name,_clock,move.speed,tostring(move.onroad),tostring(move.cancel))
end
function ARTY:_LLDMS2DD(l1,l2)
self:F2(l1,l2)
local _latlong={l1,l2}
local _latitude=nil
local _longitude=nil
for _,ll in pairs(_latlong)do
local _format="%d+:%d+:%d+"
local _ldms=ll:match(_format)
if _ldms then
local _dms=self:_split(_ldms,":")
local _deg=tonumber(_dms[1])
local _min=tonumber(_dms[2])
local _sec=tonumber(_dms[3])
local function DMS2DD(d,m,s)
return d+m/60+s/3600
end
if ll:match("N")then
_latitude=DMS2DD(_deg,_min,_sec)
elseif ll:match("S")then
_latitude=-DMS2DD(_deg,_min,_sec)
elseif ll:match("W")then
_longitude=-DMS2DD(_deg,_min,_sec)
elseif ll:match("E")then
_longitude=DMS2DD(_deg,_min,_sec)
end
local text=string.format("DMS %02d Deg %02d min %02d sec",_deg,_min,_sec)
self:T2(self.lid..text)
end
end
local text=string.format("\nLatitude  %s",tostring(_latitude))
text=text..string.format("\nLongitude %s",tostring(_longitude))
self:T2(self.lid..text)
return _latitude,_longitude
end
function ARTY:_SecondsToClock(seconds)
self:F3({seconds=seconds})
if seconds==nil then
return nil
end
local seconds=tonumber(seconds)
local _seconds=seconds%(60*60*24)
if seconds<=0 then
return nil
else
local hours=string.format("%02.f",math.floor(_seconds/3600))
local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60)))
local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60))
local days=string.format("%d",seconds/(60*60*24))
return hours..":"..mins..":"..secs.."+"..days
end
end
function ARTY:_ClockToSeconds(clock)
self:F3({clock=clock})
if clock==nil then
return nil
end
local seconds=0
local dsplit=self:_split(clock,"+")
if#dsplit>1 then
seconds=seconds+tonumber(dsplit[2])*60*60*24
end
local tsplit=self:_split(dsplit[1],":")
local i=1
for _,time in ipairs(tsplit)do
if i==1 then
seconds=seconds+tonumber(time)*60*60
elseif i==2 then
seconds=seconds+tonumber(time)*60
elseif i==3 then
seconds=seconds+tonumber(time)
end
i=i+1
end
self:T3(self.lid..string.format("Clock %s = %d seconds",clock,seconds))
return seconds
end
SUPPRESSION={
ClassName="SUPPRESSION",
Debug=false,
lid=nil,
flare=false,
smoke=false,
DCSdesc=nil,
Type=nil,
IsInfantry=nil,
SpeedMax=nil,
Tsuppress_ave=15,
Tsuppress_min=5,
Tsuppress_max=25,
TsuppressOver=nil,
IniGroupStrength=nil,
Nhit=0,
Formation="Off road",
Speed=4,
MenuON=false,
FallbackON=false,
FallbackWait=60,
FallbackDist=100,
FallbackHeading=nil,
TakecoverON=false,
TakecoverWait=120,
TakecoverRange=300,
hideout=nil,
PminFlee=10,
PmaxFlee=90,
RetreatZone=nil,
RetreatDamage=nil,
RetreatWait=7200,
CurrentAlarmState="unknown",
CurrentROE="unknown",
DefaultAlarmState="Auto",
DefaultROE="Weapon Free",
eventmoose=true,
waypoints={},
}
SUPPRESSION.ROE={
Hold="Weapon Hold",
Free="Weapon Free",
Return="Return Fire",
}
SUPPRESSION.AlarmState={
Auto="Auto",
Green="Green",
Red="Red",
}
SUPPRESSION.MenuF10=nil
SUPPRESSION.version="0.9.4"
function SUPPRESSION:New(group)
local self=BASE:Inherit(self,FSM_CONTROLLABLE:New())
if group then
self.lid=string.format("SUPPRESSION %s | ",tostring(group:GetName()))
self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s",SUPPRESSION.version,group:GetName()))
else
self:E("SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group)")
return nil
end
if group:IsGround()==false then
self:E(self.lid..string.format("SUPPRESSION fire group %s has to be a GROUND group!",group:GetName()))
return nil
end
self:SetControllable(group)
self.DCSdesc=group:GetDCSDesc(1)
self.SpeedMax=group:GetSpeedMax()
self.Speed=self.SpeedMax
self.IsInfantry=group:GetUnit(1):HasAttribute("Infantry")
self.Type=group:GetTypeName()
self.IniGroupStrength=#group:GetUnits()
self:SetDefaultROE("Free")
self:SetDefaultAlarmState("Auto")
self:AddTransition("*","Start","CombatReady")
self:AddTransition("*","Status","*")
self:AddTransition("CombatReady","Hit","Suppressed")
self:AddTransition("Suppressed","Hit","Suppressed")
self:AddTransition("Suppressed","Recovered","CombatReady")
self:AddTransition("Suppressed","TakeCover","TakingCover")
self:AddTransition("Suppressed","FallBack","FallingBack")
self:AddTransition("*","Retreat","Retreating")
self:AddTransition("TakingCover","FightBack","CombatReady")
self:AddTransition("FallingBack","FightBack","CombatReady")
self:AddTransition("Retreating","Retreated","Retreated")
self:AddTransition("*","OutOfAmmo","*")
self:AddTransition("*","Dead","*")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("TakingCover","Hit","TakingCover")
self:AddTransition("FallingBack","Hit","FallingBack")
return self
end
function SUPPRESSION:SetSuppressionTime(Tave,Tmin,Tmax)
self:F({Tave=Tave,Tmin=Tmin,Tmax=Tmax})
self.Tsuppress_min=Tmin or self.Tsuppress_min
self.Tsuppress_min=math.max(self.Tsuppress_min,1)
self.Tsuppress_max=Tmax or self.Tsuppress_max
self.Tsuppress_max=math.max(self.Tsuppress_max,self.Tsuppress_min)
self.Tsuppress_ave=Tave or self.Tsuppress_ave
self.Tsuppress_ave=math.max(self.Tsuppress_min)
self.Tsuppress_ave=math.min(self.Tsuppress_max)
self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.Tsuppress_ave))
self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.Tsuppress_min))
self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.Tsuppress_max))
end
function SUPPRESSION:SetRetreatZone(zone)
self:F({zone=zone})
self.RetreatZone=zone
end
function SUPPRESSION:DebugOn()
self:F()
self.Debug=true
end
function SUPPRESSION:FlareOn()
self:F()
self.flare=true
end
function SUPPRESSION:SmokeOn()
self:F()
self.smoke=true
end
function SUPPRESSION:SetFormation(formation)
self:F(formation)
self.Formation=formation or"Vee"
end
function SUPPRESSION:SetSpeed(speed)
self:F(speed)
self.Speed=speed or self.SpeedMax
self.Speed=math.min(self.Speed,self.SpeedMax)
end
function SUPPRESSION:Fallback(switch)
self:F(switch)
if switch==nil then
switch=true
end
self.FallbackON=switch
end
function SUPPRESSION:SetFallbackDistance(distance)
self:F(distance)
self.FallbackDist=distance
end
function SUPPRESSION:SetFallbackWait(time)
self:F(time)
self.FallbackWait=time
end
function SUPPRESSION:Takecover(switch)
self:F(switch)
if switch==nil then
switch=true
end
self.TakecoverON=switch
end
function SUPPRESSION:SetTakecoverWait(time)
self:F(time)
self.TakecoverWait=time
end
function SUPPRESSION:SetTakecoverRange(range)
self:F(range)
self.TakecoverRange=range
end
function SUPPRESSION:SetTakecoverPlace(Hideout)
self.hideout=Hideout
end
function SUPPRESSION:SetMinimumFleeProbability(probability)
self:F(probability)
self.PminFlee=probability or 10
end
function SUPPRESSION:SetMaximumFleeProbability(probability)
self:F(probability)
self.PmaxFlee=probability or 90
end
function SUPPRESSION:SetRetreatDamage(damage)
self:F(damage)
self.RetreatDamage=damage or 50
end
function SUPPRESSION:SetRetreatWait(time)
self:F(time)
self.RetreatWait=time or 7200
end
function SUPPRESSION:SetDefaultAlarmState(alarmstate)
self:F(alarmstate)
if alarmstate:lower()=="auto"then
self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto
elseif alarmstate:lower()=="green"then
self.DefaultAlarmState=SUPPRESSION.AlarmState.Green
elseif alarmstate:lower()=="red"then
self.DefaultAlarmState=SUPPRESSION.AlarmState.Red
else
self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto
end
end
function SUPPRESSION:SetDefaultROE(roe)
self:F(roe)
if roe:lower()=="free"then
self.DefaultROE=SUPPRESSION.ROE.Free
elseif roe:lower()=="hold"then
self.DefaultROE=SUPPRESSION.ROE.Hold
elseif roe:lower()=="return"then
self.DefaultROE=SUPPRESSION.ROE.Return
else
self.DefaultROE=SUPPRESSION.ROE.Free
end
end
function SUPPRESSION:MenuOn(switch)
self:F(switch)
if switch==nil then
switch=true
end
self.MenuON=switch
end
function SUPPRESSION:_CreateMenuGroup()
local SubMenuName=self.Controllable:GetName()
local MenuGroup=MENU_MISSION:New(SubMenuName,SUPPRESSION.MenuF10)
MENU_MISSION_COMMAND:New("Fallback!",MenuGroup,self.OrderFallBack,self)
MENU_MISSION_COMMAND:New("Take Cover!",MenuGroup,self.OrderTakeCover,self)
MENU_MISSION_COMMAND:New("Retreat!",MenuGroup,self.OrderRetreat,self)
MENU_MISSION_COMMAND:New("Report Status",MenuGroup,self.Status,self,true)
end
function SUPPRESSION:OrderFallBack()
local group=self.Controllable
local vicinity=group:GetCoordinate():GetRandomVec2InRadius(150,100)
local coord=COORDINATE:NewFromVec2(vicinity)
self:FallBack(self.Controllable)
end
function SUPPRESSION:OrderTakeCover()
local Hideout=self.hideout
if self.hideout==nil then
Hideout=self:_SearchHideout()
end
self:TakeCover(Hideout)
end
function SUPPRESSION:OrderRetreat()
self:Retreat()
end
function SUPPRESSION:StatusReport(message)
local group=self.Controllable
local nunits=group:CountAliveUnits()
local roe=self.CurrentROE
local state=self.CurrentAlarmState
local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife()
local ammotot=group:GetAmmunition()
local detectedG=group:GetDetectedGroupSet():CountAlive()
local detectedU=group:GetDetectedUnitSet():Count()
local text=string.format("State %s, Units=%d/%d, ROE=%s, AlarmState=%s, Hits=%d, Life(min/max/ave/ave0)=%d/%d/%d/%d, Total Ammo=%d, Detected=%d/%d",
self:GetState(),nunits,self.IniGroupStrength,self.CurrentROE,self.CurrentAlarmState,self.Nhit,life_min,life_max,life_ave,life_ave0,ammotot,detectedG,detectedU)
MESSAGE:New(text,10):ToAllIf(message or self.Debug)
self:I(self.lid..text)
end
function SUPPRESSION:onafterStart(Controllable,From,Event,To)
self:_EventFromTo("onafterStart",Event,From,To)
local text=string.format("Started SUPPRESSION for group %s.",Controllable:GetName())
self:I(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
local rzone="not defined"
if self.RetreatZone then
rzone=self.RetreatZone:GetName()
end
if self.RetreatDamage==nil then
if self.RetreatZone then
if self.IniGroupStrength==1 then
self.RetreatDamage=60.0
elseif self.IniGroupStrength==2 then
self.RetreatDamage=50.0
else
self.RetreatDamage=66.5
end
else
self.RetreatDamage=100
end
end
if self.MenuON then
if not SUPPRESSION.MenuF10 then
SUPPRESSION.MenuF10=MENU_MISSION:New("Suppression")
end
self:_CreateMenuGroup()
end
self:_SetAlarmState(self.DefaultAlarmState)
self:_SetROE(self.DefaultROE)
local text=string.format("\n******************************************************\n")
text=text..string.format("Suppressed group   = %s\n",Controllable:GetName())
text=text..string.format("Type               = %s\n",self.Type)
text=text..string.format("IsInfantry         = %s\n",tostring(self.IsInfantry))
text=text..string.format("Group strength     = %d\n",self.IniGroupStrength)
text=text..string.format("Average time       = %5.1f seconds\n",self.Tsuppress_ave)
text=text..string.format("Minimum time       = %5.1f seconds\n",self.Tsuppress_min)
text=text..string.format("Maximum time       = %5.1f seconds\n",self.Tsuppress_max)
text=text..string.format("Default ROE        = %s\n",self.DefaultROE)
text=text..string.format("Default AlarmState = %s\n",self.DefaultAlarmState)
text=text..string.format("Fall back ON       = %s\n",tostring(self.FallbackON))
text=text..string.format("Fall back distance = %5.1f m\n",self.FallbackDist)
text=text..string.format("Fall back wait     = %5.1f seconds\n",self.FallbackWait)
text=text..string.format("Fall back heading  = %s degrees\n",tostring(self.FallbackHeading))
text=text..string.format("Take cover ON      = %s\n",tostring(self.TakecoverON))
text=text..string.format("Take cover search  = %5.1f m\n",self.TakecoverRange)
text=text..string.format("Take cover wait    = %5.1f seconds\n",self.TakecoverWait)
text=text..string.format("Min flee probability = %5.1f\n",self.PminFlee)
text=text..string.format("Max flee probability = %5.1f\n",self.PmaxFlee)
text=text..string.format("Retreat zone       = %s\n",rzone)
text=text..string.format("Retreat damage     = %5.1f %%\n",self.RetreatDamage)
text=text..string.format("Retreat wait       = %5.1f seconds\n",self.RetreatWait)
text=text..string.format("Speed              = %5.1f km/h\n",self.Speed)
text=text..string.format("Speed max          = %5.1f km/h\n",self.SpeedMax)
text=text..string.format("Formation          = %s\n",self.Formation)
text=text..string.format("******************************************************\n")
self:T(self.lid..text)
if self.eventmoose then
self:HandleEvent(EVENTS.Hit,self._OnEventHit)
self:HandleEvent(EVENTS.Dead,self._OnEventDead)
else
world.addEventHandler(self)
end
self:__Status(-1)
end
function SUPPRESSION:onafterStatus(Controllable,From,Event,To)
local group=self.Controllable
if group then
local nunits=group:CountAliveUnits()
if nunits>0 then
local nammo=group:GetAmmunition()
if nammo==0 then
self:OutOfAmmo()
end
self:StatusReport(false)
if self:GetState()~="Stopped"then
self:__Status(-30)
end
else
self:Stop()
end
else
self:Stop()
end
end
function SUPPRESSION:onafterHit(Controllable,From,Event,To,Unit,AttackUnit)
self:_EventFromTo("onafterHit",Event,From,To)
if From=="CombatReady"or From=="Suppressed"then
self:_Suppress()
end
local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife()
local Damage=100-life_ave0
local RetreatCondition=Damage>=self.RetreatDamage-0.01 and self.RetreatZone
local Pflee=(self.PmaxFlee-self.PminFlee)/self.RetreatDamage*math.min(Damage,self.RetreatDamage)+self.PminFlee
local P=math.random(0,100)
local FleeCondition=P<Pflee
local text
text=string.format("\nGroup %s: Life min=%5.1f, max=%5.1f, ave=%5.1f, ave0=%5.1f group=%5.1f\n",Controllable:GetName(),life_min,life_max,life_ave,life_ave0,groupstrength)
text=string.format("Group %s: Damage = %8.4f (%8.4f retreat threshold).\n",Controllable:GetName(),Damage,self.RetreatDamage)
text=string.format("Group %s: P_Flee = %5.1f %5.1f=P_rand (P_Flee > Prand ==> Flee)\n",Controllable:GetName(),Pflee,P)
self:T(self.lid..text)
if Damage>=99.9 then
return
end
if RetreatCondition then
self:Retreat()
elseif FleeCondition then
if self.FallbackON and AttackUnit:IsGround()then
self:FallBack(AttackUnit)
elseif self.TakecoverON then
local Hideout=self.hideout
if self.hideout==nil then
Hideout=self:_SearchHideout()
end
self:TakeCover(Hideout)
end
end
end
function SUPPRESSION:onbeforeRecovered(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRecovered",Event,From,To)
local Tnow=timer.getTime()
self:T(self.lid..string.format("onbeforeRecovered: Time now: %d  - Time over: %d",Tnow,self.TsuppressionOver))
if Tnow>=self.TsuppressionOver then
return true
else
return false
end
end
function SUPPRESSION:onafterRecovered(Controllable,From,Event,To)
self:_EventFromTo("onafterRecovered",Event,From,To)
if Controllable and Controllable:IsAlive()then
local text=string.format("Group %s has recovered!",Controllable:GetName())
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
self:_SetROE()
if self.flare or self.Debug then
Controllable:FlareGreen()
end
end
end
function SUPPRESSION:onafterFightBack(Controllable,From,Event,To)
self:_EventFromTo("onafterFightBack",Event,From,To)
self:_SetROE()
self:_SetAlarmState()
local group=Controllable
local Waypoints=group:GetTemplateRoutePoints()
group:Route(Waypoints,5)
end
function SUPPRESSION:onbeforeFallBack(Controllable,From,Event,To,AttackUnit)
self:_EventFromTo("onbeforeFallBack",Event,From,To)
if From=="FallingBack"then
return false
else
return true
end
end
function SUPPRESSION:onafterFallBack(Controllable,From,Event,To,AttackUnit)
self:_EventFromTo("onafterFallback",Event,From,To)
self:T(self.lid..string.format("Group %s is falling back after %d hits.",Controllable:GetName(),self.Nhit))
local ACoord=AttackUnit:GetCoordinate()
local DCoord=Controllable:GetCoordinate()
local heading=self:_Heading(ACoord,DCoord)
if self.FallbackHeading then
heading=self.FallbackHeading
end
local Coord=DCoord:Translate(self.FallbackDist,heading)
if self.Debug then
local MarkerID=Coord:MarkToAll("Fall back position for group "..Controllable:GetName())
end
if self.smoke or self.Debug then
Coord:SmokeBlue()
end
self:_SetROE(SUPPRESSION.ROE.Hold)
self:_SetAlarmState(SUPPRESSION.AlarmState.Auto)
self:_Run(Coord,self.Speed,self.Formation,self.FallbackWait)
end
function SUPPRESSION:onbeforeTakeCover(Controllable,From,Event,To,Hideout)
self:_EventFromTo("onbeforeTakeCover",Event,From,To)
if From=="TakingCover"then
return false
end
if Hideout~=nil then
return true
else
return false
end
end
function SUPPRESSION:onafterTakeCover(Controllable,From,Event,To,Hideout)
self:_EventFromTo("onafterTakeCover",Event,From,To)
if self.Debug then
local MarkerID=Hideout:MarkToAll(string.format("Hideout for group %s",Controllable:GetName()))
end
if self.smoke or self.Debug then
Hideout:SmokeBlue()
end
self:_SetROE(SUPPRESSION.ROE.Hold)
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
self:_Run(Hideout,self.Speed,self.Formation,self.TakecoverWait)
end
function SUPPRESSION:onafterOutOfAmmo(Controllable,From,Event,To)
self:_EventFromTo("onafterOutOfAmmo",Event,From,To)
self:I(self.lid..string.format("Out of ammo!"))
if self.RetreatZone then
self:Retreat()
end
end
function SUPPRESSION:onbeforeRetreat(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRetreat",Event,From,To)
if From=="Retreating"then
local text=string.format("Group %s is already retreating.",tostring(Controllable:GetName()))
self:T2(self.lid..text)
return false
else
return true
end
end
function SUPPRESSION:onafterRetreat(Controllable,From,Event,To)
self:_EventFromTo("onafterRetreat",Event,From,To)
local text=string.format("Group %s is retreating! Alarm state green.",Controllable:GetName())
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
local ZoneCoord=self.RetreatZone:GetRandomCoordinate()
local ZoneVec2=ZoneCoord:GetVec2()
if self.smoke or self.Debug then
ZoneCoord:SmokeBlue()
end
if self.Debug then
self.RetreatZone:SmokeZone(SMOKECOLOR.Red,12)
end
self:_SetROE(SUPPRESSION.ROE.Hold)
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
self:_Run(ZoneCoord,self.Speed,self.Formation,self.RetreatWait)
end
function SUPPRESSION:onbeforeRetreated(Controllable,From,Event,To)
self:_EventFromTo("onbeforeRetreated",Event,From,To)
local inzone=self.RetreatZone:IsVec3InZone(Controllable:GetVec3())
return inzone
end
function SUPPRESSION:onafterRetreated(Controllable,From,Event,To)
self:_EventFromTo("onafterRetreated",Event,From,To)
self:_SetROE(SUPPRESSION.ROE.Return)
self:_SetAlarmState(SUPPRESSION.AlarmState.Auto)
end
function SUPPRESSION:onafterDead(Controllable,From,Event,To)
self:_EventFromTo("onafterDead",Event,From,To)
local group=self.Controllable
if group then
local nunits=group:CountAliveUnits()
local text=string.format("Group %s: One of our units just died! %d units left.",self.Controllable:GetName(),nunits)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
if nunits==0 then
self:Stop()
end
else
self:Stop()
end
end
function SUPPRESSION:onafterStop(Controllable,From,Event,To)
self:_EventFromTo("onafterStop",Event,From,To)
local text=string.format("Stopping SUPPRESSION for group %s",self.Controllable:GetName())
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
self.CallScheduler:Clear()
if self.mooseevents then
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Hit)
else
world.removeEventHandler(self)
end
end
function SUPPRESSION:onEvent(Event)
if Event==nil or Event.initiator==nil or Unit.getByName(Event.initiator:getName())==nil then
return true
end
local EventData={}
if Event.initiator then
EventData.IniDCSUnit=Event.initiator
EventData.IniUnitName=Event.initiator:getName()
EventData.IniDCSGroup=Event.initiator:getGroup()
EventData.IniGroupName=Event.initiator:getGroup():getName()
EventData.IniGroup=GROUP:FindByName(EventData.IniGroupName)
EventData.IniUnit=UNIT:FindByName(EventData.IniUnitName)
end
if Event.target then
EventData.TgtDCSUnit=Event.target
EventData.TgtUnitName=Event.target:getName()
EventData.TgtDCSGroup=Event.target:getGroup()
EventData.TgtGroupName=Event.target:getGroup():getName()
EventData.TgtGroup=GROUP:FindByName(EventData.TgtGroupName)
EventData.TgtUnit=UNIT:FindByName(EventData.TgtUnitName)
end
if Event.id==world.event.S_EVENT_HIT then
self:_OnEventHit(EventData)
end
if Event.id==world.event.S_EVENT_DEAD then
self:_OnEventDead(EventData)
end
end
function SUPPRESSION:_OnEventHit(EventData)
self:F3(EventData)
local GroupNameSelf=self.Controllable:GetName()
local GroupNameTgt=EventData.TgtGroupName
local TgtUnit=EventData.TgtUnit
local tgt=EventData.TgtDCSUnit
local IniUnit=EventData.IniUnit
if GroupNameTgt==GroupNameSelf then
self:T(self.lid..string.format("Hit event at t = %5.1f",timer.getTime()))
if self.flare or self.Debug then
TgtUnit:FlareRed()
end
self.Nhit=self.Nhit+1
self:T(self.lid..string.format("Group %s has just been hit %d times.",self.Controllable:GetName(),self.Nhit))
local life=tgt:getLife()/(tgt:getLife0()+1)*100
self:T2(self.lid..string.format("Target unit life = %5.1f",life))
self:__Hit(3,TgtUnit,IniUnit)
end
end
function SUPPRESSION:_OnEventDead(EventData)
local GroupNameSelf=self.Controllable:GetName()
local GroupNameIni=EventData.IniGroupName
if GroupNameIni==GroupNameSelf then
local IniUnit=EventData.IniUnit
local IniUnitName=EventData.IniUnitName
if EventData.IniUnit then
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName))
else
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.",GroupNameIni,IniUnitName))
end
if EventData.IniDCSUnit then
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.",GroupNameIni,IniUnitName))
else
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.",GroupNameIni,IniUnitName))
end
if IniUnit and(self.flare or self.Debug)then
IniUnit:FlareWhite()
self:T(self.lid..string.format("Flare Dead MOOSE unit."))
end
if EventData.IniDCSUnit and(self.flare or self.Debug)then
local p=EventData.IniDCSUnit:getPosition().p
trigger.action.signalFlare(p,trigger.flareColor.Yellow,0)
self:T(self.lid..string.format("Flare Dead DCS unit."))
end
self:Status()
self:__Dead(0.1)
end
end
function SUPPRESSION:_Suppress()
local Tnow=timer.getTime()
local Controllable=self.Controllable
self:_SetROE(SUPPRESSION.ROE.Hold)
local sigma=(self.Tsuppress_max-self.Tsuppress_min)/4
local Tsuppress=self:_Random_Gaussian(self.Tsuppress_ave,sigma,self.Tsuppress_min,self.Tsuppress_max)
local renew=true
if self.TsuppressionOver~=nil then
if Tsuppress+Tnow>self.TsuppressionOver then
self.TsuppressionOver=Tnow+Tsuppress
else
renew=false
end
else
self.TsuppressionOver=Tnow+Tsuppress
end
if renew then
self:__Recovered(self.TsuppressionOver-Tnow)
end
local text=string.format("Group %s is suppressed for %d seconds. Suppression ends at %d:%02d.",Controllable:GetName(),Tsuppress,self.TsuppressionOver/60,self.TsuppressionOver%60)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:T(self.lid..text)
end
function SUPPRESSION:_Run(fin,speed,formation,wait)
speed=speed or 20
formation=formation or ENUMS.Formation.Vehicle.OffRoad
wait=wait or 30
local group=self.Controllable
if group and group:IsAlive()then
local ini=group:GetCoordinate()
local dist=ini:Get2DDistance(fin)
local heading=self:_Heading(ini,fin)
local wp={}
local tasks={}
wp[1]=ini:WaypointGround(speed,formation)
if self.Debug then
local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)",#wp,self.Controllable:GetName()))
end
local ConditionWait=group:TaskCondition(nil,nil,nil,nil,wait,nil)
local TaskHold=group:TaskHold()
local TaskComboFin={}
TaskComboFin[#TaskComboFin+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint",self,#wp,true)
TaskComboFin[#TaskComboFin+1]=group:TaskControlled(TaskHold,ConditionWait)
wp[#wp+1]=fin:WaypointGround(speed,formation,TaskComboFin)
if self.Debug then
local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)",#wp,self.Controllable:GetName()))
end
group:Route(wp)
else
self:E(self.lid..string.format("ERROR: Group is not alive!"))
end
end
function SUPPRESSION._Passing_Waypoint(group,Fsm,i,final)
local text=string.format("Group %s passing waypoint %d (final=%s)",group:GetName(),i,tostring(final))
MESSAGE:New(text,10):ToAllIf(Fsm.Debug)
if Fsm.Debug then
env.info(Fsm.lid..text)
end
if final then
if Fsm:is("Retreating")then
Fsm:Retreated()
else
Fsm:FightBack()
end
end
end
function SUPPRESSION:_SearchHideout()
local Zone=ZONE_GROUP:New("Zone_Hiding",self.Controllable,self.TakecoverRange)
local gpos=self.Controllable:GetCoordinate()
Zone:Scan(Object.Category.SCENERY)
local hideouts={}
for SceneryTypeName,SceneryData in pairs(Zone:GetScannedScenery())do
for SceneryName,SceneryObject in pairs(SceneryData)do
local SceneryObject=SceneryObject
local spos=SceneryObject:GetCoordinate()
local distance=spos:Get2DDistance(gpos)
if self.Debug then
local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s",self.Controllable:GetName(),SceneryObject:GetTypeName()))
local text=string.format("%s scenery: %s, Coord %s",self.Controllable:GetName(),SceneryObject:GetTypeName(),SceneryObject:GetCoordinate():ToStringLLDMS())
self:T2(self.lid..text)
end
table.insert(hideouts,{object=SceneryObject,distance=distance})
end
end
local Hideout=nil
if#hideouts>0 then
self:T(self.lid.."Number of hideouts "..#hideouts)
local _sort=function(a,b)return a.distance<b.distance end
table.sort(hideouts,_sort)
Hideout=hideouts[1].object:GetCoordinate()
else
self:E(self.lid.."No hideouts found!")
end
return Hideout
end
function SUPPRESSION:_GetLife()
local group=self.Controllable
if group and group:IsAlive()then
local units=group:GetUnits()
local life_min=nil
local life_max=nil
local life_ave=0
local life_ave0=0
local n=0
local groupstrength=#units/self.IniGroupStrength*100
self:T2(self.lid..string.format("Group %s _GetLife nunits = %d",self.Controllable:GetName(),#units))
for _,unit in pairs(units)do
local unit=unit
if unit and unit:IsAlive()then
n=n+1
local life=unit:GetLife()/(unit:GetLife0()+1)*100
if life_min==nil or life<life_min then
life_min=life
end
if life_max==nil or life>life_max then
life_max=life
end
life_ave=life_ave+life
if self.Debug then
local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f",n,unit:GetLife(),unit:GetLife0(),life_min,life_max,life_ave/n,groupstrength)
self:T2(self.lid..text)
end
end
end
if n==0 then
return 0,0,0,0,0
end
life_ave0=life_ave/self.IniGroupStrength
life_ave=life_ave/n
return life_min,life_max,life_ave,life_ave0,groupstrength
else
return 0,0,0,0,0
end
end
function SUPPRESSION:_Heading(a,b)
local dx=b.x-a.x
local dy=b.z-a.z
local angle=math.deg(math.atan2(dy,dx))
if angle<0 then
angle=360+angle
end
return angle
end
function SUPPRESSION:_Random_Gaussian(x0,sigma,xmin,xmax)
sigma=sigma or 5
local r
local gotit=false
local i=0
while not gotit do
local x1=math.random()
local x2=math.random()
r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0
i=i+1
if(r>=xmin and r<=xmax)or i>100 then
gotit=true
end
end
return r
end
function SUPPRESSION:_SetROE(roe)
local group=self.Controllable
roe=roe or self.DefaultROE
self.CurrentROE=roe
if roe==SUPPRESSION.ROE.Free then
group:OptionROEOpenFire()
elseif roe==SUPPRESSION.ROE.Hold then
group:OptionROEHoldFire()
elseif roe==SUPPRESSION.ROE.Return then
group:OptionROEReturnFire()
else
self:E(self.lid.."Unknown ROE requested: "..tostring(roe))
group:OptionROEOpenFire()
self.CurrentROE=SUPPRESSION.ROE.Free
end
local text=string.format("Group %s now has ROE %s.",self.Controllable:GetName(),self.CurrentROE)
self:T(self.lid..text)
end
function SUPPRESSION:_SetAlarmState(state)
local group=self.Controllable
state=state or self.DefaultAlarmState
self.CurrentAlarmState=state
if state==SUPPRESSION.AlarmState.Auto then
group:OptionAlarmStateAuto()
elseif state==SUPPRESSION.AlarmState.Green then
group:OptionAlarmStateGreen()
elseif state==SUPPRESSION.AlarmState.Red then
group:OptionAlarmStateRed()
else
self:E(self.lid.."Unknown alarm state requested: "..tostring(state))
group:OptionAlarmStateAuto()
self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto
end
local text=string.format("Group %s now has Alarm State %s.",self.Controllable:GetName(),self.CurrentAlarmState)
self:T(self.lid..text)
end
function SUPPRESSION:_EventFromTo(BA,Event,From,To)
local text=string.format("\n%s: %s EVENT %s: %s --> %s",BA,self.Controllable:GetName(),Event,From,To)
self:T2(self.lid..text)
end
PSEUDOATC={
ClassName="PSEUDOATC",
group={},
Debug=false,
mdur=30,
mrefresh=120,
talt=3,
chatty=true,
eventsmoose=true,
reportplayername=false,
}
PSEUDOATC.id="PseudoATC | "
PSEUDOATC.version="0.10.5"
function PSEUDOATC:New()
local self=BASE:Inherit(self,BASE:New())
self:E(PSEUDOATC.id..string.format("PseudoATC version %s",PSEUDOATC.version))
return self
end
function PSEUDOATC:Start()
self:F()
self:I(PSEUDOATC.id.."Starting PseudoATC")
self:HandleEvent(EVENTS.Birth,self._OnBirth)
self:HandleEvent(EVENTS.Land,self._PlayerLanded)
self:HandleEvent(EVENTS.Takeoff,self._PlayerTakeOff)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft)
self:HandleEvent(EVENTS.Crash,self._PlayerLeft)
end
function PSEUDOATC:DebugOn()
self.Debug=true
end
function PSEUDOATC:DebugOff()
self.Debug=false
end
function PSEUDOATC:ChattyOn()
self.chatty=true
end
function PSEUDOATC:ChattyOff()
self.chatty=false
end
function PSEUDOATC:SetMessageDuration(duration)
self.mdur=duration or 30
end
function PSEUDOATC:SetReportPlayername()
self.reportplayername=true
return self
end
function PSEUDOATC:SetMenuRefresh(interval)
self.mrefresh=interval or 120
end
function PSEUDOATC:SetEventsMoose(switch)
self.eventsmoose=switch
end
function PSEUDOATC:SetReportAltInterval(interval)
self.talt=interval or 3
end
function PSEUDOATC:_OnBirth(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit=EventData.IniUnit
local _playername=EventData.IniPlayerName
if _unit and _playername then
self:PlayerEntered(_unit)
end
end
function PSEUDOATC:_PlayerLeft(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit=EventData.IniUnit
local _playername=EventData.IniPlayerName
if _unit and _playername then
self:PlayerLeft(_unit)
end
end
function PSEUDOATC:_PlayerLanded(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit=EventData.IniUnit
local _playername=EventData.IniPlayerName
local _base=nil
local _baseName=nil
if EventData.place then
_base=EventData.place
_baseName=EventData.place:getName()
end
if _unit and _playername and _base then
self:PlayerLanded(_unit,_baseName)
end
end
function PSEUDOATC:_PlayerTakeOff(EventData)
self:F({EventData=EventData})
local _unitName=EventData.IniUnitName
local _unit=EventData.IniUnit
local _playername=EventData.IniPlayerName
local _base=nil
local _baseName=nil
if EventData.place then
_base=EventData.place
_baseName=EventData.place:getName()
end
if _unit and _playername and _base then
self:PlayerTakeOff(_unit,_baseName)
end
end
function PSEUDOATC:PlayerEntered(unit)
self:F2({unit=unit})
local group=unit:GetGroup()
local GID=group:GetID()
local GroupName=group:GetName()
local PlayerName=unit:GetPlayerName()
local UnitName=unit:GetName()
local CallSign=unit:GetCallsign()
local UID=unit:GetDCSObject():getID()
if not self.group[GID]then
self.group[GID]={}
self.group[GID].player={}
end
self.group[GID].player[UID]={}
self.group[GID].player[UID].group=group
self.group[GID].player[UID].unit=unit
self.group[GID].player[UID].groupname=GroupName
self.group[GID].player[UID].unitname=UnitName
self.group[GID].player[UID].playername=PlayerName
self.group[GID].player[UID].callsign=CallSign
self.group[GID].player[UID].waypoints=group:GetTaskRoute()
local text=string.format("Player %s entered unit %s of group %s (id=%d).",PlayerName,UnitName,GroupName,GID)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
local countPlayerInGroup=0
for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end
if countPlayerInGroup<=1 then
self.group[GID].menu_main=missionCommands.addSubMenuForGroup(GID,"Pseudo ATC")
end
self:MenuCreatePlayer(GID,UID)
self:LocalAirports(GID,UID)
self:MenuAirports(GID,UID)
self:MenuWaypoints(GID,UID)
self.group[GID].player[UID].scheduler,self.group[GID].player[UID].schedulerid=SCHEDULER:New(nil,self.MenuRefresh,{self,GID,UID},self.mrefresh,self.mrefresh)
end
function PSEUDOATC:PlayerLanded(unit,place)
self:F2({unit=unit,place=place})
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=unit:GetPlayerName()or"Ghost"
local UnitName=unit:GetName()or"Ghostplane"
local GroupName=group:GetName()or"Ghostgroup"
if self.Debug then
local text=string.format("Player %s in unit %s of group %s landed at %s.",PlayerName,UnitName,GroupName,place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
end
self:AltitudeTimerStop(GID,UID)
if place and self.chatty then
local text=string.format("Touchdown! Welcome to %s pilot %s. Have a nice day!",place,PlayerName)
MESSAGE:New(text,self.mdur):ToGroup(group)
end
end
function PSEUDOATC:PlayerTakeOff(unit,place)
self:F2({unit=unit,place=place})
local group=unit:GetGroup()
local PlayerName=unit:GetPlayerName()or"Ghost"
local UnitName=unit:GetName()or"Ghostplane"
local GroupName=group:GetName()or"Ghostgroup"
local CallSign=unit:GetCallsign()or"Ghost11"
if self.Debug then
local text=string.format("Player %s in unit %s of group %s took off at %s.",PlayerName,UnitName,GroupName,place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
end
if place and self.chatty then
local text=string.format("%s, %s, you are airborne. Have a safe trip!",place,CallSign)
if self.reportplayername then
text=string.format("%s, %s, you are airborne. Have a safe trip!",place,PlayerName)
end
MESSAGE:New(text,self.mdur):ToGroup(group)
end
end
function PSEUDOATC:PlayerLeft(unit)
self:F({unit=unit})
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
if self.group[GID]and self.group[GID].player and self.group[GID].player[UID]then
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
local text=string.format("Player %s (callsign %s) of group %s just left unit %s.",PlayerName,CallSign,GroupName,UnitName)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
if self.group[GID].player[UID].schedulerid then
self.group[GID].player[UID].scheduler:Stop(self.group[GID].player[UID].schedulerid)
end
self:AltitudeTimerStop(GID,UID)
if self.group[GID].player[UID].menu_own then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_own)
end
local countPlayerInGroup=0
for _ in pairs(self.group[GID].player)do countPlayerInGroup=countPlayerInGroup+1 end
if self.group[GID].menu_main and countPlayerInGroup==1 then
missionCommands.removeItemForGroup(GID,self.group[GID].menu_main)
end
self.group[GID].player[UID]=nil
end
end
function PSEUDOATC:MenuRefresh(GID,UID)
self:F({GID=GID,UID=UID})
local text=string.format("Refreshing menues for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:MenuClear(GID,UID)
self:LocalAirports(GID,UID)
self:MenuAirports(GID,UID)
self:MenuWaypoints(GID,UID)
end
function PSEUDOATC:MenuCreatePlayer(GID,UID)
self:F({GID=GID,UID=UID})
local PlayerName=self.group[GID].player[UID].playername
self.group[GID].player[UID].menu_own=missionCommands.addSubMenuForGroup(GID,PlayerName,self.group[GID].menu_main)
end
function PSEUDOATC:MenuClear(GID,UID)
self:F({GID=GID,UID=UID})
local text=string.format("Clearing menus for player %s in group %s.",self.group[GID].player[UID].playername,self.group[GID].player[UID].groupname)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text,30):ToAllIf(self.Debug)
if self.group[GID].player[UID].menu_airports then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_airports)
self.group[GID].player[UID].menu_airports=nil
else
self:T2(PSEUDOATC.id.."No airports to clear menus.")
end
if self.group[GID].player[UID].menu_waypoints then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_waypoints)
self.group[GID].player[UID].menu_waypoints=nil
end
if self.group[GID].player[UID].menu_reportalt then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_reportalt)
self.group[GID].player[UID].menu_reportalt=nil
end
if self.group[GID].player[UID].menu_requestalt then
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_requestalt)
self.group[GID].player[UID].menu_requestalt=nil
end
end
function PSEUDOATC:MenuAirports(GID,UID)
self:F({GID=GID,UID=UID})
self.group[GID].player[UID].menu_airports=missionCommands.addSubMenuForGroup(GID,"Local Airports",self.group[GID].player[UID].menu_own)
local i=0
for _,airport in pairs(self.group[GID].player[UID].airports)do
i=i+1
if i>10 then
break
end
local name=airport.name
local d=airport.distance
local pos=AIRBASE:FindByName(name):GetCoordinate()
local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_airports)
missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name)
missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name)
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d",name,GID))
end
end
function PSEUDOATC:MenuWaypoints(GID,UID)
self:F({GID=GID,UID=UID})
local callsign=self.group[GID].player[UID].callsign
self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).",callsign,GID))
if#self.group[GID].player[UID].waypoints>0 then
self.group[GID].player[UID].menu_waypoints=missionCommands.addSubMenuForGroup(GID,"Waypoints",self.group[GID].player[UID].menu_own)
local j=0
for i,wp in pairs(self.group[GID].player[UID].waypoints)do
j=j+1
if j>10 then
break
end
local pos=COORDINATE:New(wp.x,wp.alt,wp.y)
local name=string.format("Waypoint %d",i-1)
if wp.name and wp.name~=""then
name=string.format("Waypoint %s",wp.name)
end
local submenu=missionCommands.addSubMenuForGroup(GID,name,self.group[GID].player[UID].menu_waypoints)
missionCommands.addCommandForGroup(GID,"Weather Report",submenu,self.ReportWeather,self,GID,UID,pos,name)
missionCommands.addCommandForGroup(GID,"Request BR",submenu,self.ReportBR,self,GID,UID,pos,name)
end
end
self.group[GID].player[UID].menu_reportalt=missionCommands.addCommandForGroup(GID,"Talk me down",self.group[GID].player[UID].menu_own,self.AltidudeTimerToggle,self,GID,UID)
self.group[GID].player[UID].menu_requestalt=missionCommands.addCommandForGroup(GID,"Request altitude",self.group[GID].player[UID].menu_own,self.ReportHeight,self,GID,UID)
end
function PSEUDOATC:ReportWeather(GID,UID,position,location)
self:F({GID=GID,UID=UID,position=position,location=location})
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS
local text=string.format("Local weather at %s:\n",location)
local Pqnh=position:GetPressure(0)
local Pqfe=position:GetPressure()
local hPa2inHg=0.0295299830714
local hPa2mmHg=0.7500615613030
local _Pqnh=string.format("%.2f inHg",Pqnh*hPa2inHg)
local _Pqfe=string.format("%.2f inHg",Pqfe*hPa2inHg)
if settings:IsMetric()then
_Pqnh=string.format("%.1f mmHg",Pqnh*hPa2mmHg)
_Pqfe=string.format("%.1f mmHg",Pqfe*hPa2mmHg)
end
text=text..string.format("QFE %.1f hPa = %s.\n",Pqfe,_Pqfe)
text=text..string.format("QNH %.1f hPa = %s.\n",Pqnh,_Pqnh)
local T=position:GetTemperature()
local _T=string.format('%d°F',UTILS.CelsiusToFahrenheit(T))
if settings:IsMetric()then
_T=string.format('%d°C',T)
end
local text=text..string.format("Temperature %s\n",_T)
local Dir,Vel=position:GetWind()
local Bn,Bd=UTILS.BeaufortScale(Vel)
local Ds=string.format('%03d°',Dir)
local Vs=string.format("%.1f knots",UTILS.MpsToKnots(Vel))
if settings:IsMetric()then
Vs=string.format('%.1f m/s',Vel)
end
local text=text..string.format("%s, Wind from %s at %s (%s).",self.group[GID].player[UID].playername,Ds,Vs,Bd)
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true)
end
function PSEUDOATC:ReportBR(GID,UID,position,location)
self:F({GID=GID,UID=UID,position=position,location=location})
local unit=self.group[GID].player[UID].unit
local coord=unit:GetCoordinate()
local angle=coord:HeadingTo(position)
local range=coord:Get2DDistance(position)
local Bs=string.format('%03d°',angle)
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS
local Rs=string.format("%.1f NM",UTILS.MetersToNM(range))
if settings:IsMetric()then
Rs=string.format("%.1f km",range/1000)
end
local text=string.format("%s: Bearing %s, Range %s.",location,Bs,Rs)
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,text,self.mdur,true)
end
function PSEUDOATC:ReportHeight(GID,UID,dt,_clear)
self:F({GID=GID,UID=UID,dt=dt})
local dt=dt or self.mdur
if _clear==nil then
_clear=false
end
local function get_AGL(p)
local agl=0
local vec2={x=p.x,y=p.z}
local ground=land.getHeight(vec2)
local agl=p.y-ground
return agl
end
local unit=self.group[GID].player[UID].unit
if unit and unit:IsAlive()then
local position=unit:GetCoordinate()
local height=get_AGL(position)
local callsign=unit:GetCallsign()
local PlayerName=self.group[GID].player[UID].playername
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername)or _SETTINGS
local Hs=string.format("%d ft",UTILS.MetersToFeet(height))
if settings:IsMetric()then
Hs=string.format("%d m",height)
end
local _text=string.format("%s, your altitude is %s AGL.",callsign,Hs)
if self.reportplayername then
_text=string.format("%s, your altitude is %s AGL.",PlayerName,Hs)
end
if _clear==false then
_text=_text..string.format(" FL%03d.",position.y/30.48)
end
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,_text,dt,_clear)
return height
end
return 0
end
function PSEUDOATC:AltidudeTimerToggle(GID,UID)
self:F({GID=GID,UID=UID})
if self.group[GID].player[UID].altimerid then
self:AltitudeTimerStop(GID,UID)
else
self:AltitudeTimeStart(GID,UID)
end
end
function PSEUDOATC:AltitudeTimeStart(GID,UID)
self:F({GID=GID,UID=UID})
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.",UID))
self.group[GID].player[UID].altimer,self.group[GID].player[UID].altimerid=SCHEDULER:New(nil,self.ReportHeight,{self,GID,UID,1,true},1,3)
end
function PSEUDOATC:AltitudeTimerStop(GID,UID)
self:F({GID=GID,UID=UID})
self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.",UID))
if self.group[GID].player[UID].altimerid then
self.group[GID].player[UID].altimer:Stop(self.group[GID].player[UID].altimerid)
end
self.group[GID].player[UID].altimer=nil
self.group[GID].player[UID].altimerid=nil
end
function PSEUDOATC:LocalAirports(GID,UID)
self:F({GID=GID,UID=UID})
self.group[GID].player[UID].airports=nil
self.group[GID].player[UID].airports={}
local pos=self.group[GID].player[UID].unit:GetCoordinate()
for i=0,2 do
local airports=coalition.getAirbases(i)
for _,airbase in pairs(airports)do
local name=airbase:getName()
local a=AIRBASE:FindByName(name)
if a then
local q=a:GetCoordinate()
local d=q:Get2DDistance(pos)
table.insert(self.group[GID].player[UID].airports,{distance=d,name=name})
end
end
end
local function compare(a,b)
return a.distance<b.distance
end
table.sort(self.group[GID].player[UID].airports,compare)
end
function PSEUDOATC:_GetPlayerUnitAndName(_unitName)
self:F(_unitName)
if _unitName~=nil then
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit,unit=unit,playername=playername})
if unit and playername then
return unit,playername
end
end
end
return nil,nil
end
function PSEUDOATC:_DisplayMessageToGroup(_unit,_text,_time,_clear)
self:F({unit=_unit,text=_text,time=_time,clear=_clear})
_time=_time or self.Tmsg
if _clear==nil then
_clear=false
end
local _gid=_unit:GetGroup():GetID()
if _gid then
if _clear==true then
trigger.action.outTextForGroup(_gid,_text,_time,_clear)
else
trigger.action.outTextForGroup(_gid,_text,_time)
end
end
end
function PSEUDOATC:_myname(unitname)
self:F2(unitname)
local unit=UNIT:FindByName(unitname)
local pname=unit:GetPlayerName()
local csign=unit:GetCallsign()
return string.format("%s (%s)",csign,pname)
end
WAREHOUSE={
ClassName="WAREHOUSE",
Debug=false,
verbosity=0,
lid=nil,
Report=true,
warehouse=nil,
alias=nil,
zone=nil,
airbase=nil,
airbasename=nil,
road=nil,
rail=nil,
spawnzone=nil,
uid=nil,
dTstatus=30,
queueid=0,
stock={},
queue={},
pending={},
transporting={},
delivered={},
defending={},
portzone=nil,
harborzone=nil,
shippinglanes={},
offroadpaths={},
autodefence=false,
spawnzonemaxdist=5000,
autosave=false,
autosavepath=nil,
autosavefile=nil,
saveparking=false,
isUnit=false,
isShip=false,
lowfuelthresh=0.15,
respawnafterdestroyed=false,
respawndelay=nil,
}
WAREHOUSE.Descriptor={
GROUPNAME="templatename",
UNITTYPE="unittype",
ATTRIBUTE="attribute",
CATEGORY="category",
ASSIGNMENT="assignment",
ASSETLIST="assetlist,"
}
WAREHOUSE.Attribute={
AIR_TRANSPORTPLANE="Air_TransportPlane",
AIR_AWACS="Air_AWACS",
AIR_FIGHTER="Air_Fighter",
AIR_BOMBER="Air_Bomber",
AIR_TANKER="Air_Tanker",
AIR_TRANSPORTHELO="Air_TransportHelo",
AIR_ATTACKHELO="Air_AttackHelo",
AIR_UAV="Air_UAV",
AIR_OTHER="Air_OtherAir",
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
GROUND_EWR="Ground_EWR",
GROUND_AAA="Ground_AAA",
GROUND_SAM="Ground_SAM",
GROUND_OTHER="Ground_OtherGround",
NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier",
NAVAL_WARSHIP="Naval_WarShip",
NAVAL_ARMEDSHIP="Naval_ArmedShip",
NAVAL_UNARMEDSHIP="Naval_UnarmedShip",
NAVAL_OTHER="Naval_OtherNaval",
OTHER_UNKNOWN="Other_Unknown",
}
WAREHOUSE.TransportType={
AIRPLANE="Air_TransportPlane",
HELICOPTER="Air_TransportHelo",
APC="Ground_APC",
TRAIN="Ground_Train",
SHIP="Naval_UnarmedShip",
AIRCRAFTCARRIER="Naval_AircraftCarrier",
WARSHIP="Naval_WarShip",
ARMEDSHIP="Naval_ArmedShip",
SELFPROPELLED="Selfpropelled",
}
WAREHOUSE.Quantity={
ALL="all",
THREEQUARTERS="3/4",
HALF="1/2",
THIRD="1/3",
QUARTER="1/4",
}
_WAREHOUSEDB={
AssetID=0,
Assets={},
WarehouseID=0,
Warehouses={}
}
WAREHOUSE.version="1.0.2a"
function WAREHOUSE:New(warehouse,alias)
local self=BASE:Inherit(self,FSM:New())
if type(warehouse)=="string"then
local warehousename=warehouse
warehouse=UNIT:FindByName(warehousename)
if warehouse==nil then
warehouse=STATIC:FindByName(warehousename,true)
end
end
if warehouse==nil then
env.error("ERROR: Warehouse does not exist!")
return nil
end
if warehouse:IsInstanceOf("STATIC")then
self.isUnit=false
elseif warehouse:IsInstanceOf("UNIT")then
self.isUnit=true
if warehouse:IsShip()then
self.isShip=true
end
else
env.error("ERROR: Warehouse is neither STATIC nor UNIT object!")
return nil
end
self.alias=alias or warehouse:GetName()
self.lid=string.format("WAREHOUSE %s | ",self.alias)
self:I(self.lid..string.format("Adding warehouse v%s for structure %s [isUnit=%s, isShip=%s]",WAREHOUSE.version,warehouse:GetName(),tostring(self:IsUnit()),tostring(self:IsShip())))
self.warehouse=warehouse
_WAREHOUSEDB.WarehouseID=_WAREHOUSEDB.WarehouseID+1
self.uid=_WAREHOUSEDB.WarehouseID
self.coalition=self.warehouse:GetCoalition()
self.countryid=self.warehouse:GetCountry()
local _airbase=self:GetCoordinate():GetClosestAirbase(nil,self:GetCoalition())
if _airbase and _airbase:GetCoordinate():Get2DDistance(self:GetCoordinate())<=5000 then
self:SetAirbase(_airbase)
end
if self.isShip then
self.zone=ZONE_AIRBASE:New(self.warehouse:GetName(),1000)
self.spawnzone=ZONE_AIRBASE:New(self.warehouse:GetName(),1000)
else
self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s",self.warehouse:GetName()),warehouse:GetVec2(),500)
self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone",self.warehouse:GetName()),warehouse:GetVec2(),250)
end
self:SetMarker(true)
self:SetReportOff()
self:SetRunwayRepairtime()
self.allowSpawnOnClientSpots=false
_WAREHOUSEDB.Warehouses[self.uid]=self
self:SetStartState("NotReadyYet")
self:AddTransition("NotReadyYet","Load","Loaded")
self:AddTransition("Stopped","Load","Loaded")
self:AddTransition("NotReadyYet","Start","Running")
self:AddTransition("Loaded","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","AddAsset","*")
self:AddTransition("*","NewAsset","*")
self:AddTransition("*","AddRequest","*")
self:AddTransition("Running","Request","*")
self:AddTransition("Running","RequestSpawned","*")
self:AddTransition("Attacked","Request","*")
self:AddTransition("*","Unloaded","*")
self:AddTransition("*","AssetSpawned","*")
self:AddTransition("*","AssetLowFuel","*")
self:AddTransition("*","Arrived","*")
self:AddTransition("*","Delivered","*")
self:AddTransition("Running","SelfRequest","*")
self:AddTransition("Attacked","SelfRequest","*")
self:AddTransition("Running","Pause","Paused")
self:AddTransition("Paused","Unpause","Running")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("Stopped","Restart","Running")
self:AddTransition("Loaded","Restart","Running")
self:AddTransition("*","Save","*")
self:AddTransition("*","Attacked","Attacked")
self:AddTransition("Attacked","Defeated","Running")
self:AddTransition("*","ChangeCountry","*")
self:AddTransition("Attacked","Captured","Running")
self:AddTransition("*","AirbaseCaptured","*")
self:AddTransition("*","AirbaseRecaptured","*")
self:AddTransition("*","RunwayDestroyed","*")
self:AddTransition("*","RunwayRepaired","*")
self:AddTransition("*","AssetDead","*")
self:AddTransition("*","Destroyed","Destroyed")
self:AddTransition("Destroyed","Respawn","Running")
return self
end
function WAREHOUSE:SetDebugOn()
self.Debug=true
return self
end
function WAREHOUSE:SetDebugOff()
self.Debug=false
return self
end
function WAREHOUSE:SetReportOn()
self.Report=true
return self
end
function WAREHOUSE:SetReportOff()
self.Report=false
return self
end
function WAREHOUSE:SetSafeParkingOn()
self.safeparking=true
return self
end
function WAREHOUSE:SetSafeParkingOff()
self.safeparking=false
return self
end
function WAREHOUSE:SetAllowSpawnOnClientParking()
self.allowSpawnOnClientSpots=true
return self
end
function WAREHOUSE:SetLowFuelThreshold(threshold)
self.lowfuelthresh=threshold or 0.15
return self
end
function WAREHOUSE:SetStatusUpdate(timeinterval)
self.dTstatus=timeinterval
return self
end
function WAREHOUSE:SetVerbosityLevel(VerbosityLevel)
self.verbosity=VerbosityLevel or 0
return self
end
function WAREHOUSE:SetSpawnZone(zone,maxdist)
self.spawnzone=zone
self.spawnzonemaxdist=maxdist or 5000
return self
end
function WAREHOUSE:GetSpawnZone()
return self.spawnzone
end
function WAREHOUSE:SetWarehouseZone(zone)
self.zone=zone
return self
end
function WAREHOUSE:GetWarehouseZone()
return self.zone
end
function WAREHOUSE:SetAutoDefenceOn()
self.autodefence=true
return self
end
function WAREHOUSE:SetAutoDefenceOff()
self.autodefence=false
return self
end
function WAREHOUSE:SetParkingIDs(ParkingIDs)
if type(ParkingIDs)~="table"then
ParkingIDs={ParkingIDs}
end
self.parkingIDs=ParkingIDs
return self
end
function WAREHOUSE:_CheckParkingValid(spot)
if self.parkingIDs==nil then
return true
end
for _,id in pairs(self.parkingIDs or{})do
if spot.TerminalID==id then
return true
end
end
return false
end
function WAREHOUSE:_CheckParkingAsset(spot,asset)
if asset.parkingIDs==nil then
return true
end
for _,id in pairs(asset.parkingIDs or{})do
if spot.TerminalID==id then
return true
end
end
return false
end
function WAREHOUSE:SetSaveOnMissionEnd(path,filename)
self.autosave=true
self.autosavepath=path
self.autosavefile=filename
return self
end
function WAREHOUSE:SetMarker(switch)
if switch==false then
self.markerOn=false
else
self.markerOn=true
end
return self
end
function WAREHOUSE:SetRespawnAfterDestroyed(delay)
self.respawnafterdestroyed=true
self.respawndelay=delay
return self
end
function WAREHOUSE:SetAirbase(airbase)
self.airbase=airbase
if airbase~=nil then
self.airbasename=airbase:GetName()
else
self.airbasename=nil
end
return self
end
function WAREHOUSE:SetRoadConnection(coordinate)
if coordinate then
self.road=coordinate:GetClosestPointToRoad()
else
self.road=false
end
return self
end
function WAREHOUSE:SetRailConnection(coordinate)
if coordinate then
self.rail=coordinate:GetClosestPointToRoad(true)
else
self.rail=false
end
return self
end
function WAREHOUSE:SetPortZone(zone)
self.portzone=zone
return self
end
function WAREHOUSE:SetHarborZone(zone)
self.harborzone=zone
return self
end
function WAREHOUSE:AddShippingLane(remotewarehouse,group,oneway)
if self.portzone==nil or remotewarehouse.portzone==nil then
local text=string.format("ERROR: Sending or receiving warehouse does not have a port zone defined. Adding shipping lane not possible!")
self:_ErrorMessage(text,5)
return self
end
local startcoord=self.portzone:GetRandomCoordinate()
local finalcoord=remotewarehouse.portzone:GetRandomCoordinate()
local lane=self:_NewLane(group,startcoord,finalcoord)
if self.Debug then
for i=1,#lane do
local coord=lane[i]
local text=string.format("Shipping lane %s to %s. Point %d.",self.alias,remotewarehouse.alias,i)
coord:MarkToCoalition(text,self:GetCoalition())
end
end
local remotename=remotewarehouse.warehouse:GetName()
if self.shippinglanes[remotename]==nil then
self.shippinglanes[remotename]={}
end
table.insert(self.shippinglanes[remotename],lane)
if not oneway then
remotewarehouse:AddShippingLane(self,group,true)
end
return self
end
function WAREHOUSE:AddOffRoadPath(remotewarehouse,group,oneway)
local startcoord=self.spawnzone:GetRandomCoordinate()
local finalcoord=remotewarehouse.spawnzone:GetRandomCoordinate()
local path=self:_NewLane(group,startcoord,finalcoord)
if path==nil then
self:E(self.lid.."ERROR: Offroad path could not be added. Group present in ME?")
return
end
if path and self.Debug then
for i=1,#path do
local coord=path[i]
local text=string.format("Off road path from %s to %s. Point %d.",self.alias,remotewarehouse.alias,i)
coord:MarkToCoalition(text,self:GetCoalition())
end
end
local remotename=remotewarehouse.warehouse:GetName()
if self.offroadpaths[remotename]==nil then
self.offroadpaths[remotename]={}
end
table.insert(self.offroadpaths[remotename],path)
if not oneway then
remotewarehouse:AddOffRoadPath(self,group,true)
end
return self
end
function WAREHOUSE:_NewLane(group,startcoord,finalcoord)
local lane=nil
if group then
local lanepoints=group:GetTemplateRoutePoints()
local laneF=lanepoints[1]
local laneL=lanepoints[#lanepoints]
local coordF=COORDINATE:New(laneF.x,0,laneF.y)
local coordL=COORDINATE:New(laneL.x,0,laneL.y)
local distF=startcoord:Get2DDistance(coordF)
local distL=startcoord:Get2DDistance(coordL)
lane={}
if distF<distL then
for i=1,#lanepoints do
local point=lanepoints[i]
local coord=COORDINATE:New(point.x,0,point.y)
table.insert(lane,coord)
end
else
for i=#lanepoints,1,-1 do
local point=lanepoints[i]
local coord=COORDINATE:New(point.x,0,point.y)
table.insert(lane,coord)
end
end
table.insert(lane,#lane,finalcoord)
end
return lane
end
function WAREHOUSE:IsNotReadyYet()
return self:is("NotReadyYet")
end
function WAREHOUSE:IsLoaded()
return self:is("Loaded")
end
function WAREHOUSE:IsRunning()
return self:is("Running")
end
function WAREHOUSE:IsPaused()
return self:is("Paused")
end
function WAREHOUSE:IsAttacked()
return self:is("Attacked")
end
function WAREHOUSE:IsDestroyed()
return self:is("Destroyed")
end
function WAREHOUSE:IsStopped()
return self:is("Stopped")
end
function WAREHOUSE:HasConnectionRoad(warehouse,markpath,smokepath)
if warehouse then
if self.road and warehouse.road then
local _,length,gotpath=self.road:GetPathOnRoad(warehouse.road,false,false,markpath,smokepath)
return gotpath,length or-1
else
return false,-1
end
end
return nil,-1
end
function WAREHOUSE:HasConnectionRail(warehouse,markpath,smokepath)
if warehouse then
if self.rail and warehouse.rail then
local _,length,gotpath=self.road:GetPathOnRoad(warehouse.road,false,true,markpath,smokepath)
return gotpath,length or-1
else
return false,-1
end
end
return nil,-1
end
function WAREHOUSE:HasConnectionNaval(warehouse,markpath,smokepath)
if warehouse then
if warehouse.warehouse:GetName()==self.warehouse:GetName()then
return true,1
end
local shippinglane=self.shippinglanes[warehouse.warehouse:GetName()]
if shippinglane then
return true,1
else
self:T2(string.format("No shipping lane defined between warehouse %s and %s!",self.alias,warehouse.alias))
end
end
return nil,-1
end
function WAREHOUSE:HasConnectionOffRoad(warehouse,markpath,smokepath)
if warehouse then
if warehouse.warehouse:GetName()==self.warehouse:GetName()then
return true,1
end
local offroadpath=self.offroadpaths[warehouse.warehouse:GetName()]
if offroadpath~=nil then
return true,1
else
self:T2(string.format("No off-road path defined between warehouse %s and %s!",self.alias,warehouse.alias))
end
end
return nil,-1
end
function WAREHOUSE:GetNumberOfAssets(Descriptor,DescriptorValue,OnlyMobile)
if Descriptor==nil or DescriptorValue==nil then
return#self.stock
else
local _stock,_nstock=self:_FilterStock(self.stock,Descriptor,DescriptorValue,nil,OnlyMobile)
return _nstock
end
end
function WAREHOUSE:GetCoordinate()
return self.warehouse:GetCoordinate()
end
function WAREHOUSE:GetVec3()
local vec3=self.warehouse:GetVec3()
return vec3
end
function WAREHOUSE:GetVec2()
local vec2=self.warehouse:GetVec2()
return vec2
end
function WAREHOUSE:GetCoalition()
return self.warehouse:GetCoalition()
end
function WAREHOUSE:GetCoalitionName()
return self.warehouse:GetCoalitionName()
end
function WAREHOUSE:GetCountry()
return self.warehouse:GetCountry()
end
function WAREHOUSE:GetCountryName()
return self.warehouse:GetCountryName()
end
function WAREHOUSE:GetAirbase()
return self.airbase
end
function WAREHOUSE:GetAirbaseName()
local name="none"
if self.airbase then
name=self.airbase:GetName()
end
return name
end
function WAREHOUSE:GetAirbaseCategory()
local category=-1
if self.airbase then
category=self.airbase:GetAirbaseCategory()
end
return category
end
function WAREHOUSE:GetAssignment(request)
return tostring(request.assignment)
end
function WAREHOUSE:FindWarehouseInDB(uid)
return _WAREHOUSEDB.Warehouses[uid]
end
function WAREHOUSE:FindNearestWarehouse(MinAssets,Descriptor,DescriptorValue,Coalition,RefCoordinate)
if Descriptor~=nil and DescriptorValue~=nil then
MinAssets=MinAssets or 1
else
MinAssets=MinAssets or 0
end
local anycoalition=nil
if Coalition~=nil then
if Coalition==false then
anycoalition=true
else
end
else
if self~=nil then
Coalition=self:GetCoalition()
else
anycoalition=true
end
end
local coord=RefCoordinate or self:GetCoordinate()
local nearest=nil
local distmin=nil
for wid,warehouse in pairs(_WAREHOUSEDB.Warehouses)do
local warehouse=warehouse
local dist=coord:Get2DDistance(warehouse:GetCoordinate())
if dist>0 then
local samecoalition=anycoalition or Coalition==warehouse:GetCoalition()
if samecoalition and not(warehouse:IsNotReadyYet()or warehouse:IsStopped()or warehouse:IsDestroyed())then
local nassets=warehouse:GetNumberOfAssets(Descriptor,DescriptorValue)
local enough=true
if Descriptor and DescriptorValue then
enough=nassets>=MinAssets
end
if enough and(distmin==nil or dist<distmin)then
distmin=dist
nearest=warehouse
end
end
end
end
return nearest,distmin
end
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
return asset
end
self:_ErrorMessage(string.format("ERROR: Group %s does not contain an asset ID in its name!",group:GetName()),0)
return nil
end
function WAREHOUSE:IsRunwayOperational()
if self.airbase then
if self.runwaydestroyed then
return false
else
return true
end
end
return nil
end
function WAREHOUSE:SetRunwayRepairtime(RepairTime)
self.runwayrepairtime=RepairTime or 3600
return self
end
function WAREHOUSE:GetRunwayRepairtime()
if self.runwaydestroyed then
local Tnow=timer.getAbsTime()
local Tsince=Tnow-self.runwaydestroyed
local Trepair=math.max(self.runwayrepairtime-Tsince,0)
return Trepair
end
return 0
end
function WAREHOUSE:IsUnit()
return self.isUnit
end
function WAREHOUSE:IsStatic()
return not self.isUnit
end
function WAREHOUSE:IsShip()
return self.isShip
end
function WAREHOUSE:onafterStart(From,Event,To)
local text=string.format("Starting warehouse %s alias %s:\n",self.warehouse:GetName(),self.alias)
text=text..string.format("Coalition = %s\n",self:GetCoalitionName())
text=text..string.format("Country  = %s\n",self:GetCountryName())
text=text..string.format("Airbase  = %s (category=%d)\n",self:GetAirbaseName(),self:GetAirbaseCategory())
env.info(text)
self.warehouse:SetState(self.warehouse,"WAREHOUSE",self)
local _road=self.spawnzone:GetCoordinate():GetClosestPointToRoad()
if _road and self.road==nil then
local _Droad=_road:Get2DDistance(self.spawnzone:GetCoordinate())
if _Droad<3000 then
self.road=_road
end
end
if self.road and self.markerOn then
self.markroad=self.road:MarkToCoalition(string.format("%s road connection.",self.alias),self:GetCoalition(),true)
end
local _rail=self.spawnzone:GetCoordinate():GetClosestPointToRoad(true)
if _rail and self.rail==nil then
local _Drail=_rail:Get2DDistance(self.spawnzone:GetCoordinate())
if _Drail<3000 then
self.rail=_rail
end
end
if self.rail and self.markerOn then
self.markrail=self.rail:MarkToCoalition(string.format("%s rail connection.",self.alias),self:GetCoalition(),true)
end
self:HandleEvent(EVENTS.Birth,self._OnEventBirth)
self:HandleEvent(EVENTS.EngineStartup,self._OnEventEngineStartup)
self:HandleEvent(EVENTS.Takeoff,self._OnEventTakeOff)
self:HandleEvent(EVENTS.Land,self._OnEventLanding)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventEngineShutdown)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.BaseCaptured,self._OnEventBaseCaptured)
self:HandleEvent(EVENTS.MissionEnd,self._OnEventMissionEnd)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventArrived)
self:__Status(-1)
end
function WAREHOUSE:onafterRestart(From,Event,To)
self:I(self.lid..string.format("Restarting Warehouse %s.",self.alias))
self:HandleEvent(EVENTS.Birth,self._OnEventBirth)
self:HandleEvent(EVENTS.EngineStartup,self._OnEventEngineStartup)
self:HandleEvent(EVENTS.Takeoff,self._OnEventTakeOff)
self:HandleEvent(EVENTS.Land,self._OnEventLanding)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventEngineShutdown)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.BaseCaptured,self._OnEventBaseCaptured)
self:HandleEvent(EVENTS.EngineShutdown,self._OnEventArrived)
self:__Status(-1)
end
function WAREHOUSE:onafterStop(From,Event,To)
self:_InfoMessage(string.format("Warehouse %s stopped!",self.alias))
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.EngineStartup)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.BaseCaptured)
self.pending=nil
self.pending={}
self.queue=nil
self.queue={}
self.stock=nil
self.stock={}
self:_UpdateWarehouseMarkText()
self.CallScheduler:Clear()
end
function WAREHOUSE:onafterPause(From,Event,To)
self:I(self.lid..string.format("Warehouse %s paused! Queued requests are not processed in this state.",self.alias))
end
function WAREHOUSE:onafterUnpause(From,Event,To)
self:I(self.lid..string.format("Warehouse %s unpaused! Processing of requests is resumed.",self.alias))
end
function WAREHOUSE:onafterStatus(From,Event,To)
if self.verbosity>=1 then
local FSMstate=self:GetState()
local coalition=self:GetCoalitionName()
local country=self:GetCountryName()
self:I(self.lid..string.format("State=%s %s [%s]: Assets=%d,  Requests: waiting=%d, pending=%d",FSMstate,country,coalition,#self.stock,#self.queue,#self.pending))
end
self:_JobDone()
self:_DisplayStatus()
self:_CheckConquered()
if self:IsRunwayOperational()==false then
local Trepair=self:GetRunwayRepairtime()
self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec",Trepair))
if Trepair==0 then
self.runwaydestroyed=nil
self:RunwayRepaired()
end
end
self:_CheckRequestConsistancy(self.queue)
if self:IsRunning()or self:IsAttacked()then
local request=self:_CheckQueue()
if request then
self:Request(request)
end
end
if self.verbosity>2 then
self:_PrintQueue(self.queue,"Queue waiting")
self:_PrintQueue(self.pending,"Queue pending")
end
self:_UpdateWarehouseMarkText()
if self.Debug then
self:_DisplayStockItems(self.stock)
end
self:__Status(-self.dTstatus)
end
function WAREHOUSE:_JobDone()
local done={}
for _,request in pairs(self.pending)do
local request=request
if request.born then
local ncargo=0
if request.cargogroupset then
ncargo=request.cargogroupset:Count()
end
local ntransport=0
if request.transportgroupset then
ntransport=request.transportgroupset:Count()
end
local ncargotot=request.nasset
local ncargodelivered=request.ndelivered
local ncargodead=ncargotot-ncargodelivered-ncargo
local ntransporttot=request.ntransport
local ntransporthome=request.ntransporthome
local ntransportdead=ntransporttot-ntransporthome-ntransport
local text=string.format("Request id=%d: Cargo: Ntot=%d, Nalive=%d, Ndelivered=%d, Ndead=%d  |  Transport: Ntot=%d, Nalive=%d, Nhome=%d, Ndead=%d",
request.uid,ncargotot,ncargo,ncargodelivered,ncargodead,ntransporttot,ntransport,ntransporthome,ntransportdead)
self:T(self.lid..text)
if ncargo==0 then
if not self.delivered[request.uid]then
self:Delivered(request)
end
if ntransport==0 then
if self.verbosity>=1 then
local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n",self.alias,request.uid,request.warehouse.alias)
text=text..string.format("- %d of %d assets delivered. Casualties %d.",ncargodelivered,ncargotot,ncargodead)
if request.ntransport>0 then
text=text..string.format("\n- %d of %d transports returned home. Casualties %d.",ntransporthome,ntransporttot,ntransportdead)
end
self:_InfoMessage(text,20)
end
table.insert(done,request)
else
for _,_group in pairs(request.transportgroupset:GetSetObjects())do
local group=_group
if group and group:IsAlive()then
local category=group:GetCategory()
local speed=group:GetVelocityKMH()
local notmoving=speed<1
local airbase=group:GetCoordinate():GetClosestAirbase():GetName()
local athomebase=self.airbase and self.airbase:GetName()==airbase
local onground=not group:InAir()
local inspawnzone=group:IsPartlyOrCompletelyInZone(self.spawnzone)
local ishome=false
if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then
ishome=inspawnzone and onground and notmoving
elseif category==Group.Category.AIRPLANE then
ishome=athomebase and onground and notmoving
end
local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s",group:GetName(),speed,tostring(onground),airbase,tostring(inspawnzone),tostring(ishome))
self:T(self.lid..text)
if ishome then
local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.",self.alias,request.uid,group:GetName())
self:T(self.lid..text)
if self.Debug then
group:SmokeRed()
end
self:Arrived(group)
end
end
end
end
else
if ntransport==0 and request.ntransport>0 then
local ncargoalive=0
for _,_group in pairs(request.cargogroupset:GetSetObjects())do
local groupname=_group:GetName()
local group=GROUP:FindByName(groupname.."#CARGO")
if group and group:IsAlive()then
if group:IsPartlyOrCompletelyInZone(self.spawnzone)then
if self.Debug then
group:SmokeBlue()
end
self:AddAsset(group)
ncargoalive=ncargoalive+1
end
end
end
self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!",self.alias,request.uid,ncargoalive))
end
end
end
end
for _,request in pairs(done)do
self:_DeleteQueueItem(request,self.pending)
end
end
function WAREHOUSE:_CheckAssetStatus()
local function _CheckGroup(_request,_group)
local request=_request
local group=_group
if group and group:IsAlive()then
local category=group:GetCategory()
for _,_unit in pairs(group:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
local unitid=unit:GetID()
local life9=unit:GetLife()
local life0=unit:GetLife0()
local life=life9/life0*100
local speed=unit:GetVelocityMPS()
local onground=unit:InAir()
local problem=false
if life<10 then
self:T(string.format("Unit %s is heavily damaged!",unit:GetName()))
end
if speed<1 and unit:GetSpeedMax()>1 and onground then
self:T(string.format("Unit %s is not moving!",unit:GetName()))
problem=true
end
if problem then
if request.assetproblem[unitid]then
local deltaT=timer.getAbsTime()-request.assetproblem[unitid]
if deltaT>300 then
unit:Destroy()
end
else
request.assetproblem[unitid]=timer.getAbsTime()
end
end
end
end
end
end
for _,request in pairs(self.pending)do
local request=request
if request.cargogroupset then
for _,_group in pairs(request.cargogroupset:GetSet())do
local group=_group
_CheckGroup(request,group)
end
end
if request.transportgroupset then
for _,group in pairs(request.transportgroupset:GetSet())do
_CheckGroup(request,group)
end
end
end
end
function WAREHOUSE:onafterAddAsset(From,Event,To,group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,skill,liveries,assignment,other)
self:T({group=group,ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight})
local n=ngroups or 1
if type(group)=="string"then
group=GROUP:FindByName(group)
end
if liveries and type(liveries)=="string"then
liveries={liveries}
end
if group then
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid and aid and rid then
local warehouse=self:FindWarehouseInDB(wid)
if warehouse then
local request=warehouse:_GetRequestOfGroup(group,warehouse.pending)
if request then
local istransport=warehouse:_GroupIsTransport(group,request)
if istransport==true then
request.ntransporthome=request.ntransporthome+1
request.transportgroupset:Remove(group:GetName(),true)
local ntrans=request.transportgroupset:Count()
self:T2(warehouse.lid..string.format("Transport %d of %s returned home. TransportSet=%d",request.ntransporthome,tostring(request.ntransport),ntrans))
elseif istransport==false then
request.ndelivered=request.ndelivered+1
local namewo=self:_GetNameWithOut(group)
request.cargogroupset:Remove(namewo,true)
local ncargo=request.cargogroupset:Count()
self:T2(warehouse.lid..string.format("Cargo %s: %d of %s delivered. CargoSet=%d",namewo,request.ndelivered,tostring(request.nasset),ncargo))
else
self:E(warehouse.lid..string.format("WARNING: Group %s is neither cargo nor transport! Need to investigate...",group:GetName()))
end
if assignment==nil and request.assignment~=nil then
assignment=request.assignment
end
end
end
local asset=self:FindAssetInDB(group)
if asset~=nil then
self:_DebugMessage(string.format("Warehouse %s: Adding KNOWN asset uid=%d with attribute=%s to stock.",self.alias,asset.uid,asset.attribute),5)
if liveries then
if type(liveries)=="table"then
asset.livery=liveries[math.random(#liveries)]
else
asset.livery=liveries
end
end
asset.skill=skill or asset.skill
asset.wid=self.uid
asset.rid=nil
asset.spawned=false
asset.requested=false
asset.isReserved=false
asset.iscargo=nil
asset.arrived=nil
if group:IsAlive()==true then
asset.damage=asset.life0-group:GetLife()
end
table.insert(self.stock,asset)
self:__NewAsset(0.1,asset,assignment or"")
else
self:_ErrorMessage(string.format("ERROR: Known asset could not be found in global warehouse db!"),0)
end
else
self:_DebugMessage(string.format("Warehouse %s: Adding %d NEW assets of group %s to stock",self.alias,n,tostring(group:GetName())),5)
local assets=self:_RegisterAsset(group,n,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment)
for _,asset in pairs(assets)do
asset.wid=self.uid
asset.rid=nil
table.insert(self.stock,asset)
self:__NewAsset(0.1,asset,assignment or"")
end
end
if group:IsAlive()==true then
self:_DebugMessage(string.format("Removing group %s",group:GetName()),5)
local opsgroup=_DATABASE:GetOpsGroup(group:GetName())
if opsgroup then
opsgroup:Despawn(0,true)
opsgroup:__Stop(-0.01)
else
group:Destroy()
end
else
local opsgroup=_DATABASE:GetOpsGroup(group:GetName())
if opsgroup then
opsgroup:Stop()
end
end
else
self:E(self.lid.."ERROR: Unknown group added as asset!")
self:E({unknowngroup=group})
end
end
function WAREHOUSE:_RegisterAsset(group,ngroups,forceattribute,forcecargobay,forceweight,loadradius,liveries,skill,assignment)
self:F({groupname=group:GetName(),ngroups=ngroups,forceattribute=forceattribute,forcecargobay=forcecargobay,forceweight=forceweight})
local n=ngroups or 1
local function _GetObjectSize(DCSdesc)
if DCSdesc.box then
local x=DCSdesc.box.max.x-DCSdesc.box.min.x
local y=DCSdesc.box.max.y-DCSdesc.box.min.y
local z=DCSdesc.box.max.z-DCSdesc.box.min.z
return math.max(x,z),x,y,z
end
return 0,0,0,0
end
local templategroupname=group:GetName()
local Descriptors=group:GetUnit(1):GetDesc()
local Category=group:GetCategory()
local TypeName=group:GetTypeName()
local SpeedMax=group:GetSpeedMax()
local RangeMin=group:GetRange()
local smax,sx,sy,sz=_GetObjectSize(Descriptors)
local weight=0
local cargobay={}
local cargobaytot=0
local cargobaymax=0
local weights={}
for _i,_unit in pairs(group:GetUnits())do
local unit=_unit
local Desc=unit:GetDesc()
local unitweight=forceweight or Desc.massEmpty
if unitweight then
weight=weight+unitweight
weights[_i]=unitweight
end
local cargomax=0
local massfuel=Desc.fuelMassMax or 0
local massempty=Desc.massEmpty or 0
local massmax=Desc.massMax or 0
cargomax=massmax-massfuel-massempty
self:T3(self.lid..string.format("Unit name=%s: mass empty=%.1f kg, fuel=%.1f kg, max=%.1f kg ==> cargo=%.1f kg",unit:GetName(),unitweight,massfuel,massmax,cargomax))
local bay=forcecargobay or unit:GetCargoBayFreeWeight()
table.insert(cargobay,bay)
cargobaytot=cargobaytot+bay
if bay>cargobaymax then
cargobaymax=bay
end
end
local attribute=forceattribute or self:_GetAttribute(group)
local assets={}
for i=1,n do
local asset={}
_WAREHOUSEDB.AssetID=_WAREHOUSEDB.AssetID+1
asset.uid=_WAREHOUSEDB.AssetID
asset.templatename=templategroupname
asset.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template)
asset.category=Category
asset.unittype=TypeName
asset.nunits=#asset.template.units
asset.range=RangeMin
asset.speedmax=SpeedMax
asset.size=smax
asset.weight=weight
asset.weights=weights
asset.DCSdesc=Descriptors
asset.attribute=attribute
asset.cargobay=cargobay
asset.cargobaytot=cargobaytot
asset.cargobaymax=cargobaymax
asset.loadradius=loadradius
if liveries then
asset.livery=liveries[math.random(#liveries)]
end
asset.skill=skill
asset.assignment=assignment
asset.spawned=false
asset.requested=false
asset.isReserved=false
asset.life0=group:GetLife0()
asset.damage=0
asset.spawngroupname=string.format("%s_AID-%d",templategroupname,asset.uid)
if i==1 then
self:_AssetItemInfo(asset)
end
_WAREHOUSEDB.Assets[asset.uid]=asset
table.insert(assets,asset)
end
return assets
end
function WAREHOUSE:_AssetItemInfo(asset)
local text=string.format("\nNew asset with id=%d for warehouse %s:\n",asset.uid,self.alias)
text=text..string.format("Spawngroup name= %s\n",asset.spawngroupname)
text=text..string.format("Template name  = %s\n",asset.templatename)
text=text..string.format("Unit type      = %s\n",asset.unittype)
text=text..string.format("Attribute      = %s\n",asset.attribute)
text=text..string.format("Category       = %d\n",asset.category)
text=text..string.format("Units #        = %d\n",asset.nunits)
text=text..string.format("Speed max      = %5.2f km/h\n",asset.speedmax)
text=text..string.format("Range max      = %5.2f km\n",asset.range/1000)
text=text..string.format("Size  max      = %5.2f m\n",asset.size)
text=text..string.format("Weight total   = %5.2f kg\n",asset.weight)
text=text..string.format("Cargo bay tot  = %5.2f kg\n",asset.cargobaytot)
text=text..string.format("Cargo bay max  = %5.2f kg\n",asset.cargobaymax)
text=text..string.format("Load radius    = %s m\n",tostring(asset.loadradius))
text=text..string.format("Skill          = %s\n",tostring(asset.skill))
text=text..string.format("Livery         = %s",tostring(asset.livery))
self:I(self.lid..text)
self:T({DCSdesc=asset.DCSdesc})
self:T3({Template=asset.template})
end
function WAREHOUSE:onafterNewAsset(From,Event,To,asset,assignment)
self:T(self.lid..string.format("New asset %s id=%d with assignment %s.",tostring(asset.templatename),asset.uid,tostring(assignment)))
end
function WAREHOUSE:onbeforeAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Assignment,Prio)
local okay=true
if AssetDescriptor==WAREHOUSE.Descriptor.ATTRIBUTE then
local gotit=false
for _,attribute in pairs(WAREHOUSE.Attribute)do
if AssetDescriptorValue==attribute then
gotit=true
end
end
if not gotit then
self:_ErrorMessage("ERROR: Invalid request. Asset attribute is unknown!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then
local gotit=false
for _,category in pairs(Group.Category)do
if AssetDescriptorValue==category then
gotit=true
end
end
if not gotit then
self:_ErrorMessage("ERROR: Invalid request. Asset category is unknown!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.GROUPNAME then
if type(AssetDescriptorValue)~="string"then
self:_ErrorMessage("ERROR: Invalid request. Asset template name must be passed as a string!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.UNITTYPE then
if type(AssetDescriptorValue)~="string"then
self:_ErrorMessage("ERROR: Invalid request. Asset unit type must be passed as a string!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSIGNMENT then
if type(AssetDescriptorValue)~="string"then
self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a string!",5)
okay=false
end
elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSETLIST then
if type(AssetDescriptorValue)~="table"then
self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a table!",5)
okay=false
end
else
self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, GROUPNAME, UNITTYPE or ASSIGNMENT!",5)
okay=false
end
if self:IsStopped()then
self:_ErrorMessage("ERROR: Invalid request. Warehouse is stopped!",0)
okay=false
end
if self:IsDestroyed()and not self.respawnafterdestroyed then
self:_ErrorMessage("ERROR: Invalid request. Warehouse is destroyed!",0)
okay=false
end
return okay
end
function WAREHOUSE:onafterAddRequest(From,Event,To,warehouse,AssetDescriptor,AssetDescriptorValue,nAsset,TransportType,nTransport,Prio,Assignment)
nAsset=nAsset or 1
TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED
Prio=Prio or 50
if nTransport==nil then
if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then
nTransport=0
else
nTransport=1
end
end
local toself=false
if self.warehouse:GetName()==warehouse.warehouse:GetName()then
toself=true
end
self.queueid=self.queueid+1
local request={
uid=self.queueid,
prio=Prio,
warehouse=warehouse,
assetdesc=AssetDescriptor,
assetdescval=AssetDescriptorValue,
nasset=nAsset,
transporttype=TransportType,
ntransport=nTransport,
assignment=tostring(Assignment),
airbase=warehouse:GetAirbase(),
category=warehouse:GetAirbaseCategory(),
ndelivered=0,
ntransporthome=0,
assets={},
toself=toself,
}
table.insert(self.queue,request)
local descval="assetlist"
if request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST then
else
descval=tostring(request.assetdescval)
end
local text=string.format("Warehouse %s: New request from warehouse %s.\nDescriptor %s=%s, #assets=%s; Transport=%s, #transports=%s.",
self.alias,warehouse.alias,request.assetdesc,descval,tostring(request.nasset),request.transporttype,tostring(request.ntransport))
self:_DebugMessage(text,5)
end
function WAREHOUSE:onbeforeRequest(From,Event,To,Request)
self:T3({warehouse=self.alias,request=Request})
local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate())
local _assets=Request.cargoassets
if Request.nasset==0 then
local text=string.format("Warehouse %s: Request denied! Zero assets were requested.",self.alias)
self:_InfoMessage(text,10)
return false
end
for _,_asset in pairs(_assets)do
local asset=_asset
if asset.range<distance then
local text=string.format("Request denied! Destination %s is out of range for asset %s.",Request.airbase:GetName(),asset.templatename)
self:_InfoMessage(text,10)
if not(self.isUnit or Request.warehouse.isUnit)then
self:_DeleteQueueItem(Request,self.queue)
end
return false
end
end
return true
end
function WAREHOUSE:onafterRequest(From,Event,To,Request)
if self.verbosity>=1 then
local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n",self.alias,Request.uid,Request.warehouse.alias)
text=text..string.format("Requested %s assets of %s=%s.\n",tostring(Request.nasset),Request.assetdesc,Request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST and"Asset list"or Request.assetdescval)
text=text..string.format("Transports %s of type %s.",tostring(Request.ntransport),tostring(Request.transporttype))
self:_InfoMessage(text,5)
end
Request.timestamp=timer.getAbsTime()
self:_SpawnAssetRequest(Request)
local _assetstock=Request.transportassets
local Parking={}
if Request.transportcategory==Group.Category.AIRPLANE or Request.transportcategory==Group.Category.HELICOPTER then
Parking=self:_FindParkingForAssets(self.airbase,_assetstock)
end
local _transportassets={}
for i=1,Request.ntransport do
local _assetitem=_assetstock[i]
local _alias=_assetitem.spawngroupname
_assetitem.rid=Request.uid
_assetitem.spawned=false
_assetitem.iscargo=false
_assetitem.arrived=false
local spawngroup=nil
Request.assets[_assetitem.uid]=_assetitem
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],true)
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],false)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then
self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!")
return
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.portzone)
elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
self:_ErrorMessage("ERROR: Transport type selfpropelled was already handled above. We should not get here!")
return
else
self:_ErrorMessage("ERROR: Unknown transport type!")
return
end
if spawngroup then
self:__AssetSpawned(0.01,spawngroup,_assetitem,Request)
end
end
Request.assetproblem={}
table.insert(self.pending,Request)
self:_DeleteQueueItem(Request,self.queue)
end
function WAREHOUSE:onafterRequestSpawned(From,Event,To,Request,CargoGroupSet,TransportGroupSet)
local _cargotype=Request.cargoattribute
local _cargocategory=Request.cargocategory
if Request.toself then
self:_DebugMessage(string.format("Selfrequest! Current status %s",self:GetState()))
self:__SelfRequest(1,CargoGroupSet,Request)
return
end
if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
self:T2(self.lid..string.format("Got selfpropelled request for %d assets.",CargoGroupSet:Count()))
for _,_group in pairs(CargoGroupSet:GetSetObjects())do
local group=_group
if _cargocategory==Group.Category.GROUND then
self:T2(self.lid..string.format("Route ground group %s.",group:GetName()))
local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate()
if self.Debug then
ToCoordinate:MarkToAll(string.format("Destination of group %s",group:GetName()))
end
self:_RouteGround(group,Request)
elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then
self:T2(self.lid..string.format("Route airborne group %s.",group:GetName()))
self:_RouteAir(group)
elseif _cargocategory==Group.Category.SHIP then
self:T2(self.lid..string.format("Route naval group %s.",group:GetName()))
self:_RouteNaval(group,Request)
elseif _cargocategory==Group.Category.TRAIN then
self:T2(self.lid..string.format("Route train group %s.",group:GetName()))
self:_RouteTrain(group,Request.warehouse.rail)
else
self:E(self.lid..string.format("ERROR: unknown category %s for self propelled cargo %s!",tostring(_cargocategory),tostring(group:GetName())))
end
end
Request.transportgroupset=TransportGroupSet
return
end
local _boardradius=500
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
_boardradius=5000
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
_boardradius=6000
end
local CargoGroups=SET_CARGO:New()
for _,_group in pairs(CargoGroupSet:GetSetObjects())do
local asset=self:FindAssetInDB(_group)
local cargogroup=CARGO_GROUP:New(_group,_cargotype,_group:GetName(),_boardradius,asset.loadradius)
cargogroup:SetWeight(asset.weight)
CargoGroups:AddCargo(cargogroup)
end
local CargoTransport
if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
local PickupAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName()))
local DeployAirbaseSet=SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName()))
CargoTransport=AI_CARGO_DISPATCHER_AIRPLANE:New(TransportGroupSet,CargoGroups,PickupAirbaseSet,DeployAirbaseSet)
CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName()))
elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
CargoTransport=AI_CARGO_DISPATCHER_HELICOPTER:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet)
CargoTransport:SetHomeZone(self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
local PickupZoneSet=SET_ZONE:New():AddZone(self.spawnzone)
local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.spawnzone)
CargoTransport=AI_CARGO_DISPATCHER_APC:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,0)
CargoTransport:SetHomeZone(self.spawnzone)
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
local PickupZoneSet=SET_ZONE:New():AddZone(self.portzone)
PickupZoneSet:AddZone(self.harborzone)
local DeployZoneSet=SET_ZONE:New():AddZone(Request.warehouse.harborzone)
local remotename=Request.warehouse.warehouse:GetName()
local ShippingLane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])]
CargoTransport=AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet,CargoGroups,PickupZoneSet,DeployZoneSet,ShippingLane)
CargoTransport:SetHomeZone(self.portzone)
else
self:E(self.lid.."ERROR: Unknown transporttype!")
end
local pickupouter=200
local pickupinner=0
local deployouter=200
local deployinner=0
if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
pickupouter=1000
pickupinner=20
deployouter=1000
deployinner=0
else
pickupouter=200
pickupinner=0
if self.spawnzone.Radius~=nil then
pickupouter=self.spawnzone.Radius
pickupinner=20
end
deployouter=200
deployinner=0
if self.spawnzone.Radius~=nil then
deployouter=Request.warehouse.spawnzone.Radius
deployinner=20
end
end
CargoTransport:SetPickupRadius(pickupouter,pickupinner)
CargoTransport:SetDeployRadius(deployouter,deployinner)
Request.carriercargo={}
for _,carriergroup in pairs(TransportGroupSet:GetSetObjects())do
local asset=self:FindAssetInDB(carriergroup)
for _i,_carrierunit in pairs(carriergroup:GetUnits())do
local carrierunit=_carrierunit
Request.carriercargo[carrierunit:GetName()]={}
local cargobay=asset.cargobay[_i]
carrierunit:SetCargoBayWeightLimit(cargobay)
self:T2(self.lid..string.format("Cargo bay weight limit of carrier unit %s: %.1f kg.",carrierunit:GetName(),carrierunit:GetCargoBayFreeWeight()))
end
end
function CargoTransport:OnAfterPickedUp(From,Event,To,Carrier,PickupZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local text=string.format("Carrier group %s picked up at pickup zone %s.",Carrier:GetName(),PickupZone:GetName())
warehouse:T(warehouse.lid..text)
end
function CargoTransport:OnAfterDeployed(From,Event,To,Carrier,DeployZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
end
function CargoTransport:OnAfterHome(From,Event,To,Carrier,Coordinate,Speed,Height,HomeZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local text=string.format("Carrier group %s going home to zone %s.",Carrier:GetName(),HomeZone:GetName())
warehouse:T(warehouse.lid..text)
end
function CargoTransport:OnAfterLoaded(From,Event,To,Carrier,Cargo,CarrierUnit,PickupZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s",Carrier:GetName(),Cargo:GetName(),CarrierUnit:GetName(),PickupZone:GetName())
warehouse:T(warehouse.lid..text)
local group=Cargo:GetObject()
local request=warehouse:_GetRequestOfGroup(group,warehouse.pending)
table.insert(request.carriercargo[CarrierUnit:GetName()],warehouse:_GetNameWithOut(Cargo:GetName()))
end
function CargoTransport:OnAfterUnloaded(From,Event,To,Carrier,Cargo,CarrierUnit,DeployZone)
local warehouse=Carrier:GetState(Carrier,"WAREHOUSE")
local group=Cargo:GetObject()
local text=string.format("Cargo group %s was unloaded from carrier unit %s.",tostring(group:GetName()),tostring(CarrierUnit:GetName()))
warehouse:T(warehouse.lid..text)
warehouse:Arrived(group)
end
function CargoTransport:OnAfterBackHome(From,Event,To,Carrier)
local carrier=Carrier
local warehouse=carrier:GetState(carrier,"WAREHOUSE")
carrier:SmokeWhite()
local text=string.format("Carrier %s is back home at warehouse %s.",tostring(Carrier:GetName()),tostring(warehouse.warehouse:GetName()))
MESSAGE:New(text,5):ToAllIf(warehouse.Debug)
warehouse:I(warehouse.lid..text)
warehouse:__Arrived(1,Carrier)
end
CargoTransport:__Start(5)
end
function WAREHOUSE:onafterUnloaded(From,Event,To,group)
self:_DebugMessage(string.format("Cargo %s unloaded!",tostring(group:GetName())),5)
if group and group:IsAlive()then
if self.Debug then
group:SmokeWhite()
end
local speedmax=group:GetSpeedMax()
if group:IsGround()then
if speedmax>1 then
group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(),speedmax*0.5,AI.Task.VehicleFormation.RANK,3)
else
self:Arrived(group)
end
elseif group:IsAir()then
self:Arrived(group)
elseif group:IsShip()then
self:Arrived(group)
end
else
self:E(self.lid..string.format("ERROR unloaded Cargo group is not alive!"))
end
end
function WAREHOUSE:onbeforeArrived(From,Event,To,group)
local asset=self:FindAssetInDB(group)
if asset then
if asset.flightgroup and not asset.arrived then
asset.arrived=true
return false
end
if asset.arrived==true then
return false
else
asset.arrived=true
return true
end
end
end
function WAREHOUSE:onafterArrived(From,Event,To,group)
if self.Debug then
group:SmokeOrange()
end
local request=self:_GetRequestOfGroup(group,self.pending)
if request then
local warehouse=request.warehouse
local istransport=self:_GroupIsTransport(group,request)
if istransport==true then
warehouse=self
elseif istransport==false then
warehouse=request.warehouse
else
self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport",group:GetName()))
return
end
self:_DebugMessage(string.format("Group %s arrived at warehouse %s!",tostring(group:GetName()),warehouse.alias),5)
if group:IsGround()and group:GetSpeedMax()>1 then
group:RouteGroundTo(warehouse:GetCoordinate(),group:GetSpeedMax()*0.3,"Off Road")
end
self:T(self.lid.."Asset arrived at warehouse adding in 60 sec")
warehouse:__AddAsset(60,group)
end
end
function WAREHOUSE:onafterDelivered(From,Event,To,request)
if self.verbosity>=1 then
local text=string.format("Warehouse %s: All assets delivered to warehouse %s!",self.alias,request.warehouse.alias)
self:_InfoMessage(text,5)
end
if self.Debug then
self:_Fireworks(request.warehouse:GetCoordinate())
end
self.delivered[request.uid]=true
end
function WAREHOUSE:onafterSelfRequest(From,Event,To,groupset,request)
self:_DebugMessage(string.format("Assets spawned at warehouse %s after self request!",self.alias))
for _,_group in pairs(groupset:GetSetObjects())do
local group=_group
if self.Debug then
group:FlareGreen()
end
end
if self:IsAttacked()then
if self.autodefence then
for _,_group in pairs(groupset:GetSetObjects())do
local group=_group
local speedmax=group:GetSpeedMax()
if group:IsGround()and speedmax>1 and group:IsNotInZone(self.zone)then
group:RouteGroundTo(self.zone:GetRandomCoordinate(),0.8*speedmax,"Off Road")
end
end
end
table.insert(self.defending,request)
end
end
function WAREHOUSE:onafterAttacked(From,Event,To,Coalition,Country)
local text=string.format("Warehouse %s: We are under attack!",self.alias)
self:_InfoMessage(text)
if self.Debug then
self:GetCoordinate():SmokeOrange()
end
if self.autodefence then
local nground=self:GetNumberOfAssets(WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND)
local text=string.format("Warehouse auto defence activated.\n")
if nground>0 then
text=text..string.format("Deploying all %d ground assets.",nground)
self:AddRequest(self,WAREHOUSE.Descriptor.CATEGORY,Group.Category.GROUND,WAREHOUSE.Quantity.ALL,nil,nil,0,"AutoDefence")
else
text=text..string.format("No ground assets currently available.")
end
self:_InfoMessage(text)
else
local text=string.format("Warehouse auto defence inactive.")
self:I(self.lid..text)
end
end
function WAREHOUSE:onafterDefeated(From,Event,To)
local text=string.format("Warehouse %s: Enemy attack was defeated!",self.alias)
self:_InfoMessage(text)
if self.Debug then
self:GetCoordinate():SmokeGreen()
end
if self.autodefence then
for _,request in pairs(self.defending)do
for _,_group in pairs(request.cargogroupset:GetSetObjects())do
local group=_group
local speed=group:GetSpeedMax()
if group:IsGround()and speed>1 then
group:RouteGroundTo(self:GetCoordinate(),speed*0.3)
end
self:__AddAsset(60,group)
end
end
self.defending=nil
self.defending={}
end
end
function WAREHOUSE:onafterRespawn(From,Event,To)
local text=string.format("Respawning warehouse %s",self.alias)
self:_InfoMessage(text)
self.warehouse:ReSpawn()
end
function WAREHOUSE:onbeforeChangeCountry(From,Event,To,Country)
local currentCountry=self:GetCountry()
local text=string.format("Warehouse %s: request to change country %d-->%d",self.alias,currentCountry,Country)
self:_DebugMessage(text,10)
if currentCountry~=Country then
return true
end
return false
end
function WAREHOUSE:onafterChangeCountry(From,Event,To,Country)
local CoalitionOld=self:GetCoalition()
self.warehouse:ReSpawn(Country)
local CoalitionNew=self:GetCoalition()
self.queue=nil
self.queue={}
if self.airbasename then
local airbase=AIRBASE:FindByName(self.airbasename)
local airbaseCoalition=airbase:GetCoalition()
if CoalitionNew==airbaseCoalition then
self.airbase=airbase
else
self.airbase=nil
end
end
if self.Debug then
if CoalitionNew==coalition.side.RED then
self:GetCoordinate():SmokeRed()
elseif CoalitionNew==coalition.side.BLUE then
self:GetCoordinate():SmokeBlue()
end
end
end
function WAREHOUSE:onbeforeCaptured(From,Event,To,Coalition,Country)
self:ChangeCountry(Country)
end
function WAREHOUSE:onafterCaptured(From,Event,To,Coalition,Country)
local text=string.format("Warehouse %s: We were captured by enemy coalition (side=%d)!",self.alias,Coalition)
self:_InfoMessage(text)
end
function WAREHOUSE:onafterAirbaseCaptured(From,Event,To,Coalition)
local text=string.format("Warehouse %s: Our airbase %s was captured by the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition)
self:_InfoMessage(text)
if self.Debug then
if Coalition==coalition.side.RED then
self.airbase:GetCoordinate():SmokeRed()
elseif Coalition==coalition.side.BLUE then
self.airbase:GetCoordinate():SmokeBlue()
end
end
self.airbase=nil
end
function WAREHOUSE:onafterAirbaseRecaptured(From,Event,To,Coalition)
local text=string.format("Warehouse %s: We recaptured our airbase %s from the enemy (coalition=%d)!",self.alias,self.airbasename,Coalition)
self:_InfoMessage(text)
self.airbase=AIRBASE:FindByName(self.airbasename)
if self.Debug then
if Coalition==coalition.side.RED then
self.airbase:GetCoordinate():SmokeRed()
elseif Coalition==coalition.side.BLUE then
self.airbase:GetCoordinate():SmokeBlue()
end
end
end
function WAREHOUSE:onafterRunwayDestroyed(From,Event,To)
local text=string.format("Warehouse %s: Runway %s destroyed!",self.alias,self.airbasename)
self:_InfoMessage(text)
self.runwaydestroyed=timer.getAbsTime()
return self
end
function WAREHOUSE:onafterRunwayRepaired(From,Event,To)
local text=string.format("Warehouse %s: Runway %s repaired!",self.alias,self.airbasename)
self:_InfoMessage(text)
self.runwaydestroyed=nil
return self
end
function WAREHOUSE:onafterAssetSpawned(From,Event,To,group,asset,request)
local text=string.format("Asset %s from request id=%d was spawned!",asset.spawngroupname,request.uid)
self:T(self.lid..text)
asset.spawned=true
asset.spawngroupname=group:GetName()
self:_DeleteStockItem(asset)
if asset.iscargo==true then
request.cargogroupset=request.cargogroupset or SET_GROUP:New()
request.cargogroupset:AddGroup(group)
else
request.transportgroupset=request.transportgroupset or SET_GROUP:New()
request.transportgroupset:AddGroup(group)
end
group:SetState(group,"WAREHOUSE",self)
local n=0
for _,_asset in pairs(request.assets)do
local assetitem=_asset
self:T(self.lid..string.format("Asset %s spawned %s as %s",assetitem.templatename,tostring(assetitem.spawned),tostring(assetitem.spawngroupname)))
if assetitem.spawned then
n=n+1
else
end
end
if n==request.nasset+request.ntransport then
self:T(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned",n,request.nasset,request.ntransport,request.uid))
self:RequestSpawned(request,request.cargogroupset,request.transportgroupset)
else
self:T(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET",n,request.nasset,request.ntransport,request.uid))
end
end
function WAREHOUSE:onafterAssetDead(From,Event,To,asset,request)
if asset and request then
local text=string.format("Asset %s from request id=%d is dead!",asset.templatename,request.uid)
self:T(self.lid..text)
local groupname=asset.spawngroupname
local NoTriggerEvent=true
if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
if request.cargogroupset then
request.cargogroupset:Remove(groupname,NoTriggerEvent)
self:T(self.lid..string.format("Removed selfpropelled cargo %s: ncargo=%d.",groupname,request.cargogroupset:Count()))
else
self:E(self.lid..string.format("ERROR: cargogroupset is nil for request ID=%s!",tostring(request.uid)))
end
else
local istransport=not asset.iscargo
if istransport==true then
request.transportgroupset:Remove(groupname,NoTriggerEvent)
self:T(self.lid..string.format("Removed transport %s: ntransport=%d",groupname,request.transportgroupset:Count()))
elseif istransport==false then
request.cargogroupset:Remove(groupname,NoTriggerEvent)
self:T(self.lid..string.format("Removed transported cargo %s outside carrier: ncargo=%d",groupname,request.cargogroupset:Count()))
else
end
end
else
self:E(self.lid.."ERROR: Asset and/or Request is nil in onafterAssetDead")
end
end
function WAREHOUSE:onafterDestroyed(From,Event,To)
local text=string.format("Warehouse %s was destroyed! Assets lost %d. Respawn=%s",self.alias,#self.stock,tostring(self.respawnafterdestroyed))
self:_InfoMessage(text)
if self.respawnafterdestroyed then
if self.respawndelay then
self:Pause()
self:__Respawn(self.respawndelay)
else
self:Respawn()
end
else
for k,_ in pairs(self.queue)do
self.queue[k]=nil
end
for k,_ in pairs(self.stock)do
end
for k=#self.stock,1,-1 do
self.stock[k]=nil
end
end
end
function WAREHOUSE:onafterSave(From,Event,To,path,filename)
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Saving warehouse assets to file %s",filename)
MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report)
self:I(self.lid..text)
local warehouseassets=""
warehouseassets=warehouseassets..string.format("coalition=%d\n",self:GetCoalition())
warehouseassets=warehouseassets..string.format("country=%d\n",self:GetCountry())
for _,_asset in pairs(self.stock)do
local asset=_asset
local assetstring=""
for key,value in pairs(asset)do
if key=="templatename"or key=="attribute"or key=="cargobay"or key=="weight"or key=="loadradius"or key=="livery"or key=="skill"or key=="assignment"then
local name
if type(value)=="table"then
name=string.format("%s=%s;",key,value[1])
else
name=string.format("%s=%s;",key,value)
end
assetstring=assetstring..name
end
self:I(string.format("Loaded asset: %s",assetstring))
end
warehouseassets=warehouseassets..assetstring.."\n"
end
_savefile(filename,warehouseassets)
end
function WAREHOUSE:onbeforeLoad(From,Event,To,path,filename)
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:_ErrorMessage(string.format("ERROR: file %s does not exist! Cannot load assets.",filename),60)
return false
end
end
function WAREHOUSE:onafterLoad(From,Event,To,path,filename)
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
filename=filename or string.format("WAREHOUSE-%d_%s.txt",self.uid,self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading warehouse assets from file %s",filename)
MESSAGE:New(text,30):ToAllIf(self.Debug or self.Report)
self:I(self.lid..text)
local data=_loadfile(filename)
local assetdata=UTILS.Split(data,"\n")
local Coalition
local Country
local assets={}
for _,asset in pairs(assetdata)do
local descriptors=UTILS.Split(asset,";")
local asset={}
local isasset=false
for _,descriptor in pairs(descriptors)do
local keyval=UTILS.Split(descriptor,"=")
if#keyval==2 then
if keyval[1]=="coalition"then
Coalition=tonumber(keyval[2])
elseif keyval[1]=="country"then
Country=tonumber(keyval[2])
else
isasset=true
local key=keyval[1]
local val=keyval[2]
if val=="nil"then
val=nil
end
if key=="cargobay"or key=="weight"or key=="loadradius"then
asset[key]=tonumber(val)
else
asset[key]=val
end
end
end
end
if isasset then
table.insert(assets,asset)
end
end
if Country~=self:GetCountry()then
self:T(self.lid..string.format("Changing warehouse country %d-->%d on loading assets.",self:GetCountry(),Country))
self:ChangeCountry(Country)
end
for _,_asset in pairs(assets)do
local asset=_asset
local group=GROUP:FindByName(asset.templatename)
if group then
self:AddAsset(group,1,asset.attribute,asset.cargobay,asset.weight,asset.loadradius,asset.skill,asset.livery,asset.assignment)
else
self:E(string.format("ERROR: Group %s doest not exit. Cannot be loaded as asset.",tostring(asset.templatename)))
end
end
end
function WAREHOUSE:_SpawnAssetRequest(Request)
self:F2({requestUID=Request.uid})
local cargoassets=Request.cargoassets
local Parking={}
if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then
Parking=self:_FindParkingForAssets(self.airbase,cargoassets)or{}
end
local UnControlled=true
for i=1,#cargoassets do
local asset=cargoassets[i]
if not asset.spawned then
asset.iscargo=true
asset.rid=Request.uid
local _alias=asset.spawngroupname
Request.assets[asset.uid]=asset
local _group=nil
if asset.category==Group.Category.GROUND then
_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation)
elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
if Parking[asset.uid]then
_group=self:_SpawnAssetAircraft(_alias,asset,Request,Parking[asset.uid],UnControlled,Request.lateActivation)
else
_group=self:_SpawnAssetAircraft(_alias,asset,Request,nil,UnControlled,Request.lateActivation)
end
elseif asset.category==Group.Category.TRAIN then
if self.rail then
_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone,Request.lateActivation)
end
elseif asset.category==Group.Category.SHIP then
_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.portzone,Request.lateActivation)
else
self:E(self.lid.."ERROR: Unknown asset category!")
end
if _group then
self:__AssetSpawned(0.01,_group,asset,Request)
end
end
end
end
function WAREHOUSE:_SpawnAssetGroundNaval(alias,asset,request,spawnzone,lateactivated)
if asset and(asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP or asset.category==Group.Category.TRAIN)then
local template=self:_SpawnAssetPrepareTemplate(asset,alias)
template.route.points[1]={}
local coord=spawnzone:GetRandomCoordinate()
if asset.category==Group.Category.TRAIN then
coord=self.rail
end
for i=1,#template.units do
local unit=template.units[i]
local SX=unit.x or 0
local SY=unit.y or 0
local BX=asset.template.route.points[1].x
local BY=asset.template.route.points[1].y
local TX=coord.x+(SX-BX)
local TY=coord.z+(SY-BY)
template.units[i].x=TX
template.units[i].y=TY
if asset.livery then
unit.livery_id=asset.livery
end
if asset.skill then
unit.skill=asset.skill
end
end
template.lateActivation=lateactivated
template.route.points[1].x=coord.x
template.route.points[1].y=coord.z
template.x=coord.x
template.y=coord.z
template.alt=coord.y
local group=_DATABASE:Spawn(template)
return group
end
return nil
end
function WAREHOUSE:_SpawnAssetAircraft(alias,asset,request,parking,uncontrolled,lateactivated)
if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
local template=self:_SpawnAssetPrepareTemplate(asset,alias)
local _type=COORDINATE.WaypointType.TakeOffParking
local _action=COORDINATE.WaypointAction.FromParkingArea
if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then
_type=COORDINATE.WaypointType.TakeOffParkingHot
_action=COORDINATE.WaypointAction.FromParkingAreaHot
uncontrolled=false
end
local airstart=asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TurningPoint or false
if airstart then
_type=COORDINATE.WaypointType.TurningPoint
_action=COORDINATE.WaypointAction.TurningPoint
uncontrolled=false
end
if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
if request.toself then
local coord=self.airbase:GetCoordinate()
if airstart then
coord:SetAltitude(math.random(1000,2000))
end
local wp=coord:WaypointAir("RADIO",_type,_action,0,false,self.airbase,{},"Parking")
template.route.points={wp}
else
template.route.points=self:_GetFlightplan(asset,self.airbase,request.warehouse.airbase)
end
else
template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action,0,true,self.airbase,nil,"Spawnpoint")
end
local AirbaseID=self.airbase:GetID()
local AirbaseCategory=self:GetAirbaseCategory()
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
else
if parking 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
end
end
for i=1,#template.units do
local unit=template.units[i]
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
local coord=self.airbase:GetCoordinate()
unit.x=coord.x
unit.y=coord.z
unit.alt=coord.y
if airstart then
unit.alt=math.random(1000,2000)
end
unit.parking_id=nil
unit.parking=nil
else
local coord=nil
local terminal=nil
if airstart then
coord=self.airbase:GetCoordinate():SetAltitude(math.random(1000,2000))
else
coord=parking[i].Coordinate
terminal=parking[i].TerminalID
end
if self.Debug and terminal then
local text=string.format("Spawnplace unit %s terminal %d.",unit.name,terminal)
coord:MarkToAll(text)
env.info(text)
end
unit.x=coord.x
unit.y=coord.z
unit.alt=coord.y
unit.parking_id=nil
unit.parking=terminal
end
if asset.livery then
unit.livery_id=asset.livery
end
if asset.skill then
unit.skill=asset.skill
end
if asset.payload then
unit.payload=asset.payload.pylons
end
if asset.modex then
unit.onboard_num=asset.modex[i]
end
if asset.callsign then
unit.callsign=asset.callsign[i]
end
end
template.x=template.units[1].x
template.y=template.units[1].y
template.uncontrolled=uncontrolled
self:T2({airtemplate=template})
local group=_DATABASE:Spawn(template)
return group
end
return nil
end
function WAREHOUSE:_SpawnAssetPrepareTemplate(asset,alias)
local template=UTILS.DeepCopy(asset.template)
template.name=alias
template.CoalitionID=self:GetCoalition()
template.CountryID=self:GetCountry()
template.groupId=nil
template.lateActivation=false
if asset.missionTask then
self:T(self.lid..string.format("Setting mission task to %s",tostring(asset.missionTask)))
template.task=asset.missionTask
end
template.route={}
template.route.routeRelativeTOT=true
template.route.points={}
for i=1,#template.units do
local unit=template.units[i]
unit.unitId=nil
unit.name=string.format("%s-%02d",template.name,i)
end
return template
end
function WAREHOUSE:_RouteGround(group,request)
if group and group:IsAlive()then
local _speed=group:GetSpeedMax()*0.7
local Waypoints={}
local hasoffroad=self:HasConnectionOffRoad(request.warehouse,self.Debug)
if hasoffroad then
local remotename=request.warehouse.warehouse:GetName()
local path=self.offroadpaths[remotename][math.random(#self.offroadpaths[remotename])]
for i=1,#path do
local coord=path[i]
local Waypoint=coord:WaypointGround(_speed,"Off Road")
table.insert(Waypoints,Waypoint)
end
else
Waypoints=group:TaskGroundOnRoad(request.warehouse.road,_speed,"Off Road",false,self.road)
local FromWP=group:GetCoordinate():WaypointGround(_speed,"Off Road")
table.insert(Waypoints,1,FromWP)
end
for n,wp in ipairs(Waypoints)do
local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group,n,#Waypoints)
group:SetTaskWaypoint(wp,tf)
end
group:Route(Waypoints,1)
group:OptionROEReturnFire()
group:OptionAlarmStateGreen()
end
end
function WAREHOUSE:_RouteNaval(group,request)
if group and group:IsAlive()then
local _speed=group:GetSpeedMax()*0.8
local remotename=request.warehouse.warehouse:GetName()
local lane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])]
if lane then
local Waypoints={}
for i=1,#lane do
local coord=lane[i]
local Waypoint=coord:WaypointGround(_speed)
table.insert(Waypoints,Waypoint)
end
local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",group)
local Waypoint=Waypoints[#Waypoints]
group:SetTaskWaypoint(Waypoint,TaskFunction)
group:Route(Waypoints,1)
group:OptionROEReturnFire()
else
self:E(self.lid..string.format("ERROR: No shipping lane defined for Naval asset!"))
end
end
end
function WAREHOUSE:_RouteAir(aircraft)
if aircraft and aircraft:IsAlive()~=nil then
self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s",aircraft:GetName(),tostring(aircraft:IsAlive())))
if self.flightcontrol then
local fg=FLIGHTGROUP:New(aircraft)
fg:SetReadyForTakeoff(true)
else
aircraft:StartUncontrolled(math.random(60))
end
self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s (after start command)",aircraft:GetName(),tostring(aircraft:IsAlive())))
aircraft:OptionROEReturnFire()
aircraft:OptionROTPassiveDefense()
else
self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!",tostring(aircraft:GetName()),tostring(aircraft:IsAlive())))
end
end
function WAREHOUSE:_RouteTrain(Group,Coordinate,Speed)
if Group and Group:IsAlive()then
local _speed=Speed or Group:GetSpeedMax()*0.6
local Waypoints=Group:TaskGroundOnRailRoads(Coordinate,Speed)
local TaskFunction=self:_SimpleTaskFunction("warehouse:_Arrived",Group)
local Waypoint=Waypoints[#Waypoints]
Group:SetTaskWaypoint(Waypoint,TaskFunction)
Group:Route(Waypoints,1)
end
end
function WAREHOUSE:_Arrived(group)
self:_DebugMessage(string.format("Group %s arrived!",tostring(group:GetName())))
if group then
self:__Arrived(1,group)
end
end
function WAREHOUSE:_PassingWaypoint(group,n,N)
self:T(self.lid..string.format("Group %s passing waypoint %d of %d!",tostring(group:GetName()),n,N))
if n==N then
self:__Arrived(1,group)
end
end
function WAREHOUSE:GetAssetByID(id)
if id then
return _WAREHOUSEDB.Assets[id]
else
return nil
end
end
function WAREHOUSE:GetAssetByName(GroupName)
local name=self:_GetNameWithOut(GroupName)
local _,aid,_=self:_GetIDsFromGroup(GROUP:FindByName(name))
if aid then
return _WAREHOUSEDB.Assets[aid]
else
return nil
end
end
function WAREHOUSE:GetRequestByID(id)
if id then
for _,_request in pairs(self.queue)do
local request=_request
if request.uid==id then
return request,true
end
end
for _,_request in pairs(self.pending)do
local request=_request
if request.uid==id then
return request,false
end
end
end
return nil,nil
end
function WAREHOUSE:_OnEventBirth(EventData)
self:T3(self.lid..string.format("Warehouse %s (id=%s) captured event birth!",self.alias,self.uid))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
local asset=self:GetAssetByID(aid)
local request=self:GetRequestByID(rid)
if asset and request then
self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s",self.alias,request.uid,asset.uid,EventData.IniUnitName,tostring(asset.spawned)))
request.born=true
else
self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s",tostring(aid),tostring(rid),tostring(EventData.IniUnitName)))
end
else
end
end
end
function WAREHOUSE:_OnEventEngineStartup(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event engine startup!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event engine startup of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventTakeOff(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event takeoff!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event takeoff of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventLanding(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event landing!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid~=nil and wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event landing of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventEngineShutdown(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event engine shutdown!",self.alias))
if EventData and EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.",self.alias,EventData.IniUnitName))
end
end
end
function WAREHOUSE:_OnEventArrived(EventData)
if EventData and EventData.IniUnit then
local unit=EventData.IniUnit
if unit and unit:IsAlive()==true and unit:InAir()==false then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid~=nil and aid~=nil and rid~=nil then
if self.uid==wid then
local request=self:_GetRequestOfGroup(group,self.pending)
if request then
local istransport=self:_GroupIsTransport(group,request)
local closest=group:GetCoordinate():GetClosestAirbase()
local rightairbase=closest:GetName()==request.warehouse:GetAirbase():GetName()
if istransport==false and rightairbase then
local nunits=#group:GetUnits()
local dt=10*(nunits-1)+1
if self.verbosity>=1 then
local text=string.format("Air asset group %s from warehouse %s arrived at its destination. Trigger Arrived event in %d sec",group:GetName(),self.alias,dt)
self:_InfoMessage(text)
end
self:__Arrived(dt,group)
end
end
end
else
self:T3(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.",tostring(wid),tostring(aid),tostring(rid)))
end
end
end
end
function WAREHOUSE:_OnEventCrashOrDead(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event dead or crash!",self.alias))
if EventData then
if EventData.IniUnitName then
local warehousename=self.warehouse:GetName()
if EventData.IniUnitName==warehousename then
self:_DebugMessage(string.format("Warehouse %s alias %s was destroyed!",warehousename,self.alias))
self:Destroyed()
end
if self.airbase and self.airbasename and self.airbasename==EventData.IniUnitName then
if self:IsRunwayOperational()then
self:RunwayDestroyed()
else
self.runwaydestroyed=timer.getAbsTime()
end
end
end
self:T2(self.lid..string.format("Warehouse %s captured event dead or crash or unit %s",self.alias,tostring(EventData.IniUnitName)))
if EventData.IniGroup then
local group=EventData.IniGroup
local wid,aid,rid=self:_GetIDsFromGroup(group)
if wid==self.uid then
self:T(self.lid..string.format("Warehouse %s captured event dead or crash of its asset unit %s",self.alias,EventData.IniUnitName))
for _,request in pairs(self.pending)do
local request=request
if request.uid==rid then
self:_UnitDead(EventData.IniUnit,EventData.IniGroup,request)
end
end
end
end
end
end
function WAREHOUSE:_UnitDead(deadunit,deadgroup,request)
local opsgroup=_DATABASE:FindOpsGroup(deadgroup)
if opsgroup then
return nil
end
local nalive=deadgroup:CountAliveUnits()
local groupdead=false
if nalive>0 then
groupdead=false
else
groupdead=true
end
local asset=self:FindAssetInDB(deadgroup)
local unitname=self:_GetNameWithOut(deadunit)
local groupname=self:_GetNameWithOut(deadgroup)
if groupdead then
self:T(self.lid..string.format("Group %s (transport=%s) is dead!",groupname,tostring(self:_GroupIsTransport(deadgroup,request))))
if self.Debug then
deadgroup:SmokeWhite()
end
self:AssetDead(asset,request)
end
local NoTriggerEvent=true
if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then
if not asset.iscargo then
local cargogroupnames=request.carriercargo[unitname]
if cargogroupnames then
for _,cargoname in pairs(cargogroupnames)do
request.cargogroupset:Remove(cargoname,NoTriggerEvent)
self:T(self.lid..string.format("Removed transported cargo %s inside dead carrier %s: ncargo=%d",cargoname,unitname,request.cargogroupset:Count()))
end
end
else
self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport!",deadgroup:GetName()))
end
end
end
function WAREHOUSE:_OnEventBaseCaptured(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event base captured!",self.alias))
if self.airbasename==nil then
return
end
if EventData and EventData.Place then
local airbase=EventData.Place
if EventData.PlaceName==self.airbasename then
local NewCoalitionAirbase=airbase:GetCoalition()
self:T(self.lid..string.format("Airbase of warehouse %s (coalition ID=%d) was captured! New owner coalition ID=%d.",self.alias,self:GetCoalition(),NewCoalitionAirbase))
if self.airbase==nil then
if NewCoalitionAirbase==self:GetCoalition()then
self:AirbaseRecaptured(NewCoalitionAirbase)
end
else
if NewCoalitionAirbase~=self:GetCoalition()then
self:AirbaseCaptured(NewCoalitionAirbase)
end
end
end
end
end
function WAREHOUSE:_OnEventMissionEnd(EventData)
self:T3(self.lid..string.format("Warehouse %s captured event mission end!",self.alias))
if self.autosave then
self:Save(self.autosavepath,self.autosavefile)
end
end
function WAREHOUSE:_CheckConquered()
local coord=self.zone:GetCoordinate()
local radius=self.zone:GetRadius()
local gotunits,_,_,units,_,_=coord:ScanObjects(radius,true,false,false)
local Nblue=0
local Nred=0
local Nneutral=0
local CountryBlue=nil
local CountryRed=nil
local CountryNeutral=nil
if gotunits then
for _,_unit in pairs(units)do
local unit=_unit
local distance=coord:Get2DDistance(unit:GetCoordinate())
if unit:IsGround()and unit:IsAlive()and distance<=radius then
local _coalition=unit:GetCoalition()
local _country=unit:GetCountry()
self:T2(self.lid..string.format("Unit %s in warehouse zone of radius=%d m. Coalition=%d, country=%d. Distance = %d m.",unit:GetName(),radius,_coalition,_country,distance))
if _coalition==coalition.side.BLUE then
Nblue=Nblue+1
CountryBlue=_country
elseif _coalition==coalition.side.RED then
Nred=Nred+1
CountryRed=_country
else
Nneutral=Nneutral+1
CountryNeutral=_country
end
end
end
end
self:T(self.lid..string.format("Ground troops in warehouse zone: blue=%d, red=%d, neutral=%d",Nblue,Nred,Nneutral))
local newcoalition=self:GetCoalition()
local newcountry=self:GetCountry()
if Nblue>0 and Nred==0 and Nneutral==0 then
newcoalition=coalition.side.BLUE
newcountry=CountryBlue
elseif Nblue==0 and Nred>0 and Nneutral==0 then
newcoalition=coalition.side.RED
newcountry=CountryRed
elseif Nblue==0 and Nred==0 and Nneutral>0 then
end
if self:IsAttacked()and newcoalition~=self:GetCoalition()then
self:Captured(newcoalition,newcountry)
return
end
if self:GetCoalition()==coalition.side.BLUE then
if self:IsRunning()and Nred>0 then
self:Attacked(coalition.side.RED,CountryRed)
end
if self:IsAttacked()and Nred==0 then
self:Defeated()
end
elseif self:GetCoalition()==coalition.side.RED then
if self:IsRunning()and Nblue>0 then
self:Attacked(coalition.side.BLUE,CountryBlue)
end
if self:IsAttacked()and Nblue==0 then
self:Defeated()
end
elseif self:GetCoalition()==coalition.side.NEUTRAL then
if self:IsRunning()and Nred>0 then
self:Attacked(coalition.side.RED,CountryRed)
elseif self:IsRunning()and Nblue>0 then
self:Attacked(coalition.side.BLUE,CountryBlue)
end
end
end
function WAREHOUSE:_CheckAirbaseOwner()
if self.airbasename then
local airbase=AIRBASE:FindByName(self.airbasename)
local airbasecurrentcoalition=airbase:GetCoalition()
if self.airbase then
if self:GetCoalition()~=airbasecurrentcoalition then
self.airbase=nil
end
else
if self:GetCoalition()==airbasecurrentcoalition then
self.airbase=airbase
end
end
end
end
function WAREHOUSE:_CheckRequestConsistancy(queue)
self:T3(self.lid..string.format("Number of queued requests = %d",#queue))
local invalid={}
for _,_request in pairs(queue)do
local request=_request
self:T2(self.lid..string.format("Checking request id=%d.",request.uid))
local valid=true
if request.nasset==0 then
self:E(self.lid..string.format("ERROR: INVALID request. Request for zero assets not possible. Can happen when, e.g. \"all\" ground assets are requests but none in stock."))
valid=false
end
if self:GetCoalition()~=request.warehouse:GetCoalition()then
self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coalition! Own coalition %s != %s of requesting warehouse.",self:GetCoalitionName(),request.warehouse:GetCoalitionName()))
valid=false
end
if request.warehouse:IsStopped()then
self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is stopped!"))
valid=false
end
if request.warehouse:IsDestroyed()and not self.respawnafterdestroyed then
self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is destroyed!"))
valid=false
end
if valid==false then
self:E(self.lid..string.format("Got invalid request id=%d.",request.uid))
table.insert(invalid,request)
else
self:T3(self.lid..string.format("Got valid request id=%d.",request.uid))
end
end
for _,_request in pairs(invalid)do
self:E(self.lid..string.format("Deleting INVALID request id=%d.",_request.uid))
self:_DeleteQueueItem(_request,self.queue)
end
end
function WAREHOUSE:_CheckRequestValid(request)
local _assets,_nassets,_enough=self:_FilterStock(self.stock,request.assetdesc,request.assetdescval,request.nasset)
if#_assets==0 then
return true
end
local nasset=request.nasset
if type(request.nasset)=="string"then
nasset=self:_QuantityRel2Abs(request.nasset,_nassets)
end
local text=string.format("Request valid? Number of assets: requested=%s=%d, selected=%d, total=%d, enough=%s.",tostring(request.nasset),nasset,#_assets,_nassets,tostring(_enough))
self:T(text)
local asset=_assets[1]
local asset_plane=asset.category==Group.Category.AIRPLANE
local asset_helo=asset.category==Group.Category.HELICOPTER
local asset_ground=asset.category==Group.Category.GROUND
local asset_train=asset.category==Group.Category.TRAIN
local asset_naval=asset.category==Group.Category.SHIP
local asset_air=asset_helo or asset_plane
local valid=true
local requestcategory=request.warehouse:GetAirbaseCategory()
if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
if asset_air then
if asset_plane then
if requestcategory==Airbase.Category.HELIPAD or self:GetAirbaseCategory()==Airbase.Category.HELIPAD then
self:E("ERROR: Incorrect request. Asset airplane requested but warehouse or requestor is HELIPAD/FARP!")
valid=false
end
elseif asset_helo then
if self:GetAirbaseCategory()==-1 or requestcategory==-1 then
self:E("ERROR: Incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destination base!")
valid=false
end
end
if self.airbase==nil or request.airbase==nil then
self:E("ERROR: Incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!")
valid=false
else
local termtype_dep=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory())
local termtype_des=asset.terminalType or self:_GetTerminal(asset.attribute,request.warehouse:GetAirbaseCategory())
local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep)
local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des)
self:T(string.format("Asset attribute = %s, DEPARTURE: terminal type = %d, spots = %d, DESTINATION: terminal type = %d, spots = %d",asset.attribute,termtype_dep,np_departure,termtype_des,np_destination))
if np_departure<nasset then
self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.",termtype_dep,np_departure,nasset))
valid=false
end
if np_destination==0 then
self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse. Available spots = %d!",termtype_des,np_destination))
valid=false
end
end
elseif asset_ground then
local inwater=self.spawnzone:GetCoordinate():IsSurfaceTypeWater()or request.warehouse.spawnzone:GetCoordinate():IsSurfaceTypeWater()
if inwater and not request.lateActivation then
self:E("ERROR: Incorrect request. Ground asset requested but at least one spawn zone is in water!")
return false
end
if asset_train then
local hasrail=self:HasConnectionRail(request.warehouse)
if not hasrail then
self:E("ERROR: Incorrect request. No valid path on rail for train assets!")
valid=false
end
else
if self.warehouse:GetName()~=request.warehouse.warehouse:GetName()then
local hasroad=self:HasConnectionRoad(request.warehouse)
local hasoffroad=self:HasConnectionOffRoad(request.warehouse)
if not(hasroad or hasoffroad)then
self:E("ERROR: Incorrect request. No valid path on or off road for ground assets!")
valid=false
end
end
end
elseif asset_naval then
local shippinglane=self:HasConnectionNaval(request.warehouse)
if not shippinglane then
self:E("ERROR: Incorrect request. No shipping lane has been defined between warehouses!")
valid=false
end
end
else
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME or requestcategory~=Airbase.Category.AIRDROME then
self:E("ERROR: Incorrect request. Warehouse or requestor does not have an airdrome. No transport by plane possible!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.APC then
if self:GetAirbaseCategory()==Airbase.Category.SHIP or requestcategory==Airbase.Category.SHIP then
self:E("ERROR: Incorrect request. Warehouse or requestor is SHIP. No transport by APC possible!")
valid=false
end
local hasroad=self:HasConnectionRoad(request.warehouse)
if not hasroad then
self:E("ERROR: Incorrect request. No valid path on road for ground transport assets!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
if self:GetAirbaseCategory()==-1 then
self:E("ERROR: Incorrect request. Warehouse has no airbase. Transport by helicopter not possible!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.SHIP or request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
or request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or request.transporttype==WAREHOUSE.TransportType.WARSHIP then
local shippinglane=self:HasConnectionNaval(request.warehouse)
if not shippinglane then
self:E("ERROR: Incorrect request. No shipping lane has been defined between warehouses!")
valid=false
end
elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then
self:E("ERROR: Incorrect request. Transport by TRAIN not implemented yet!")
valid=false
else
self:E("ERROR: Incorrect request. Transport type unknown!")
valid=false
end
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE or request.transporttype==WAREHOUSE.TransportType.HELICOPTER then
local _assets,_nassets,_enough=self:_FilterStock(self.stock,WAREHOUSE.Descriptor.ATTRIBUTE,request.transporttype,request.ntransport,true)
local nasset=request.ntransport
if type(request.ntransport)=="string"then
nasset=self:_QuantityRel2Abs(request.ntransport,_nassets)
end
local text=string.format("Request valid? Number of transports: requested=%s=%d, selected=%d, total=%d, enough=%s.",tostring(request.ntransport),nasset,#_assets,_nassets,tostring(_enough))
self:T(text)
local termtype=self:_GetTerminal(request.transporttype,self:GetAirbaseCategory())
local np_departure=self.airbase:GetParkingSpotsNumber(termtype)
self:T(self.lid..string.format("Transport attribute = %s, terminal type = %d, spots at departure = %d.",request.transporttype,termtype,np_departure))
if np_departure<nasset then
self:E(self.lid..string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.",termtype,np_departure,nasset))
valid=false
end
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
termtype=self:_GetTerminal(request.transporttype,request.warehouse:GetAirbaseCategory())
local np_destination=request.airbase:GetParkingSpotsNumber(termtype)
self:T(self.lid..string.format("Transport attribute = %s: total # of spots (type=%d) at destination = %d.",asset.attribute,termtype,np_destination))
if np_destination==0 then
self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse for transports. Available spots = %d!",termtype,np_destination))
valid=false
end
end
end
end
if valid==false then
self:E(self.lid..string.format("ERROR: Got invalid request id=%d.",request.uid))
else
self:T3(self.lid..string.format("Request id=%d valid :)",request.uid))
end
return valid
end
function WAREHOUSE:_CheckRequestNow(request)
if(request.warehouse:IsRunning()==false)and not(request.toself and self:IsAttacked())then
local text=string.format("Warehouse %s: Request denied! Receiving warehouse %s is not running. Current state %s.",self.alias,request.warehouse.alias,request.warehouse:GetState())
self:_InfoMessage(text,5)
return false
end
local onlymobile=false
if type(request.ntransport)=="number"and request.ntransport==0 and not request.toself then
onlymobile=true
end
local _assets,_nassets,_enough=self:_FilterStock(self.stock,request.assetdesc,request.assetdescval,request.nasset,onlymobile)
if not _enough then
local text=string.format("Warehouse %s: Request ID=%d denied! Not enough (cargo) assets currently available.",self.alias,request.uid)
self:_InfoMessage(text,5)
text=string.format("Enough=%s, #assets=%d, nassets=%d, request.nasset=%s",tostring(_enough),#_assets,_nassets,tostring(request.nasset))
self:T(self.lid..text)
return false
end
local _transports
local _assetattribute
local _assetcategory
local _assetairstart=false
if _nassets>0 then
local asset=_assets[1]
_assetattribute=_assets[1].attribute
_assetcategory=_assets[1].category
_assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false
if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then
if self.airbase.storage then
local nS=self.airbase.storage:GetAmount(asset.unittype)
local nA=asset.nunits*request.nasset
if nS<nA then
local text=string.format("Warehouse %s: Request denied! DCS Warehouse has only %d assets of type %s ==> NOT enough to spawn the requested %d asset units (%d groups)",
self.alias,nS,asset.unittype,nA,request.nasset)
self:_InfoMessage(text,5)
return false
end
end
if self:IsRunwayOperational()or _assetairstart then
if _assetairstart then
else
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.",self.alias)
self:_InfoMessage(text,5)
return false
end
end
else
local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias)
self:_InfoMessage(text,5)
return false
end
else
local text=string.format("Warehouse %s: Request denied! No airbase",self.alias)
self:_InfoMessage(text,5)
return false
end
end
request.cargoassets=_assets
end
if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then
_transports=self:_GetTransportsForAssets(request)
if#_transports>0 then
local _transportattribute=_transports[1].attribute
local _transportcategory=_transports[1].category
if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then
if self:IsRunwayOperational()then
local Parking=self:_FindParkingForAssets(self.airbase,_transports)
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.",self.alias)
self:_InfoMessage(text,5)
return false
end
else
local text=string.format("Warehouse %s: Request denied! Runway is still destroyed",self.alias)
self:_InfoMessage(text,5)
return false
end
else
local text=string.format("Warehouse %s: Request denied! No airbase currently!",self.alias)
self:_InfoMessage(text,5)
return false
end
end
else
local text=string.format("Warehouse %s: Request denied! Not enough transport carriers available at the moment.",self.alias)
self:_InfoMessage(text,5)
return false
end
else
if _assetcategory==Group.Category.GROUND then
local dist=self.warehouse:GetCoordinate():Get2DDistance(self.spawnzone:GetCoordinate())
if dist>self.spawnzonemaxdist then
local text=string.format("Warehouse %s: Request denied! Not close enough to spawn zone. Distance = %d m. We need to be at least within %d m range to spawn.",self.alias,dist,self.spawnzonemaxdist)
self:_InfoMessage(text,5)
return false
end
elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
end
end
request.cargoassets=_assets
request.cargoattribute=_assets[1].attribute
request.cargocategory=_assets[1].category
request.nasset=#_assets
local text=string.format("Selected cargo assets, attibute=%s, category=%d:\n",request.cargoattribute,request.cargocategory)
for _i,_asset in pairs(_assets)do
local asset=_asset
text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d",_i,asset.templatename,asset.unittype,asset.category,asset.nunits)
end
self:T(self.lid..text)
if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then
request.transportassets=_transports
request.transportattribute=_transports[1].attribute
request.transportcategory=_transports[1].category
request.ntransport=#_transports
local text=string.format("Selected transport assets, attibute=%s, category=%d:\n",request.transportattribute,request.transportcategory)
for _i,_asset in pairs(_transports)do
local asset=_asset
text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d\n",_i,asset.templatename,asset.unittype,asset.category,asset.nunits)
end
self:T(self.lid..text)
end
return true
end
function WAREHOUSE:_GetTransportsForAssets(request)
local transports=self:_FilterStock(self.stock,WAREHOUSE.Descriptor.ATTRIBUTE,request.transporttype,nil,true)
local cargoassets=UTILS.DeepCopy(request.cargoassets)
local cargoset=request.transportcargoset
local function sort_transports(a,b)
return a.cargobaymax>b.cargobaymax
end
local function sort_cargoassets(a,b)
return a.weight>b.weight
end
table.sort(transports,sort_transports)
table.sort(cargoassets,sort_cargoassets)
self:T2(self.lid.."Transport capability:")
local totalbay=0
for i=1,#transports do
local transport=transports[i]
for j=1,transport.nunits do
totalbay=totalbay+transport.cargobay[j]
self:T2(self.lid..string.format("Cargo bay = %d  (unit=%d)",transport.cargobay[j],j))
end
end
self:T2(self.lid..string.format("Total capacity = %d",totalbay))
self:T2(self.lid.."Cargo weight:")
local totalcargoweight=0
for i=1,#cargoassets do
local asset=cargoassets[i]
totalcargoweight=totalcargoweight+asset.weight
self:T2(self.lid..string.format("weight = %d",asset.weight))
end
self:T2(self.lid..string.format("Total weight = %d",totalcargoweight))
local used_transports={}
for i=1,#transports do
local transport=transports[i]
local putintocarrier={}
local used=false
for k=1,transport.nunits do
local cargobay=transport.cargobay[k]
for j,asset in pairs(cargoassets)do
local asset=asset
local delta=cargobay-asset.weight
if delta>=0 then
cargobay=cargobay-asset.weight
self:T3(self.lid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d",transport.templatename,k,asset.uid,transport.cargobay[k],cargobay,asset.weight))
table.insert(putintocarrier,j)
used=true
else
self:T2(self.lid..string.format("Carrier unit %s too small for cargo asset %s ==> cannot be used! Cargo bay - asset weight = %d kg",transport.templatename,asset.templatename,delta))
end
end
end
for j=#putintocarrier,1,-1 do
local nput=putintocarrier[j]
local cargo=cargoassets[nput]
if cargo then
self:T2(self.lid..string.format("Cargo id=%d assigned for carrier id=%d",cargo.uid,transport.uid))
table.remove(cargoassets,nput)
end
end
if used then
table.insert(used_transports,transport)
end
local ntrans=self:_QuantityRel2Abs(request.ntransport,#transports)
if#used_transports>=ntrans then
request.ntransport=#used_transports
break
end
end
local text=string.format("Used Transports for request %d to warehouse %s:\n",request.uid,request.warehouse.alias)
local totalcargobay=0
for _i,_transport in pairs(used_transports)do
local transport=_transport
text=text..string.format("%d) %s: cargobay tot = %d kg, cargobay max = %d kg, nunits=%d\n",_i,transport.unittype,transport.cargobaytot,transport.cargobaymax,transport.nunits)
totalcargobay=totalcargobay+transport.cargobaytot
end
text=text..string.format("Total cargo bay capacity = %.1f kg\n",totalcargobay)
text=text..string.format("Total cargo weight       = %.1f kg\n",totalcargoweight)
text=text..string.format("Minimum number of runs   = %.1f",totalcargoweight/totalcargobay)
self:_DebugMessage(text)
return used_transports
end
function WAREHOUSE:_QuantityRel2Abs(relative,ntot)
local nabs=0
if type(relative)=="string"then
if relative==WAREHOUSE.Quantity.ALL then
nabs=ntot
elseif relative==WAREHOUSE.Quantity.THREEQUARTERS then
nabs=UTILS.Round(ntot*3/4)
elseif relative==WAREHOUSE.Quantity.HALF then
nabs=UTILS.Round(ntot/2)
elseif relative==WAREHOUSE.Quantity.THIRD then
nabs=UTILS.Round(ntot/3)
elseif relative==WAREHOUSE.Quantity.QUARTER then
nabs=UTILS.Round(ntot/4)
else
nabs=math.min(1,ntot)
end
else
nabs=relative
end
self:T2(self.lid..string.format("Relative %s: tot=%d, abs=%.2f",tostring(relative),ntot,nabs))
return nabs
end
function WAREHOUSE:_CheckQueue()
self:_SortQueue()
local request=nil
local invalid={}
local gotit=false
for _,_qitem in ipairs(self.queue)do
local qitem=_qitem
local valid=self:_CheckRequestValid(qitem)
local okay=false
if valid then
okay=self:_CheckRequestNow(qitem)
else
table.insert(invalid,qitem)
end
if okay and valid and not gotit then
request=qitem
gotit=true
break
end
end
for _,_request in pairs(invalid)do
self:T(self.lid..string.format("Deleting invalid request id=%d.",_request.uid))
self:_DeleteQueueItem(_request,self.queue)
end
return request
end
function WAREHOUSE:_SimpleTaskFunction(Function,group)
self:F2({Function})
local warehouse=self.warehouse:GetName()
local groupname=group:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mygroup     = GROUP:FindByName(\"%s\") ',groupname)
if self.isUnit then
DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse)
else
DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse)
end
DCSScript[#DCSScript+1]=string.format('local warehouse   = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ')
DCSScript[#DCSScript+1]=string.format('%s(mygroup)',Function)
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function WAREHOUSE:_SimpleTaskFunctionWP(Function,group,n,N)
self:F2({Function})
local warehouse=self.warehouse:GetName()
local groupname=group:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mygroup     = GROUP:FindByName(\"%s\") ',groupname)
if self.isUnit then
DCSScript[#DCSScript+1]=string.format("local mywarehouse = UNIT:FindByName(\"%s\") ",warehouse)
else
DCSScript[#DCSScript+1]=string.format("local mywarehouse = STATIC:FindByName(\"%s\") ",warehouse)
end
DCSScript[#DCSScript+1]=string.format('local warehouse   = mywarehouse:GetState(mywarehouse, \"WAREHOUSE\") ')
DCSScript[#DCSScript+1]=string.format('%s(mygroup, %d, %d)',Function,n,N)
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function WAREHOUSE:_GetTerminal(_attribute,_category)
local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER or _attribute==WAREHOUSE.Attribute.AIR_UAV then
_terminal=AIRBASE.TerminalType.FighterAircraft
elseif _attribute==WAREHOUSE.Attribute.AIR_BOMBER or _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTPLANE or _attribute==WAREHOUSE.Attribute.AIR_TANKER or _attribute==WAREHOUSE.Attribute.AIR_AWACS then
_terminal=AIRBASE.TerminalType.OpenBig
elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then
_terminal=AIRBASE.TerminalType.HelicopterUsable
else
end
if _category==Airbase.Category.SHIP then
if not(_attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO)then
_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
end
return _terminal
end
function WAREHOUSE:_FindParkingForAssets(airbase,assets)
local scanradius=25
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
local function _overlap(l1,l2,dist)
local safedist=(l1/2+l2/2)*1.05
local safe=(dist>safedist)
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s",l1,l2,safedist,dist,tostring(safe)))
return safe
end
local function _clients()
local coords={}
if not self.allowSpawnOnClientSpots then
local clients=_DATABASE.CLIENTS
for clientname,client in pairs(clients)do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
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
end
return coords
end
local parkingdata=airbase.parking
local obstacles={}
self.clientcoords=self.clientcoords or _clients()
for clientname,_coord in pairs(self.clientcoords)do
table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"})
end
for _,parkingspot in pairs(parkingdata)do
local _spot=parkingspot.Coordinate
local _termid=parkingspot.TerminalID
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius,scanunits,scanstatics,scanscenery)
for _,_unit in pairs(_units)do
local unit=_unit
local _coord=unit:GetVec3()
local _size=self:_GetObjectSize(unit:GetDCSObject())
local _name=unit:GetName()
if unit and unit:IsAlive()then
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"})
end
end
for _,static in pairs(_statics)do
local _coord=static:getPoint()
local _name=static:getName()
local _size=self:_GetObjectSize(static)
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"})
end
for _,scenery in pairs(_sceneries)do
local _coord=scenery:getPoint()
local _name=scenery:getTypeName()
local _size=self:_GetObjectSize(scenery)
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"})
end
end
local parking={}
for _,asset in pairs(assets)do
local _asset=asset
if not _asset.spawned then
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory())
parking[_asset.uid]={}
for i=1,_asset.nunits do
local assetname=_asset.spawngroupname.."-"..tostring(i)
local gotit=false
for _,_parkingspot in pairs(parkingdata)do
local parkingspot=_parkingspot
local valid=true
if asset.parkingIDs then
valid=self:_CheckParkingAsset(parkingspot,asset)
else
local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)
local validParking=self:_CheckParkingValid(parkingspot)
local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID)
valid=validTerminal and validParking and validBWlist
end
if valid then
local _spot=parkingspot.Coordinate
local _termid=parkingspot.TerminalID
local free=true
local problem=nil
for _,obstacle in pairs(obstacles)do
local dist=_spot:Get2DDistance(obstacle.coord)
local safe=_overlap(_asset.size,obstacle.size,dist)
if not safe then
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE",assetname,_asset.uid,_termid,dist))
free=false
problem=obstacle
problem.dist=dist
break
else
end
end
if free then
table.insert(parking[_asset.uid],parkingspot)
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!",_termid,assetname,_asset.uid))
table.insert(obstacles,{coord=_spot,size=_asset.size,name=assetname,type="asset"})
gotit=true
break
else
if self.Debug then
local coord=problem.coord
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(text)
end
else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!",_termid))
end
end
else
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported",parkingspot.TerminalID,parkingspot.TerminalType))
end
end
if not gotit then
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]",assetname,_asset.uid))
return nil
end
end
end
end
return parking
end
function WAREHOUSE:_GetRequestOfGroup(group,queue)
local wid,aid,rid=self:_GetIDsFromGroup(group)
for _,_request in pairs(queue)do
local request=_request
if request.uid==rid then
return request
end
end
end
function WAREHOUSE:_GroupIsTransport(group,request)
local asset=self:FindAssetInDB(group)
if asset and asset.iscargo~=nil then
return not asset.iscargo
else
local groupname=self:_GetNameWithOut(group)
if request.transportgroupset then
local transporters=request.transportgroupset:GetSetObjects()
for _,transport in pairs(transporters)do
if transport:GetName()==groupname then
return true
end
end
end
if request.cargogroupset then
local cargos=request.cargogroupset:GetSetObjects()
for _,cargo in pairs(cargos)do
if self:_GetNameWithOut(cargo)==groupname then
return false
end
end
end
end
return nil
end
function WAREHOUSE:_GetNameWithOut(group)
local groupname=type(group)=="string"and group or group:GetName()
if groupname:find("CARGO")then
local name=groupname:gsub("#CARGO","")
return name
else
return groupname
end
end
function WAREHOUSE:_GetIDsFromGroup(group)
if group then
local groupname=group:GetName()
local wid,aid,rid=self:_GetIDsFromGroupName(groupname)
return wid,aid,rid
else
self:E("WARNING: Group not found in GetIDsFromGroup() function!")
end
end
function WAREHOUSE:_GetIDsFromGroupName(groupname)
local function analyse(text)
local unspawned=UTILS.Split(text,"#")[1]
local keywords=UTILS.Split(unspawned,"_")
local _wid=nil
local _aid=nil
local _rid=nil
for _,keys in pairs(keywords)do
local str=UTILS.Split(keys,"-")
local key=str[1]
local val=str[2]
if key:find("WID")then
_wid=tonumber(val)
elseif key:find("AID")then
_aid=tonumber(val)
elseif key:find("RID")then
_rid=tonumber(val)
end
end
return _wid,_aid,_rid
end
local wid,aid,rid=analyse(groupname)
local asset=self:GetAssetByID(aid)
if asset then
wid=asset.wid
rid=asset.rid
end
self:T3(self.lid..string.format("Group Name   = %s",tostring(groupname)))
self:T3(self.lid..string.format("Warehouse ID = %s",tostring(wid)))
self:T3(self.lid..string.format("Asset     ID = %s",tostring(aid)))
self:T3(self.lid..string.format("Request   ID = %s",tostring(rid)))
return wid,aid,rid
end
function WAREHOUSE:FilterStock(descriptor,attribute,nmax,mobile)
return self:_FilterStock(self.stock,descriptor,attribute,nmax,mobile)
end
function WAREHOUSE:_FilterStock(stock,descriptor,attribute,nmax,mobile)
nmax=nmax or WAREHOUSE.Quantity.ALL
if mobile==nil then
mobile=false
end
local filtered={}
if descriptor==WAREHOUSE.Descriptor.ASSETLIST then
local ntot=0
for _,_rasset in pairs(attribute)do
local rasset=_rasset
for _,_asset in ipairs(stock)do
local asset=_asset
if rasset.uid==asset.uid then
table.insert(filtered,asset)
break
end
end
end
return filtered,#filtered,#filtered>=#attribute
end
local ntot=0
for _,_asset in ipairs(stock)do
local asset=_asset
local ismobile=asset.speedmax>0
if asset[descriptor]==attribute then
if(mobile==true and ismobile)or mobile==false then
ntot=ntot+1
end
end
end
if ntot==0 then
return filtered,ntot,false
end
nmax=self:_QuantityRel2Abs(nmax,ntot)
for _i,_asset in ipairs(stock)do
local asset=_asset
if asset[descriptor]==attribute then
if(mobile and asset.speedmax>0)or(not mobile)then
table.insert(filtered,asset)
if nmax~=nil and#filtered>=nmax then
return filtered,ntot,true
end
end
end
end
return filtered,ntot,ntot>=nmax
end
function WAREHOUSE:_HasAttribute(group,attribute)
if group then
local groupattribute=self:_GetAttribute(group)
return groupattribute==attribute
end
return false
end
function WAREHOUSE:_GetAttribute(group)
local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN
if group then
local 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")
local tanker=group:HasAttribute("Tankers")
local uav=group:HasAttribute("UAVs")
local transporthelo=group:HasAttribute("Transport helicopters")
local attackhelicopter=group:HasAttribute("Attack helicopters")
local apc=group:HasAttribute("APC")
local truck=group:HasAttribute("Trucks")and group:GetCategory()==Group.Category.GROUND
local infantry=group:HasAttribute("Infantry")
local ifv=group:HasAttribute("IFV")
local artillery=group:HasAttribute("Artillery")
local tank=group:HasAttribute("Old Tanks")or group:HasAttribute("Modern Tanks")
local aaa=group:HasAttribute("AAA")
local ewr=group:HasAttribute("EWR")
local sam=group:HasAttribute("SAM elements")and(not group:HasAttribute("AAA"))
local train=group:GetCategory()==Group.Category.TRAIN
local aircraftcarrier=group:HasAttribute("Aircraft Carriers")
local warship=group:HasAttribute("Heavy armed ships")
local armedship=group:HasAttribute("Armed ships")or group:HasAttribute("Armed Ship")
local unarmedship=group:HasAttribute("Unarmed ships")
if transportplane then
attribute=WAREHOUSE.Attribute.AIR_TRANSPORTPLANE
elseif awacs then
attribute=WAREHOUSE.Attribute.AIR_AWACS
elseif fighter then
attribute=WAREHOUSE.Attribute.AIR_FIGHTER
elseif bomber then
attribute=WAREHOUSE.Attribute.AIR_BOMBER
elseif tanker then
attribute=WAREHOUSE.Attribute.AIR_TANKER
elseif transporthelo then
attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO
elseif attackhelicopter then
attribute=WAREHOUSE.Attribute.AIR_ATTACKHELO
elseif uav then
attribute=WAREHOUSE.Attribute.AIR_UAV
elseif apc then
attribute=WAREHOUSE.Attribute.GROUND_APC
elseif ifv then
attribute=WAREHOUSE.Attribute.GROUND_IFV
elseif infantry then
attribute=WAREHOUSE.Attribute.GROUND_INFANTRY
elseif artillery then
attribute=WAREHOUSE.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=WAREHOUSE.Attribute.GROUND_TANK
elseif aaa then
attribute=WAREHOUSE.Attribute.GROUND_AAA
elseif ewr then
attribute=WAREHOUSE.Attribute.GROUND_EWR
elseif sam then
attribute=WAREHOUSE.Attribute.GROUND_SAM
elseif truck then
attribute=WAREHOUSE.Attribute.GROUND_TRUCK
elseif train then
attribute=WAREHOUSE.Attribute.GROUND_TRAIN
elseif aircraftcarrier then
attribute=WAREHOUSE.Attribute.NAVAL_AIRCRAFTCARRIER
elseif warship then
attribute=WAREHOUSE.Attribute.NAVAL_WARSHIP
elseif armedship then
attribute=WAREHOUSE.Attribute.NAVAL_ARMEDSHIP
elseif unarmedship then
attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP
else
if group:IsGround()then
attribute=WAREHOUSE.Attribute.GROUND_OTHER
elseif group:IsShip()then
attribute=WAREHOUSE.Attribute.NAVAL_OTHER
elseif group:IsAir()then
attribute=WAREHOUSE.Attribute.AIR_OTHER
else
attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN
end
end
end
return attribute
end
function WAREHOUSE:_GetObjectSize(DCSobject)
local DCSdesc=DCSobject:getDesc()
if DCSdesc.box then
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x)
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y)
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z)
return math.max(x,z),x,y,z
end
return 0,0,0,0
end
function WAREHOUSE:GetStockInfo(stock)
local _data={}
for _j,_attribute in pairs(WAREHOUSE.Attribute)do
local n=0
for _i,_item in pairs(stock)do
local _ite=_item
if _ite.attribute==_attribute then
n=n+1
end
end
_data[_attribute]=n
end
return _data
end
function WAREHOUSE:_DeleteStockItem(stockitem)
for i=1,#self.stock do
local item=self.stock[i]
if item.uid==stockitem.uid then
table.remove(self.stock,i)
break
end
end
end
function WAREHOUSE:_DeleteQueueItem(qitem,queue)
for i=1,#queue do
local _item=queue[i]
if _item.uid==qitem.uid then
self:T(self.lid..string.format("Deleting queue item id=%d.",qitem.uid))
table.remove(queue,i)
break
end
end
end
function WAREHOUSE:_DeleteQueueItemByID(qitemID,queue)
for i=1,#queue do
local _item=queue[i]
if _item.uid==qitemID then
self:T(self.lid..string.format("Deleting queue item id=%d.",qitemID))
table.remove(queue,i)
break
end
end
end
function WAREHOUSE:_SortQueue()
self:F3()
local function _sort(a,b)
return(a.prio<b.prio)or(a.prio==b.prio and a.uid<b.uid)
end
table.sort(self.queue,_sort)
end
function WAREHOUSE:_CheckFuel()
for i,qitem in ipairs(self.pending)do
local qitem=qitem
if qitem.transportgroupset then
for _,_group in pairs(qitem.transportgroupset:GetSet())do
local group=_group
if group and group:IsAlive()then
local fuel=group:GetFuelMin()
self:T2(self.lid..string.format("Transport group %s min fuel state = %.2f",group:GetName(),fuel))
if fuel<self.lowfuelthresh and not qitem.lowfuel then
self:I(self.lid..string.format("Transport group %s is low on fuel! Min fuel state = %.2f",group:GetName(),fuel))
qitem.lowfuel=true
local asset=self:FindAssetInDB(group)
self:AssetLowFuel(asset,qitem)
break
end
end
end
end
if qitem.cargogroupset then
for _,_group in pairs(qitem.cargogroupset:GetSet())do
local group=_group
if group and group:IsAlive()then
local fuel=group:GetFuelMin()
self:T2(self.lid..string.format("Cargo group %s min fuel state = %.2f. Threshold = %.2f",group:GetName(),fuel,self.lowfuelthresh))
if fuel<self.lowfuelthresh and not qitem.lowfuel then
self:I(self.lid..string.format("Cargo group %s is low on fuel! Min fuel state = %.2f",group:GetName(),fuel))
qitem.lowfuel=true
local asset=self:FindAssetInDB(group)
self:AssetLowFuel(asset,qitem)
break
end
end
end
end
end
end
function WAREHOUSE:_PrintQueue(queue,name)
if self.verbosity>=2 then
local total="Empty"
if#queue>0 then
total=string.format("Total = %d",#queue)
end
local text=string.format("%s at %s: %s",name,self.alias,total)
for i,qitem in ipairs(queue)do
local qitem=qitem
local uid=qitem.uid
local prio=qitem.prio
local clock="N/A"
if qitem.timestamp then
clock=tostring(UTILS.SecondsToClock(qitem.timestamp))
end
local assignment=tostring(qitem.assignment)
local requestor=qitem.warehouse.alias
local airbasename=qitem.warehouse:GetAirbaseName()
local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory()
local assetdesc=qitem.assetdesc
local assetdescval=qitem.assetdescval
if assetdesc==WAREHOUSE.Descriptor.ASSETLIST then
assetdescval="Asset list"
end
local nasset=tostring(qitem.nasset)
local ndelivered=tostring(qitem.ndelivered)
local ncargogroupset="N/A"
if qitem.cargogroupset then
ncargogroupset=tostring(qitem.cargogroupset:Count())
end
local transporttype="N/A"
if qitem.transporttype then
transporttype=qitem.transporttype
end
local ntransport="N/A"
if qitem.ntransport then
ntransport=tostring(qitem.ntransport)
end
local ntransportalive="N/A"
if qitem.transportgroupset then
ntransportalive=tostring(qitem.transportgroupset:Count())
end
local ntransporthome="N/A"
if qitem.ntransporthome then
ntransporthome=tostring(qitem.ntransporthome)
end
text=text..string.format(
"\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s",
i,uid,prio,clock,assignment,requestor,airbasename,requestorAirbaseCat,assetdesc,assetdescval,nasset,ncargogroupset,ndelivered,transporttype,ntransport,ntransportalive,ntransporthome)
end
if#queue==0 then
self:I(self.lid..text)
else
if total~="Empty"then
self:I(self.lid..text)
end
end
end
end
function WAREHOUSE:_DisplayStatus()
if self.verbosity>=3 then
local text=string.format("\n------------------------------------------------------\n")
text=text..string.format("Warehouse %s status: %s\n",self.alias,self:GetState())
text=text..string.format("------------------------------------------------------\n")
text=text..string.format("Coalition name   = %s\n",self:GetCoalitionName())
text=text..string.format("Country name     = %s\n",self:GetCountryName())
text=text..string.format("Airbase name     = %s (category=%d)\n",self:GetAirbaseName(),self:GetAirbaseCategory())
text=text..string.format("Queued requests  = %d\n",#self.queue)
text=text..string.format("Pending requests = %d\n",#self.pending)
text=text..string.format("------------------------------------------------------\n")
text=text..self:_GetStockAssetsText()
self:I(text)
end
end
function WAREHOUSE:_GetStockAssetsText(messagetoall)
local _data=self:GetStockInfo(self.stock)
local text="Stock:\n"
local total=0
for _attribute,_count in pairs(_data)do
if _count>0 then
local attribute=tostring(UTILS.Split(_attribute,"_")[2])
text=text..string.format("%s = %d\n",attribute,_count)
total=total+_count
end
end
text=text..string.format("===================\n")
text=text..string.format("Total = %d\n",total)
text=text..string.format("------------------------------------------------------\n")
MESSAGE:New(text,10):ToAllIf(messagetoall)
return text
end
function WAREHOUSE:_UpdateWarehouseMarkText()
if self.markerOn then
local text=string.format("Warehouse state: %s\nTotal assets in stock %d:\n",self:GetState(),#self.stock)
for _attribute,_count in pairs(self:GetStockInfo(self.stock)or{})do
if _count>0 then
local attribute=tostring(UTILS.Split(_attribute,"_")[2])
text=text..string.format("%s=%d, ",attribute,_count)
end
end
local coordinate=self:GetCoordinate()
local coalition=self:GetCoalition()
if not self.markerWarehouse then
self.markerWarehouse=MARKER:New(coordinate,text):ToCoalition(coalition)
else
local refresh=false
if self.markerWarehouse.text~=text then
self.markerWarehouse.text=text
refresh=true
end
if self.markerWarehouse.coordinate~=coordinate then
self.markerWarehouse.coordinate=coordinate
refresh=true
end
if self.markerWarehouse.coalition~=coalition then
self.markerWarehouse.coalition=coalition
refresh=true
end
if refresh then
self.markerWarehouse:Refresh()
end
end
end
end
function WAREHOUSE:_DisplayStockItems(stock)
local text=self.lid..string.format("Warehouse %s stock assets:",self.alias)
for _i,_stock in pairs(stock)do
local mystock=_stock
local name=mystock.templatename
local category=mystock.category
local cargobaymax=mystock.cargobaymax
local cargobaytot=mystock.cargobaytot
local nunits=mystock.nunits
local range=mystock.range
local size=mystock.size
local speed=mystock.speedmax
local uid=mystock.uid
local unittype=mystock.unittype
local weight=mystock.weight
local attribute=mystock.attribute
text=text..string.format("\n%02d) uid=%d, name=%s, unittype=%s, category=%d, attribute=%s, nunits=%d, speed=%.1f km/h, range=%.1f km, size=%.1f m, weight=%.1f kg, cargobax max=%.1f kg tot=%.1f kg",
_i,uid,name,unittype,category,attribute,nunits,speed,range/1000,size,weight,cargobaymax,cargobaytot)
end
self:T3(text)
end
function WAREHOUSE:_Fireworks(coord)
coord=coord or self:GetCoordinate()
for i=1,91 do
local color=math.random(0,3)
coord:Flare(color,i-1)
end
end
function WAREHOUSE:_InfoMessage(text,duration)
duration=duration or 20
if duration>0 and self.Debug or self.Report then
MESSAGE:New(text,duration):ToCoalition(self:GetCoalition())
end
self:I(self.lid..text)
end
function WAREHOUSE:_DebugMessage(text,duration)
duration=duration or 20
if self.Debug and duration>0 then
MESSAGE:New(text,duration):ToAllIf(self.Debug)
end
self:T(self.lid..text)
end
function WAREHOUSE:_ErrorMessage(text,duration)
duration=duration or 20
if duration>0 then
MESSAGE:New(text,duration):ToAll()
end
self:E(self.lid..text)
end
function WAREHOUSE:_GetMaxHeight(D,alphaC,alphaD,Hdep,Hdest,Deltahhold)
local Hhold=Hdest+Deltahhold
local hdest=Hdest-Hdep
local hhold=hdest+Deltahhold
local Dp=math.sqrt(D^2+hhold^2)
local alphaS=math.atan(hdest/D)
local alphaH=math.atan(hhold/D)
local alphaCp=alphaC-alphaH
local alphaDp=alphaD+alphaH
local gammap=math.pi-alphaCp-alphaDp
local sCp=Dp*math.sin(alphaDp)/math.sin(gammap)
local sDp=Dp*math.sin(alphaCp)/math.sin(gammap)
local hmax=sCp*math.sin(alphaC)
if self.Debug then
env.info(string.format("Hdep    = %.3f km",Hdep/1000))
env.info(string.format("Hdest   = %.3f km",Hdest/1000))
env.info(string.format("DetaHold= %.3f km",Deltahhold/1000))
env.info()
env.info(string.format("D       = %.3f km",D/1000))
env.info(string.format("Dp      = %.3f km",Dp/1000))
env.info()
env.info(string.format("alphaC  = %.3f Deg",math.deg(alphaC)))
env.info(string.format("alphaCp = %.3f Deg",math.deg(alphaCp)))
env.info()
env.info(string.format("alphaD  = %.3f Deg",math.deg(alphaD)))
env.info(string.format("alphaDp = %.3f Deg",math.deg(alphaDp)))
env.info()
env.info(string.format("alphaS  = %.3f Deg",math.deg(alphaS)))
env.info(string.format("alphaH  = %.3f Deg",math.deg(alphaH)))
env.info()
env.info(string.format("sCp      = %.3f km",sCp/1000))
env.info(string.format("sDp      = %.3f km",sDp/1000))
env.info()
env.info(string.format("hmax     = %.3f km",hmax/1000))
env.info()
local hdescent=hmax-hhold
local dClimb=hmax/math.tan(alphaC)
local dDescent=(hmax-hhold)/math.tan(alphaD)
local dCruise=D-dClimb-dDescent
env.info(string.format("hmax     = %.3f km",hmax/1000))
env.info(string.format("hdescent = %.3f km",hdescent/1000))
env.info(string.format("Dclimb   = %.3f km",dClimb/1000))
env.info(string.format("Dcruise  = %.3f km",dCruise/1000))
env.info(string.format("Ddescent = %.3f km",dDescent/1000))
env.info()
end
return hmax
end
function WAREHOUSE:_GetFlightplan(asset,departure,destination)
local Vmax=asset.speedmax/3.6
local Range=asset.range
local category=asset.category
local ceiling=asset.DCSdesc.Hmax
local Vymax=asset.DCSdesc.VyMax
local VxCruiseMax=0.90*Vmax
local VxCruiseMin=math.min(VxCruiseMax*0.70,166)
local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax)
local VxClimb=math.min(Vmax*0.90,200)
local VxDescent=math.min(Vmax*0.60,140)
local VxHolding=VxDescent*0.9
local VxFinal=VxHolding*0.9
local VyClimb=math.min(7.6,Vymax)
local AlphaClimb=math.rad(4)
local AlphaDescent=math.rad(4)
local FLcruise_expect=150*RAT.unit.FL2m
if category==Group.Category.HELICOPTER then
FLcruise_expect=1000
end
local Pdeparture=departure:GetCoordinate()
local H_departure=Pdeparture.y
local Pdestination=destination:GetCoordinate()
local H_destination=Pdestination.y
local Rhmin=5000
local Rhmax=10000
if category==Group.Category.HELICOPTER then
Rhmin=500
Rhmax=1000
end
local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax,Rhmin)
local d_holding=Pholding:Get2DDistance(Pdestination)
local H_holding=Pholding.y
local heading=Pdeparture:HeadingTo(Pholding)
local d_total=Pdeparture:Get2DDistance(Pholding)
local h_holding=1200
if category==Group.Category.HELICOPTER then
h_holding=150
end
h_holding=UTILS.Randomize(h_holding,0.2)
local DeltaholdingMax=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,0)
if h_holding>DeltaholdingMax then
h_holding=math.abs(DeltaholdingMax)
end
local Hh_holding=H_holding+h_holding
local h_max=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,h_holding)
local FLmax=h_max+H_departure
local FLmin=math.max(H_departure,Hh_holding)
FLmax=math.min(FLmax,ceiling)
if FLmin>FLmax then
FLmin=FLmax
end
if FLcruise_expect<FLmin then
FLcruise_expect=FLmin
end
if FLcruise_expect>FLmax then
FLcruise_expect=FLmax
end
local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax)
local h_climb=FLcruise-H_departure
local h_descent=FLcruise-Hh_holding
local d_climb=h_climb/math.tan(AlphaClimb)
local d_descent=h_descent/math.tan(AlphaDescent)
local d_cruise=d_total-d_climb-d_descent
local text=string.format("Flight plan:\n")
text=text..string.format("Vx max        = %.2f km/h\n",Vmax*3.6)
text=text..string.format("Vx climb      = %.2f km/h\n",VxClimb*3.6)
text=text..string.format("Vx cruise     = %.2f km/h\n",VxCruise*3.6)
text=text..string.format("Vx descent    = %.2f km/h\n",VxDescent*3.6)
text=text..string.format("Vx holding    = %.2f km/h\n",VxHolding*3.6)
text=text..string.format("Vx final      = %.2f km/h\n",VxFinal*3.6)
text=text..string.format("Vy max        = %.2f m/s\n",Vymax)
text=text..string.format("Vy climb      = %.2f m/s\n",VyClimb)
text=text..string.format("Alpha Climb   = %.2f Deg\n",math.deg(AlphaClimb))
text=text..string.format("Alpha Descent = %.2f Deg\n",math.deg(AlphaDescent))
text=text..string.format("Dist climb    = %.3f km\n",d_climb/1000)
text=text..string.format("Dist cruise   = %.3f km\n",d_cruise/1000)
text=text..string.format("Dist descent  = %.3f km\n",d_descent/1000)
text=text..string.format("Dist total    = %.3f km\n",d_total/1000)
text=text..string.format("h_climb       = %.3f km\n",h_climb/1000)
text=text..string.format("h_desc        = %.3f km\n",h_descent/1000)
text=text..string.format("h_holding     = %.3f km\n",h_holding/1000)
text=text..string.format("h_max         = %.3f km\n",h_max/1000)
text=text..string.format("FL min        = %.3f km\n",FLmin/1000)
text=text..string.format("FL expect     = %.3f km\n",FLcruise_expect/1000)
text=text..string.format("FL cruise *   = %.3f km\n",FLcruise/1000)
text=text..string.format("FL max        = %.3f km\n",FLmax/1000)
text=text..string.format("Ceiling       = %.3f km\n",ceiling/1000)
text=text..string.format("Max range     = %.3f km\n",Range/1000)
self:T(self.lid..text)
if d_cruise<0 then
d_cruise=100
end
local wp={}
local c={}
local _type=COORDINATE.WaypointType.TakeOffParking
local _action=COORDINATE.WaypointAction.FromParkingArea
if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then
_type=COORDINATE.WaypointType.TakeOffParkingHot
_action=COORDINATE.WaypointAction.FromParkingAreaHot
else
end
c[#c+1]=Pdeparture
wp[#wp+1]=Pdeparture:WaypointAir("RADIO",_type,_action,VxClimb*3.6,true,departure,nil,"Departure")
local Pcruise=Pdeparture:Translate(d_climb,heading)
Pcruise.y=FLcruise
c[#c+1]=Pcruise
wp[#wp+1]=Pcruise:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxCruise*3.6,true,nil,nil,"Cruise")
local Pdescent=Pcruise:Translate(d_cruise,heading)
Pdescent.y=FLcruise
c[#c+1]=Pdescent
wp[#wp+1]=Pdescent:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxDescent*3.6,true,nil,nil,"Descent")
Pholding.y=H_holding+h_holding
c[#c+1]=Pholding
wp[#wp+1]=Pholding:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,VxHolding*3.6,true,nil,nil,"Holding")
c[#c+1]=Pdestination
wp[#wp+1]=Pdestination:WaypointAir("RADIO",COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,VxFinal*3.6,true,destination,nil,"Final Destination")
if self.Debug then
for i,coord in pairs(c)do
local coord=coord
local dist=0
if i>1 then
dist=coord:Get2DDistance(c[i-1])
end
coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i,dist/1000))
end
end
return wp,c
end
FOX={
ClassName="FOX",
verbose=0,
Debug=false,
lid=nil,
menuadded={},
menudisabled=nil,
destroy=nil,
launchalert=nil,
marklaunch=nil,
missiles={},
players={},
safezones={},
launchzones={},
protectedset=nil,
explosionpower=0.1,
explosiondist=200,
explosiondist2=500,
bigmissilemass=50,
dt50=5,
dt10=1,
dt05=0.5,
dt01=0.1,
dt00=0.01,
}
FOX.MenuF10={}
FOX.MenuF10Root=nil
FOX.version="0.8.0"
function FOX:New()
self.lid="FOX | "
local self=BASE:Inherit(self,FSM:New())
self:SetDefaultMissileDestruction(true)
self:SetDefaultLaunchAlerts(true)
self:SetDefaultLaunchMarks(true)
self:SetExplosionDistance()
self:SetExplosionDistanceBigMissiles()
self:SetExplosionPower()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","MissileLaunch","*")
self:AddTransition("*","MissileDestroyed","*")
self:AddTransition("*","EnterSafeZone","*")
self:AddTransition("*","ExitSafeZone","*")
self:AddTransition("Running","Stop","Stopped")
return self
end
function FOX:onafterStart(From,Event,To)
local text=string.format("Starting FOX Missile Trainer %s",FOX.version)
env.info(text)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Shot)
if self.Debug then
self:HandleEvent(EVENTS.Hit)
end
if self.Debug then
self:TraceClass(self.ClassName)
self:TraceLevel(2)
end
self:__Status(-20)
end
function FOX:onafterStop(From,Event,To)
local text=string.format("Stopping FOX Missile Trainer %s",FOX.version)
env.info(text)
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Shot)
if self.Debug then
self:UnhandleEvent(EVENTS.Hit)
end
end
function FOX:AddSafeZone(zone)
table.insert(self.safezones,zone)
return self
end
function FOX:AddLaunchZone(zone)
table.insert(self.launchzones,zone)
return self
end
function FOX:SetProtectedGroupSet(groupset)
self.protectedset=groupset
return self
end
function FOX:AddProtectedGroup(group)
if not self.protectedset then
self.protectedset=SET_GROUP:New()
end
self.protectedset:AddGroup(group)
return self
end
function FOX:SetExplosionPower(power)
self.explosionpower=power or 0.1
return self
end
function FOX:SetExplosionDistance(distance)
self.explosiondist=distance or 200
return self
end
function FOX:SetExplosionDistanceBigMissiles(distance,explosivemass)
self.explosiondist2=distance or 500
self.bigmissilemass=explosivemass or 50
return self
end
function FOX:SetDisableF10Menu()
self.menudisabled=true
return self
end
function FOX:SetEnableF10Menu()
self.menudisabled=false
return self
end
function FOX:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function FOX:SetDefaultMissileDestruction(switch)
if switch==nil then
self.destroy=false
else
self.destroy=switch
end
return self
end
function FOX:SetDefaultLaunchAlerts(switch)
if switch==nil then
self.launchalert=false
else
self.launchalert=switch
end
return self
end
function FOX:SetDefaultLaunchMarks(switch)
if switch==nil then
self.marklaunch=false
else
self.marklaunch=switch
end
return self
end
function FOX:SetDebugOnOff(switch)
if switch==nil then
self.Debug=false
else
self.Debug=switch
end
return self
end
function FOX:SetDebugOn()
self:SetDebugOnOff(true)
return self
end
function FOX:SetDebugOff()
self:SetDebugOff(false)
return self
end
function FOX:onafterStatus(From,Event,To)
local fsmstate=self:GetState()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time)
if self.verbose>=1 then
self:I(self.lid..string.format("Missile trainer status %s: %s",clock,fsmstate))
end
self:_CheckMissileStatus()
self:_CheckPlayers()
if fsmstate=="Running"then
self:__Status(-10)
end
end
function FOX:_CheckPlayers()
for playername,_playersettings in pairs(self.players)do
local playersettings=_playersettings
local unitname=playersettings.unitname
local unit=UNIT:FindByName(unitname)
if unit and unit:IsAlive()then
local coord=unit:GetCoordinate()
local issafe=self:_CheckCoordSafe(coord)
if issafe then
if not playersettings.inzone then
self:EnterSafeZone(playersettings)
playersettings.inzone=true
end
else
if playersettings.inzone==true then
self:ExitSafeZone(playersettings)
playersettings.inzone=false
end
end
end
end
end
function FOX:_RemoveMissile(missile)
if missile then
for i,_missile in pairs(self.missiles)do
local m=_missile
if missile.missileName==m.missileName then
table.remove(self.missiles,i)
return
end
end
end
end
function FOX:_CheckMissileStatus()
local text="Missiles:"
local inactive={}
for i,_missile in pairs(self.missiles)do
local missile=_missile
local targetname="unkown"
if missile.targetUnit then
targetname=missile.targetUnit:GetName()
end
local playername="none"
if missile.targetPlayer then
playername=missile.targetPlayer.name
end
local active=tostring(missile.active)
local mtype=missile.missileType
local dtype=missile.missileType
local range=UTILS.MetersToNM(missile.missileRange)
if not active then
table.insert(inactive,i)
end
local heading=self:_GetWeapongHeading(missile.weapon)
text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s",i,mtype,active,range,heading,targetname,playername,missile.missileName)
end
if#self.missiles==0 then
text=text.." none"
end
if self.verbose>=2 then
self:I(self.lid..text)
end
for i=#self.missiles,1,-1 do
local missile=self.missiles[i]
if missile and not missile.active then
table.remove(self.missiles,i)
end
end
end
function FOX:_IsProtected(targetunit)
if not self.protectedset then
return false
end
if targetunit and targetunit:IsAlive()then
local targetgroup=targetunit:GetGroup()
if targetgroup then
local targetname=targetgroup:GetName()
for _,_group in pairs(self.protectedset:GetSet())do
local group=_group
if group then
local groupname=group:GetName()
if targetname==groupname then
return true
end
end
end
end
end
return false
end
function FOX._FuncTrack(weapon,self,missile)
local missileCoord=missile.missileCoord:UpdateFromVec3(weapon.vec3)
local missileVelocity=weapon:GetSpeed()
self:GetMissileTarget(missile)
local target=nil
if missile.targetUnit then
if missile.targetPlayer then
if missile.targetPlayer.destroy==true then
target=missile.targetUnit
end
else
if self:_IsProtected(missile.targetUnit)then
target=missile.targetUnit
end
end
else
local function _GetTarget(_unit)
local unit=_unit
local playerCoord=unit:GetCoordinate()
local dist=missileCoord:Get3DDistance(playerCoord)
if dist<=self.explosiondist then
return unit
end
end
local mindist=nil
for _,_player in pairs(self.players)do
local player=_player
if player.unitname~=missile.shooterName then
local playerCoord=player.unit:GetCoordinate()
local dist=missileCoord:Get3DDistance(playerCoord)
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
if(mindist==nil or dist<mindist)and(Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist)then
mindist=dist
target=player.unit
end
end
end
if self.protectedset then
mindist=nil
for _,_group in pairs(self.protectedset:GetSet())do
local group=_group
for _,_unit in pairs(group:GetUnits())do
local unit=_unit
if unit and unit:IsAlive()then
if unit:GetName()~=missile.shooterName then
local playerVec3=unit:GetVec3()
local dist=missileCoord:Get3DDistance(playerVec3)
local Dshooter2player=missile.shotCoord:Get3DDistance(playerVec3)
if(mindist==nil or dist<mindist)and(Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist)then
mindist=dist
target=unit
end
end
end
end
end
end
if target then
self:T(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m",missile.missileType,target:GetName(),tostring(mindist)))
end
end
if target then
local targetVec3=target:GetVec3()
local distance=missileCoord:Get3DDistance(targetVec3)
local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive()then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetVec3())
end
if self.Debug then
local bearing=missileCoord:HeadingTo(targetVec3)
local eta=distance/missileVelocity
self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec",missile.missileType,target:GetName(),distance,missileVelocity,bearing,eta))
end
local destroymissile=distance<=self.explosiondist
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
destroymissile=missile.explosive>=self.bigmissilemass
end
if destroymissile and self:_CheckCoordSafe(targetVec3)then
self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m",
missile.missileType,missile.missileName,missile.shooterName,target:GetName(),tostring(missile.targetPlayer~=nil),distance))
weapon:Destroy()
missile.active=false
if self.Debug then
missileCoord:SmokeRed()
end
self:MissileDestroyed(missile)
if self.explosionpower>0 and distance>50 and(distShooter==nil or(distShooter and distShooter>50))then
missileCoord:Explosion(self.explosionpower)
end
if missile.targetPlayer then
local text=string.format("Destroying missile. %s",self:_DeadText())
MESSAGE:New(text,10):ToGroup(target:GetGroup())
missile.targetPlayer.dead=missile.targetPlayer.dead+1
end
else
local dt=1.0
if distance>50000 then
dt=self.dt50
elseif distance>10000 then
dt=self.dt10
elseif distance>5000 then
dt=self.dt05
elseif distance>1000 then
dt=self.dt01
else
dt=self.dt00
end
weapon:SetTimeStepTrack(dt)
end
else
self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.",missile.missileType,missile.missileName,missile.shooterName))
weapon:SetTimeStepTrack(0.1)
end
end
function FOX._FuncImpact(weapon,self,missile)
if missile.targetPlayer then
local player=missile.targetPlayer
if player and player.unit:IsAlive()then
local text=string.format("Missile defeated. Well done, %s!",player.name)
MESSAGE:New(text,10):ToClient(player.client)
player.defeated=player.defeated+1
end
end
missile.active=false
self:T(FOX.lid..string.format("Terminating missile track timer."))
weapon.tracking=false
end
function FOX:onafterMissileLaunch(From,Event,To,missile)
local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s",missile.missileType,missile.missileName,tostring(missile.targetName),missile.shooterName)
self:T(FOX.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
for _,_player in pairs(self.players)do
local player=_player
local playerUnit=player.unit
if playerUnit and playerUnit:IsAlive()and player.coalition~=missile.shooterCoalition then
local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord)
local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord)
if player.launchalert then
if(missile.targetPlayer and player.unitname==missile.targetPlayer.unitname)or(distance<missile.missileRange)then
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.",UTILS.MetersToNM(distance),bearing)
self:ScheduleOnce(5,FOX._SayNotchingHeadings,self,player,missile.weapon)
MESSAGE:New(text,5,"ALERT"):ToClient(player.client)
end
end
if player.marklaunch then
local text=string.format("Missile launch coordinates:\n%s\n%s",missile.shotCoord:ToStringLLDMS(),missile.shotCoord:ToStringBULLS(player.coalition))
missile.shotCoord:MarkToGroup(text,player.group)
end
end
end
missile.Weapon:SetFuncTrack(FOX._FuncTrack,self,missile)
missile.Weapon:SetFuncImpact(FOX._FuncImpact,self,missile)
self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds."))
missile.Weapon:StartTrack(0.0001)
end
function FOX:OnEventPlayerEnterAircraft(EventData)
end
function FOX:OnEventBirth(EventData)
self:F3({eventbirth=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local playerunit,playername=self:_GetPlayerUnitAndName(_unitName)
self:T(self.lid.."BIRTH: unit   = "..tostring(EventData.IniUnitName))
self:T(self.lid.."BIRTH: group  = "..tostring(EventData.IniGroupName))
self:T(self.lid.."BIRTH: player = "..tostring(playername))
if playerunit and playername then
local _uid=playerunit:GetID()
local _group=playerunit:GetGroup()
local _callsign=playerunit:GetCallsign()
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.",playername,_callsign,_unitName,_group:GetName())
self:T(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
if not self.menudisabled then
self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName)
end
local playerData={}
playerData.unit=playerunit
playerData.unitname=_unitName
playerData.group=_group
playerData.groupname=_group:GetName()
playerData.name=playername
playerData.callsign=playerData.unit:GetCallsign()
playerData.client=CLIENT:FindByName(_unitName,nil,true)
playerData.coalition=_group:GetCoalition()
playerData.destroy=playerData.destroy or self.destroy
playerData.launchalert=playerData.launchalert or self.launchalert
playerData.marklaunch=playerData.marklaunch or self.marklaunch
playerData.defeated=playerData.defeated or 0
playerData.dead=playerData.dead or 0
self.players[playername]=playerData
end
end
function FOX:GetMissileTarget(missile)
local target=nil
local targetName="unknown"
local targetUnit=nil
if missile.weapon and missile.weapon:isExist()then
target=missile.weapon:getTarget()
if target then
self:T2({missiletarget=target})
targetUnit=UNIT:Find(target)
if targetUnit then
targetName=targetUnit:GetName()
missile.targetUnit=targetUnit
missile.targetPlayer=self:_GetPlayerFromUnit(missile.targetUnit)
end
end
end
if missile.targetName and missile.targetName~=targetName then
self:I(self.lid..string.format("Missile %s(%s) changed target to %s. Previous target was %s.",missile.missileType,missile.missileName,targetName,missile.targetName))
end
missile.targetName=targetName
end
function FOX:OnEventShot(EventData)
self:T2({eventshot=EventData})
if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.weapon==nil then
return
end
local weapon=WEAPON:New(EventData.weapon)
local _weapon=weapon:GetTypeName()
local _target=EventData.Weapon:getTarget()
local _targetName="unknown"
local _targetUnit=nil
local desc=weapon.desc
self:T2({desc=desc})
local missilecategory=desc.missileCategory
local missilerange=nil
if missilecategory then
missilerange=desc.rangeMaxAltMax
end
self:T2(FOX.lid.."EVENT SHOT: FOX")
self:T2(FOX.lid..string.format("EVENT SHOT: Ini unit     = %s",tostring(EventData.IniUnitName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Ini group    = %s",tostring(EventData.IniGroupName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type  = %s",tostring(weapon:GetTypeName())))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s",tostring(weapon:GetCategory())))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil categ = %s",tostring(missilecategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil range = %s",tostring(missilerange)))
if not self:_CheckCoordLaunch(EventData.IniUnit:GetCoordinate())then
self:T(self.lid.."Missile was not fired in launch zone. No tracking!")
return
end
local _track=weapon:IsMissile()and missilecategory and(missilecategory==1 or missilecategory==2 or missilecategory==6)
if _track then
local missile={}
missile.active=true
missile.weapon=EventData.weapon
missile.Weapon=weapon
missile.missileType=_weapon
missile.missileRange=missilerange
missile.missileName=EventData.weapon:getName()
missile.shooterUnit=EventData.IniUnit
missile.shooterGroup=EventData.IniGroup
missile.shooterCoalition=EventData.IniUnit:GetCoalition()
missile.shooterName=EventData.IniUnitName
missile.shotTime=timer.getAbsTime()
missile.shotCoord=EventData.IniUnit:GetCoordinate()
missile.fuseDist=desc.fuseDist
missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
missile.targetOrig=missile.targetName
missile.missileCoord=COORDINATE:New(0,0,0)
self:GetMissileTarget(missile)
self:I(FOX.lid..string.format("EVENT SHOT: Shooter=%s %s(%s) ==> Target=%s, fuse dist=%s, explosive=%s",
tostring(missile.shooterName),tostring(missile.missileType),tostring(missile.missileName),tostring(missile.targetName),tostring(missile.fuseDist),tostring(missile.explosive)))
if missile.targetPlayer or self:_IsProtected(missile.targetUnit)or missile.targetName=="unknown"then
table.insert(self.missiles,missile)
self:__MissileLaunch(0.1,missile)
end
end
end
function FOX:OnEventHit(EventData)
self:T({eventhit=EventData})
if EventData.Weapon==nil then
return
end
if EventData.IniUnit==nil then
return
end
if EventData.TgtUnit==nil then
return
end
local weapon=EventData.Weapon
local weaponname=weapon:getName()
for i,_missile in pairs(self.missiles)do
local missile=_missile
if missile.missileName==weaponname then
self:I(self.lid..string.format("WARNING: Missile %s (%s) hit target %s. Missile trainer target was %s.",missile.missileType,missile.missileName,EventData.TgtUnitName,missile.targetName))
self:I({missile=missile})
return
end
end
end
function FOX:_AddF10Commands(_unitName)
self:F(_unitName)
local _unit,playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and playername then
local group=_unit:GetGroup()
local gid=group:GetID()
if group and gid then
if not self.menuadded[gid]then
self.menuadded[gid]=true
local _rootPath=nil
if FOX.MenuF10Root then
_rootPath=FOX.MenuF10Root
else
if FOX.MenuF10[gid]==nil then
FOX.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"FOX")
end
_rootPath=FOX.MenuF10[gid]
end
missionCommands.addCommandForGroup(gid,"Destroy Missiles On/Off",_rootPath,self._ToggleDestroyMissiles,self,_unitName)
missionCommands.addCommandForGroup(gid,"Launch Alerts On/Off",_rootPath,self._ToggleLaunchAlert,self,_unitName)
missionCommands.addCommandForGroup(gid,"Mark Launch On/Off",_rootPath,self._ToggleLaunchMark,self,_unitName)
missionCommands.addCommandForGroup(gid,"My Status",_rootPath,self._MyStatus,self,_unitName)
end
else
self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName or"unknown"))
end
else
self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName or"unknown"))
end
end
function FOX:_MyStatus(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local m,mtext=self:_GetTargetMissiles(playerData.name)
local text=string.format("Status of player %s:\n",playerData.name)
local safe=self:_CheckCoordSafe(playerData.unit:GetCoordinate())
text=text..string.format("Destroy missiles? %s\n",tostring(playerData.destroy))
text=text..string.format("Launch alert? %s\n",tostring(playerData.launchalert))
text=text..string.format("Launch marks? %s\n",tostring(playerData.marklaunch))
text=text..string.format("Am I safe? %s\n",tostring(safe))
text=text..string.format("Missiles defeated: %d\n",playerData.defeated)
text=text..string.format("Missiles destroyed: %d\n",playerData.dead)
text=text..string.format("Me target: %d\n%s",m,mtext)
MESSAGE:New(text,10,nil,true):ToClient(playerData.client)
end
end
end
function FOX:_GetTargetMissiles(playername)
local text=""
local n=0
for _,_missile in pairs(self.missiles)do
local missile=_missile
if missile.targetPlayer and missile.targetPlayer.name==playername then
n=n+1
text=text..string.format("Type %s: active %s\n",missile.missileType,tostring(missile.active))
end
end
return n,text
end
function FOX:_ToggleLaunchAlert(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.launchalert=not playerData.launchalert
local text=""
if playerData.launchalert==true then
text=string.format("%s, missile launch alerts are now ENABLED.",playerData.name)
else
text=string.format("%s, missile launch alerts are now DISABLED.",playerData.name)
end
MESSAGE:New(text,5):ToClient(playerData.client)
end
end
end
function FOX:_ToggleLaunchMark(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.marklaunch=not playerData.marklaunch
local text=""
if playerData.marklaunch==true then
text=string.format("%s, missile launch marks are now ENABLED.",playerData.name)
else
text=string.format("%s, missile launch marks are now DISABLED.",playerData.name)
end
MESSAGE:New(text,5):ToClient(playerData.client)
end
end
end
function FOX:_ToggleDestroyMissiles(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.destroy=not playerData.destroy
local text=""
if playerData.destroy==true then
text=string.format("%s, incoming missiles will be DESTROYED.",playerData.name)
else
text=string.format("%s, incoming missiles will NOT be DESTROYED.",playerData.name)
end
MESSAGE:New(text,5):ToClient(playerData.client)
end
end
end
function FOX:_DeadText()
local texts={}
texts[1]="You're dead!"
texts[2]="Meet your maker!"
texts[3]="Time to meet your maker!"
texts[4]="Well, I guess that was it!"
texts[5]="Bye, bye!"
texts[6]="Cheers buddy, was nice knowing you!"
local r=math.random(#texts)
return texts[r]
end
function FOX:_CheckCoordSafe(coord)
if#self.safezones==0 then
return true
end
for _,_zone in pairs(self.safezones)do
local zone=_zone
local Vec2={x=coord.x,y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
if inzone then
return true
end
end
return false
end
function FOX:_CheckCoordLaunch(coord)
if#self.launchzones==0 then
return true
end
for _,_zone in pairs(self.launchzones)do
local zone=_zone
local Vec2={x=coord.x,y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
if inzone then
return true
end
end
return false
end
function FOX:_GetWeapongHeading(weapon)
if weapon and weapon:isExist()then
local wp=weapon:getPosition()
local wph=math.atan2(wp.x.z,wp.x.x)
if wph<0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
return wph
end
return-1
end
function FOX:_SayNotchingHeadings(playerData,weapon)
if playerData and playerData.unit and playerData.unit:IsAlive()then
local nr,nl=self:_GetNotchingHeadings(weapon)
if nr and nl then
local text=string.format("Notching heading %03d° or %03d°",nr,nl)
MESSAGE:New(text,5,"FOX"):ToClient(playerData.client)
end
end
end
function FOX:_GetNotchingHeadings(weapon)
if weapon then
local hdg=self:_GetWeapongHeading(weapon)
local hdg1=hdg+90
if hdg1>360 then
hdg1=hdg1-360
end
local hdg2=hdg-90
if hdg2<0 then
hdg2=hdg2+360
end
return hdg1,hdg2
end
return nil,nil
end
function FOX:_GetPlayerFromUnitname(unitName)
for _,_player in pairs(self.players)do
local player=_player
if player.unitname==unitName then
return player
end
end
return nil
end
function FOX:_GetPlayerFromUnit(unit)
if unit and unit:IsAlive()then
local unitname=unit:GetName()
for _,_player in pairs(self.players)do
local player=_player
if player.unitname==unitname then
return player
end
end
end
return nil
end
function FOX:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName~=nil then
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit,unit=unit,playername=playername})
if DCSunit and unit and playername then
self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername)))
return unit,playername
end
end
end
return nil,nil
end
MANTIS={
ClassName="MANTIS",
name="mymantis",
SAM_Templates_Prefix="",
SAM_Group=nil,
EWR_Templates_Prefix="",
EWR_Group=nil,
Adv_EWR_Group=nil,
HQ_Template_CC="",
HQ_CC=nil,
SAM_Table={},
SAM_Table_Long={},
SAM_Table_Medium={},
SAM_Table_Short={},
SAM_Table_PointDef={},
lid="",
Detection=nil,
AWACS_Detection=nil,
debug=false,
checkradius=25000,
grouping=5000,
acceptrange=80000,
detectinterval=30,
engagerange=95,
autorelocate=false,
advanced=false,
adv_ratio=100,
adv_state=0,
AWACS_Prefix="",
advAwacs=false,
verbose=false,
awacsrange=250000,
Shorad=nil,
ShoradLink=false,
ShoradTime=600,
ShoradActDistance=25000,
UseEmOnOff=false,
TimeStamp=0,
state2flag=false,
SamStateTracker={},
DLink=false,
DLTimeStamp=0,
Padding=10,
SuppressedGroups={},
automode=true,
autoshorad=true,
ShoradGroupSet=nil,
checkforfriendlies=false,
SmokeDecoy=false,
SmokeDecoyColor=SMOKECOLOR.White,
checkcounter=1,
}
MANTIS.AdvancedState={
GREEN=0,
AMBER=1,
RED=2,
}
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 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"},
["SA-5"]={Range=250,Blindspot=7,Height=40,Type="Long",Radar="5N62V"},
["SA-6"]={Range=25,Blindspot=0,Height=8,Type="Medium",Radar="1S91"},
["SA-10"]={Range=119,Blindspot=0,Height=18,Type="Long",Radar="S-300PS 4"},
["SA-11"]={Range=35,Blindspot=0,Height=20,Type="Medium",Radar="SA-11"},
["Roland"]={Range=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="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="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="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"},
["SA-10B"]={Range=75,Blindspot=0,Height=18,Type="Medium",Radar="SA-10B"},
["SA-17"]={Range=50,Blindspot=3,Height=30,Type="Medium",Radar="SA-17"},
["SA-20A"]={Range=150,Blindspot=5,Height=27,Type="Long",Radar="S-300PMU1"},
["SA-20B"]={Range=200,Blindspot=4,Height=27,Type="Long",Radar="S-300PMU2"},
["HQ-2"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"},
["TAMIR IDFA"]={Range=20,Blindspot=0.6,Height=12.3,Type="Short",Radar="IRON_DOME_LN"},
["STUNNER IDFA"]={Range=250,Blindspot=1,Height=45,Type="Long",Radar="DAVID_SLING_LN"},
}
MANTIS.SamDataHDS={
["SA-2 HDS"]={Range=56,Blindspot=7,Height=30,Type="Medium",Radar="V759"},
["SA-3 HDS"]={Range=20,Blindspot=6,Height=30,Type="Short",Radar="V-601P"},
["SA-10C HDS 2"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85DE ln"},
["SA-10C HDS 1"]={Range=90,Blindspot=5,Height=25,Type="Long",Radar="5P85CE ln"},
["SA-12 HDS 2"]={Range=100,Blindspot=10,Height=25,Type="Long",Radar="S-300V 9A82 l"},
["SA-12 HDS 1"]={Range=75,Blindspot=1,Height=25,Type="Long",Radar="S-300V 9A83 l"},
["SA-23 HDS 2"]={Range=200,Blindspot=5,Height=37,Type="Long",Radar="S-300VM 9A82ME"},
["SA-23 HDS 1"]={Range=100,Blindspot=1,Height=50,Type="Long",Radar="S-300VM 9A83ME"},
["HQ-2 HDS"]={Range=50,Blindspot=6,Height=35,Type="Medium",Radar="HQ_2_Guideline_LN"},
}
MANTIS.SamDataSMA={
["RBS98M SMA"]={Range=20,Blindspot=0.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 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"},
["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"},
["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)
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.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
self.acceptrange=80000
self.detectinterval=30
self.engagerange=95
self.autorelocate=false
self.autorelocateunits={HQ=false,EWR=false}
self.advanced=false
self.adv_ratio=100
self.adv_state=0
self.verbose=false
self.Adv_EWR_Group=nil
self.AWACS_Prefix=awacs or nil
self.awacsrange=250000
self.Shorad=nil
self.ShoradLink=false
self.ShoradTime=600
self.ShoradActDistance=25000
self.TimeStamp=timer.getAbsTime()
self.relointerval=math.random(1800,3600)
self.state2flag=false
self.SamStateTracker={}
self.DLink=false
self.Padding=Padding or 10
self.SuppressedGroups={}
self.automode=true
self.usezones=false
self.AcceptZones={}
self.RejectZones={}
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()
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
end
if type(awacs)=="string"then
self.advAwacs=true
else
self.advAwacs=false
end
self.lid=string.format("MANTIS %s | ",self.name)
if self.debug then
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self.ewr_templates={}
if type(samprefix)~="table"then
self.SAM_Templates_Prefix={samprefix}
end
if type(ewrprefix)~="table"then
self.EWR_Templates_Prefix={ewrprefix}
end
for _,_group in pairs(self.SAM_Templates_Prefix)do
table.insert(self.ewr_templates,_group)
end
for _,_group in pairs(self.EWR_Templates_Prefix)do
table.insert(self.ewr_templates,_group)
end
if self.advAwacs then
table.insert(self.ewr_templates,awacs)
end
self:T({self.ewr_templates})
self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition)
self.EWR_Group=SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition)
if self.FilterZones then
self.SAM_Group:FilterZones(self.FilterZones)
end
if self.dynamic then
self.SAM_Group:FilterStart()
self.EWR_Group:FilterStart()
else
self.SAM_Group:FilterOnce()
self.EWR_Group:FilterOnce()
end
if self.HQ_Template_CC then
self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC)
end
self.checkcounter=1
self.version="0.9.28"
self:I(string.format("***** Starting MANTIS Version %s *****",self.version))
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Relocating","*")
self:AddTransition("*","GreenState","*")
self:AddTransition("*","RedState","*")
self:AddTransition("*","AdvStateChange","*")
self:AddTransition("*","ShoradActivated","*")
self:AddTransition("*","SeadSuppressionStart","*")
self:AddTransition("*","SeadSuppressionEnd","*")
self:AddTransition("*","SeadSuppressionPlanned","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function MANTIS:_GetSAMTable()
self:T(self.lid.."GetSAMTable")
return self.SAM_Table
end
function MANTIS:_SetSAMTable(table)
self:T(self.lid.."SetSAMTable")
self.SAM_Table=table
return self
end
function MANTIS:SetEWRGrouping(radius)
self:T(self.lid.."SetEWRGrouping")
local radius=radius or 5000
self.grouping=radius
return self
end
function MANTIS:AddScootZones(ZoneSet,Number,Random,Formation)
self:T(self.lid.." AddScootZones")
self.SkateZones=ZoneSet
self.SkateNumber=Number or 3
self.shootandscoot=true
self.ScootRandom=Random
self.ScootFormation=Formation or"Cone"
return self
end
function MANTIS:AddZones(AcceptZones,RejectZones,ConflictZones)
self:T(self.lid.."AddZones")
self.AcceptZones=AcceptZones or{}
self.RejectZones=RejectZones or{}
self.ConflictZones=ConflictZones or{}
if#self.AcceptZones>0 or#self.RejectZones>0 or#self.ConflictZones>0 then
self.usezones=true
end
return self
end
function MANTIS:SetEWRRange(radius)
self:T(self.lid.."SetEWRRange")
return self
end
function MANTIS:SetSAMRadius(radius)
self:T(self.lid.."SetSAMRadius")
local radius=radius or 25000
self.checkradius=radius
return self
end
function MANTIS:SetSAMRange(range)
self:T(self.lid.."SetSAMRange")
local range=range or 95
if range<0 or range>100 then
range=95
end
self.engagerange=range
return self
end
function MANTIS: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)
self:T(self.lid.."SetNewSAMRangeWhileRunning")
local range=range or 95
if range<0 or range>100 then
range=95
end
self.engagerange=range
self:_RefreshSAMTable()
self.mysead.EngagementRange=range
return self
end
function MANTIS:Debug(onoff)
self:T(self.lid.."SetDebug")
local onoff=onoff or false
self.debug=onoff
if onoff then
BASE:TraceOn()
BASE:TraceClass("MANTIS")
BASE:TraceLevel(1)
else
BASE:TraceOff()
end
return self
end
function MANTIS:GetCommandCenter()
self:T(self.lid.."GetCommandCenter")
if self.HQ_CC then
return self.HQ_CC
else
return nil
end
end
function MANTIS:SetAwacs(prefix)
self:T(self.lid.."SetAwacs")
if prefix~=nil then
if type(prefix)=="string"then
self.AWACS_Prefix=prefix
self.advAwacs=true
end
end
return self
end
function MANTIS:SetAwacsRange(range)
self:T(self.lid.."SetAwacsRange")
local range=range or 250000
self.awacsrange=range
return self
end
function MANTIS:SetCommandCenter(group)
self:T(self.lid.."SetCommandCenter")
local group=group or nil
if group~=nil then
if type(group)=="string"then
self.HQ_CC=GROUP:FindByName(group)
self.HQ_Template_CC=group
else
self.HQ_CC=group
self.HQ_Template_CC=group:GetName()
end
end
return self
end
function MANTIS:SetDetectInterval(interval)
self:T(self.lid.."SetDetectInterval")
local interval=interval or 30
self.detectinterval=interval
return self
end
function MANTIS:SetAdvancedMode(onoff,ratio)
self:T(self.lid.."SetAdvancedMode")
local onoff=onoff or false
local ratio=ratio or 100
if(type(self.HQ_Template_CC)=="string")and onoff and self.dynamic then
self.adv_ratio=ratio
self.advanced=true
self.adv_state=0
self.Adv_EWR_Group=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart()
self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****",self.version))
else
local text=self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both."
local m=MESSAGE:New(text,10,"MANTIS",true):ToAll()
self:E(text)
end
return self
end
function MANTIS:SetUsingEmOnOff(switch)
self:T(self.lid.."SetUsingEmOnOff")
self.UseEmOnOff=switch or false
return self
end
function MANTIS:SetUsingDLink(DLink)
self:T(self.lid.."SetUsingDLink")
self.DLink=true
self.Detection=DLink
self.DLTimeStamp=timer.getAbsTime()
return self
end
function MANTIS:_CheckHQState()
self:T(self.lid.."CheckHQState")
local text=self.lid.." Checking HQ State"
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
if self.advanced then
local hq=self.HQ_Template_CC
local hqgrp=GROUP:FindByName(hq)
if hqgrp then
if hqgrp:IsAlive()then
return true
else
return false
end
end
end
return self
end
function MANTIS:_CheckEWRState()
self:T(self.lid.."CheckEWRState")
local text=self.lid.." Checking EWR State"
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
if self.advanced then
local EWR_Group=self.Adv_EWR_Group
local nalive=EWR_Group:CountAlive()
if self.advAwacs then
local awacs=GROUP:FindByName(self.AWACS_Prefix)
if awacs~=nil then
if awacs:IsAlive()then
nalive=nalive+1
end
end
end
if nalive>0 then
return true
else
return false
end
end
return self
end
function MANTIS:_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)
if self.verbose then self:I(self.lid.." Calculating Advanced State")end
local currstate=self.adv_state
local EWR_State=self:_CheckEWRState()
local HQ_State=self:_CheckHQState()
if EWR_State and HQ_State then
self.adv_state=0
elseif EWR_State or HQ_State then
self.adv_state=1
else
self.adv_state=2
end
local interval=self.detectinterval
local ratio=self.adv_ratio/100
ratio=ratio*self.adv_state
local newinterval=interval+(interval*ratio)
if self.debug or self.verbose then
local text=self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d",currstate,self.adv_state,newinterval)
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
end
return newinterval,currstate
end
function MANTIS:SetAutoRelocate(hq,ewr)
self:T(self.lid.."SetAutoRelocate")
local hqrel=hq or false
local ewrel=ewr or false
if hqrel or ewrel then
self.autorelocate=true
self.autorelocateunits={HQ=hqrel,EWR=ewrel}
end
return self
end
function MANTIS:_RelocateGroups()
self:T(self.lid.."RelocateGroups")
local text=self.lid.." Relocating Groups"
local m=MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug)
if self.verbose then self:I(text)end
if self.autorelocate then
local HQGroup=self.HQ_CC
if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive()then
local _hqgrp=self.HQ_CC
local text=self.lid.." Relocating HQ"
_hqgrp:RelocateGroundRandomInRadius(20,500,true,true,nil,true)
end
if self.autorelocateunits.EWR then
local EWR_GRP=SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
local EWR_Grps=EWR_GRP.Set
for _,_grp in pairs(EWR_Grps)do
if _grp:IsAlive()and _grp:IsGround()then
local text=self.lid.." Relocating EWR ".._grp:GetName()
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
if self.verbose then self:I(text)end
_grp:RelocateGroundRandomInRadius(20,500,true,true,nil,true)
end
end
end
end
return self
end
function MANTIS:_CheckCoordinateInZones(coord)
self:T(self.lid.."_CheckCoordinateInZones")
local inzone=false
if#self.AcceptZones>0 then
for _,_zone in pairs(self.AcceptZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
inzone=true
self:T(self.lid.."Target coord in Accept Zone!")
break
end
end
end
if#self.RejectZones>0 and inzone then
for _,_zone in pairs(self.RejectZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
inzone=false
self:T(self.lid.."Target coord in Reject Zone!")
break
end
end
end
if#self.ConflictZones>0 and not inzone then
for _,_zone in pairs(self.ConflictZones)do
local zone=_zone
if zone:IsCoordinateInZone(coord)then
inzone=true
self:T(self.lid.."Target coord in Conflict Zone!")
break
end
end
end
return inzone
end
function MANTIS:_PreFilterHeight(height)
self:T(self.lid.."_PreFilterHeight")
local set={}
local dlink=self.Detection
local detectedgroups=dlink:GetContactTable()
for _,_contact in pairs(detectedgroups)do
local contact=_contact
local grp=contact.group
if grp:IsAlive()then
if grp:GetHeight(true)<height then
local coord=grp:GetCoordinate()
table.insert(set,coord)
end
end
end
return set
end
function MANTIS:_CheckObjectInZone(dectset,samcoordinate,radius,height,dlink)
self:T(self.lid.."_CheckObjectInZone")
local rad=radius or self.checkradius
local set=dectset
if dlink then
set=self:_PreFilterHeight(height)
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()
end
for _,_coord in pairs(set)do
local coord=_coord
local targetdistance=samcoordinate:DistanceFromPointVec2(coord)
if not targetdistance then
targetdistance=samcoordinate:Get2DDistance(coord)
end
local zonecheck=true
if self.usezones then
zonecheck=self:_CheckCoordinateInZones(coord)
end
if self.verbose and self.debug then
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 | 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=self.friendlyset:GetClosestGroup(samcoordinate)
if closestfriend and distance and distance<rad then
nofriendlies=false
end
end
if targetdistance<=rad and zonecheck==true and nofriendlies==true then
return true,targetdistance
end
end
return false,0
end
function MANTIS:StartDetection()
self:T(self.lid.."Starting Detection")
local groupset=self.EWR_Group
local grouping=self.grouping or 5000
local interval=self.detectinterval or 20
local MANTISdetection=DETECTION_AREAS:New(groupset,grouping)
MANTISdetection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
MANTISdetection:SetRefreshTimeInterval(interval)
MANTISdetection:__Start(2)
return MANTISdetection
end
function MANTIS:StartIntelDetection()
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")
IntelOne:Start()
local IntelTwo=INTEL:New(samset,self.Coalition,self.name.." IntelTwo")
IntelTwo:Start()
local IntelDlink=INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,300)
IntelDlink:__Start(1)
self:SetUsingDLink(IntelDlink)
table.insert(self.intelset,IntelOne)
table.insert(self.intelset,IntelTwo)
return IntelDlink
end
function MANTIS:StartAwacsDetection()
self:T(self.lid.."Starting Awacs Detection")
local group=self.AWACS_Prefix
local groupset=SET_GROUP:New():FilterPrefixes(group):FilterCoalitions(self.Coalition):FilterStart()
local grouping=self.grouping or 5000
local interval=self.detectinterval or 60
local MANTISAwacs=DETECTION_AREAS:New(groupset,grouping)
MANTISAwacs:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
MANTISAwacs:SetAcceptRange(self.awacsrange)
MANTISAwacs:SetRefreshTimeInterval(interval)
MANTISAwacs:Start()
return MANTISAwacs
end
function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm)
self:T(self.lid.."_GetSAMDataFromUnits")
local found=false
local range=self.checkradius
local height=3000
local type=MANTIS.SamType.MEDIUM
local radiusscale=self.radiusscale[type]
local blind=0
local group=GROUP:FindByName(grpname)
local units=group:GetUnits()
local SAMData=self.SamData
if mod then
SAMData=self.SamDataHDS
elseif sma then
SAMData=self.SamDataSMA
elseif chm then
SAMData=self.SamDataCH
end
for _,_unit in pairs(units)do
local unit=_unit
local type=string.lower(unit:GetTypeName())
for idx,entry in pairs(SAMData)do
local _entry=entry
local _radar=string.lower(_entry.Radar)
if string.find(type,_radar,1,true)then
type=_entry.Type
radiusscale=self.radiusscale[type]
range=_entry.Range*1000*radiusscale
height=_entry.Height*1000
blind=_entry.Blindspot*100
found=true
break
end
end
if found then break end
end
if not found then
local grp=GROUP:FindByName(grpname)
if(grp and grp:IsAlive()and grp:IsAAA())or string.find(grpname,"AAA",1,true)then
range=2000
height=2000
blind=50
type=MANTIS.SamType.POINT
found=true
end
end
if not found then
self:E(self.lid..string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end
return range,height,type,blind
end
function MANTIS:_GetSAMRange(grpname)
self:T(self.lid.."_GetSAMRange for "..tostring(grpname))
local range=self.checkradius
local height=3000
local type=MANTIS.SamType.MEDIUM
local radiusscale=self.radiusscale[type]
local blind=0
local found=false
local HDSmod=false
local SMAMod=false
local CHMod=false
if string.find(grpname,"HDS",1,true)then
HDSmod=true
elseif string.find(grpname,"SMA",1,true)then
SMAMod=true
elseif string.find(grpname,"CHM",1,true)then
CHMod=true
end
for idx,entry in pairs(self.SamData)do
self:T2("ID = "..idx)
if string.find(grpname,idx,1,true)then
local _entry=entry
type=_entry.Type
radiusscale=self.radiusscale[type]
range=_entry.Range*1000*radiusscale
height=_entry.Height*1000
blind=_entry.Blindspot
self:T("Matching Groupname = "..grpname.." Range= "..range)
found=true
break
end
end
if not found then
local grp=GROUP:FindByName(grpname)
if(grp and grp:IsAlive()and grp:IsAAA())or string.find(grpname,"AAA",1,true)then
range=2000
height=2000
blind=50
type=MANTIS.SamType.POINT
found=true
end
end
if(not found)or HDSmod or SMAMod or CHMod then
range,height,type=self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
elseif not found then
self:E(self.lid..string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end
if found and string.find(grpname,"SHORAD",1,true)then
type=MANTIS.SamType.POINT
end
return range,height,type,blind
end
function MANTIS:SetSAMStartState()
self:T(self.lid.."Setting SAM Start States")
local SAM_SET=self.SAM_Group
local SAM_Grps=SAM_SET.Set
local SAM_Tbl={}
local SAM_Tbl_lg={}
local SAM_Tbl_md={}
local SAM_Tbl_sh={}
local SAM_Tbl_pt={}
local SEAD_Grps={}
local engagerange=self.engagerange
for _i,_group in pairs(SAM_Grps)do
if _group:IsGround()and _group:IsAlive()then
local group=_group
if self.UseEmOnOff then
group:OptionAlarmStateRed()
group:EnableEmission(false)
else
group:OptionAlarmStateGreen()
end
group:OptionEngageRange(engagerange)
local grpname=group:GetName()
local grpcoord=group:GetCoordinate()
local grprange,grpheight,type,blind=self:_GetSAMRange(grpname)
table.insert(SAM_Tbl,{grpname,grpcoord,grprange,grpheight,blind,type})
if type==MANTIS.SamType.LONG then
table.insert(SAM_Tbl_lg,{grpname,grpcoord,grprange,grpheight,blind,type})
table.insert(SEAD_Grps,grpname)
self:T("SAM "..grpname.." is type LONG")
elseif type==MANTIS.SamType.MEDIUM then
table.insert(SAM_Tbl_md,{grpname,grpcoord,grprange,grpheight,blind,type})
table.insert(SEAD_Grps,grpname)
self:T("SAM "..grpname.." is type MEDIUM")
elseif type==MANTIS.SamType.SHORT then
table.insert(SAM_Tbl_sh,{grpname,grpcoord,grprange,grpheight,blind,type})
table.insert(SEAD_Grps,grpname)
self:T("SAM "..grpname.." is type SHORT")
elseif type==MANTIS.SamType.POINT then
table.insert(SAM_Tbl_pt,{grpname,grpcoord,grprange,grpheight,blind,type})
self:T("SAM "..grpname.." is type POINT")
self.ShoradGroupSet:Add(grpname,group)
if not self.autoshorad then
table.insert(SEAD_Grps,grpname)
end
end
self.SamStateTracker[grpname]="GREEN"
end
end
self.SAM_Table=SAM_Tbl
self.SAM_Table_Long=SAM_Tbl_lg
self.SAM_Table_Medium=SAM_Tbl_md
self.SAM_Table_Short=SAM_Tbl_sh
self.SAM_Table_PointDef=SAM_Tbl_pt
local mysead=SEAD:New(SEAD_Grps,self.Padding)
mysead:SetEngagementRange(engagerange)
mysead:AddCallBack(self)
if self.UseEmOnOff then
mysead:SwitchEmissions(true)
end
self.mysead=mysead
return self
end
function MANTIS:_RefreshSAMTable()
self:T(self.lid.."RefreshSAMTable")
local SAM_SET=self.SAM_Group
local SAM_Grps=SAM_SET.Set
local SAM_Tbl={}
local SAM_Tbl_lg={}
local SAM_Tbl_md={}
local SAM_Tbl_sh={}
local SAM_Tbl_pt={}
local SEAD_Grps={}
local engagerange=self.engagerange
for _i,_group in pairs(SAM_Grps)do
local group=_group
group:OptionEngageRange(engagerange)
if group:IsGround()and group:IsAlive()then
local grpname=group:GetName()
local grpcoord=group:GetCoordinate()
local grprange,grpheight,type,blind=self:_GetSAMRange(grpname)
local radaralive=group:IsSAM()
table.insert(SAM_Tbl,{grpname,grpcoord,grprange,grpheight,blind,type})
table.insert(SEAD_Grps,grpname)
if type==MANTIS.SamType.LONG and radaralive then
table.insert(SAM_Tbl_lg,{grpname,grpcoord,grprange,grpheight,blind,type})
self:T({grpname,grprange,grpheight})
elseif type==MANTIS.SamType.MEDIUM and radaralive then
table.insert(SAM_Tbl_md,{grpname,grpcoord,grprange,grpheight,blind,type})
self:T({grpname,grprange,grpheight})
elseif type==MANTIS.SamType.SHORT and radaralive then
table.insert(SAM_Tbl_sh,{grpname,grpcoord,grprange,grpheight,blind,type})
self:T({grpname,grprange,grpheight})
elseif type==MANTIS.SamType.POINT or(not radaralive)then
table.insert(SAM_Tbl_pt,{grpname,grpcoord,grprange,grpheight,blind,type})
self:T({grpname,grprange,grpheight})
self.ShoradGroupSet:Add(grpname,group)
if self.autoshorad then
self.Shorad.Groupset=self.ShoradGroupSet
end
end
end
end
self.SAM_Table=SAM_Tbl
self.SAM_Table_Long=SAM_Tbl_lg
self.SAM_Table_Medium=SAM_Tbl_md
self.SAM_Table_Short=SAM_Tbl_sh
self.SAM_Table_PointDef=SAM_Tbl_pt
if self.mysead~=nil then
local mysead=self.mysead
mysead:UpdateSet(SEAD_Grps)
end
return self
end
function MANTIS:AddShorad(Shorad,Shoradtime)
self:T(self.lid.."AddShorad")
local Shorad=Shorad or nil
local ShoradTime=Shoradtime or 600
local ShoradLink=true
if Shorad:IsInstanceOf("SHORAD")then
self.ShoradLink=ShoradLink
self.Shorad=Shorad
self.ShoradTime=Shoradtime
end
return self
end
function MANTIS:RemoveShorad()
self:T(self.lid.."RemoveShorad")
self.ShoradLink=false
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.SHORT)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=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 switchedon<limit then
samgroup:EnableEmission(true)
switchedon=switchedon+1
switch=true
elseif(not self.UseEmOnOff)and switchedon<limit then
samgroup:OptionAlarmStateRed()
switchedon=switchedon+1
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
self:T("Smoking")
local units=samgroup: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
if self.ShoradLink and(Distance<self.ShoradActDistance or Distance<blind)then
local Shorad=self.Shorad
local radius=self.checkradius
local ontime=self.ShoradTime
Shorad:WakeUpShorad(name,radius,ontime)
self:__ShoradActivated(1,name,radius,ontime)
end
if(self.debug or self.verbose)and switch then
local text=string.format("SAM %s in alarm state RED!",name)
if self.verbose then self:I(self.lid..text)end
end
end
else
if samgroup:IsAlive()and not suppressed and not activeshorad then
if self.UseEmOnOff then
samgroup:EnableEmission(false)
else
samgroup:OptionAlarmStateGreen()
end
if self.SamStateTracker[name]~="GREEN"then
self:__GreenState(1,samgroup)
self.SamStateTracker[name]="GREEN"
end
if self.debug or self.verbose then
local text=string.format("SAM %s in alarm state GREEN!",name)
if self.verbose then self:I(self.lid..text)end
end
end
end
end
if self.debug or self.verbose then
for _,_status in pairs(self.SamStateTracker)do
if _status=="GREEN"then
instatusgreen=instatusgreen+1
elseif _status=="RED"then
instatusred=instatusred+1
end
end
if self.Shorad then
for _,_name in pairs(self.Shorad.ActiveGroups or{})do
activeshorads=activeshorads+1
end
end
end
return instatusred,instatusgreen,activeshorads
end
function MANTIS:_Check(detection,dlink)
self:T(self.lid.."Check")
local detset=detection:GetDetectedItemCoordinates()
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
local instatusredl,instatusgreenl,activeshoradsl=self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
local samset=self.SAM_Table_Medium
local instatusredm,instatusgreenm,activeshoradsm=self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
local samset=self.SAM_Table_Short
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()
instatusred,instatusgreen,activeshorads=self:_CheckLoop(samset,detset,dlink,self.maxclassic)
end
if self.debug or self.verbose then
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("+-----------------------------+")
MESSAGE:New(statusreport:Text(),10):ToAll():ToLog()
end
return self
end
function MANTIS:_Relocate()
self:T(self.lid.."Relocate")
self:_RelocateGroups()
return self
end
function MANTIS:_CheckAdvState()
self:T(self.lid.."CheckAdvSate")
local interval,oldstate=self:_CalcAdvState()
local newstate=self.adv_state
if newstate~=oldstate then
self:__AdvStateChange(1,oldstate,newstate,interval)
if newstate==2 then
self.state2flag=true
local samset=self:_GetSAMTable()
for _,_data in pairs(samset)do
local name=_data[1]
local samgroup=GROUP:FindByName(name)
if samgroup:IsAlive()then
if self.UseEmOnOff then
samgroup:EnableEmission(true)
else
samgroup:OptionAlarmStateRed()
end
end
end
elseif newstate<=1 then
self.detectinterval=interval
self.state2flag=false
end
end
return self
end
function MANTIS:_CheckDLinkState()
self:T(self.lid.."_CheckDLinkState")
local dlink=self.Detection
local TS=timer.getAbsTime()
if not dlink:Is("Running")and(TS-self.DLTimeStamp>29)then
self.DLink=false
self.Detection=self:StartDetection()
self:I(self.lid.."Intel DLink not running - switching back to single detection!")
end
end
function MANTIS:onafterStart(From,Event,To)
self:T({From,Event,To})
self:T(self.lid.."Starting MANTIS")
self:SetSAMStartState()
if not INTEL then
self.Detection=self:StartDetection()
else
self.Detection=self:StartIntelDetection()
end
if self.autoshorad then
self.Shorad=SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
self.Shorad:SetDefenseLimits(80,95)
self.ShoradLink=true
self.Shorad.Groupset=self.ShoradGroupSet
self.Shorad.debug=self.debug
end
if self.shootandscoot and self.SkateZones and self.Shorad then
self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation)
end
self:__Status(-math.random(1,10))
return self
end
function MANTIS:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
if not self.state2flag then
self:_Check(self.Detection,self.DLink)
end
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
local thistime=timer.getAbsTime()
local timepassed=thistime-self.TimeStamp
local halfintv=math.floor(timepassed/relointerval)
if halfintv>=1 then
self.TimeStamp=timer.getAbsTime()
self:_Relocate()
self:__Relocating(1)
end
end
if self.advanced then
self:_CheckAdvState()
end
if self.DLink then
self:_CheckDLinkState()
end
return self
end
function MANTIS:onafterStatus(From,Event,To)
self:T({From,Event,To})
if self.debug and self.verbose then
self:I(self.lid.."Status Report")
for _name,_state in pairs(self.SamStateTracker)do
self:I(string.format("Site %s\tStatus %s",_name,_state))
end
end
local interval=self.detectinterval*-1
self:__Status(interval)
return self
end
function MANTIS:onafterStop(From,Event,To)
self:T({From,Event,To})
return self
end
function MANTIS:onafterRelocating(From,Event,To)
self:T({From,Event,To})
return self
end
function MANTIS:onafterGreenState(From,Event,To,Group)
self:T({From,Event,To,Group:GetName()})
return self
end
function MANTIS:onafterRedState(From,Event,To,Group)
self:T({From,Event,To,Group:GetName()})
return self
end
function MANTIS:onafterAdvStateChange(From,Event,To,Oldstate,Newstate,Interval)
self:T({From,Event,To,Oldstate,Newstate,Interval})
return self
end
function MANTIS:onafterShoradActivated(From,Event,To,Name,Radius,Ontime)
self:T({From,Event,To,Name,Radius,Ontime})
return self
end
function MANTIS:onafterSeadSuppressionStart(From,Event,To,Group,Name,Attacker)
self:T({From,Event,To,Name})
self.SuppressedGroups[Name]=true
if self.ShoradLink then
local Shorad=self.Shorad
local radius=self.checkradius
local ontime=self.ShoradTime
Shorad:WakeUpShorad(Name,radius,ontime,nil,true)
self:__ShoradActivated(1,Name,radius,ontime)
end
return self
end
function MANTIS:onafterSeadSuppressionEnd(From,Event,To,Group,Name)
self:T({From,Event,To,Name})
self.SuppressedGroups[Name]=false
return self
end
function MANTIS:onafterSeadSuppressionPlanned(From,Event,To,Group,Name,SuppressionStartTime,SuppressionEndTime,Attacker)
self:T({From,Event,To,Name})
return self
end
end
SHORAD={
ClassName="SHORAD",
name="MyShorad",
debug=false,
Prefixes="",
Radius=20000,
Groupset=nil,
Samset=nil,
Coalition=nil,
ActiveTimer=600,
ActiveGroups={},
lid="",
DefendHarms=true,
DefendMavs=true,
DefenseLowProb=70,
DefenseHighProb=90,
UseEmOnOff=true,
shootandscoot=false,
SkateNumber=3,
SkateZones=nil,
minscootdist=100,
maxscootdist=3000,
scootrandomcoord=false,
}
do
SHORAD.Harms={
["AGM_88"]="AGM_88",
["AGM_122"]="AGM_122",
["AGM_84"]="AGM_84",
["AGM_45"]="AGM_45",
["ALARM"]="ALARM",
["LD-10"]="LD-10",
["X_58"]="X_58",
["X_28"]="X_28",
["X_25"]="X_25",
["X_31"]="X_31",
["Kh25"]="Kh25",
["HY-2"]="HY-2",
["ADM_141A"]="ADM_141A",
}
SHORAD.Mavs={
["AGM"]="AGM",
["C-701"]="C-701",
["Kh25"]="Kh25",
["Kh29"]="Kh29",
["Kh31"]="Kh31",
["Kh66"]="Kh66",
}
function SHORAD:New(Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition,UseEmOnOff)
local self=BASE:Inherit(self,FSM:New())
self:T({Name,ShoradPrefix,Samset,Radius,ActiveTimer,Coalition})
local GroupSet=SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
self.name=Name or"MyShorad"
self.Prefixes=ShoradPrefix or"SAM SHORAD"
self.Radius=Radius or 20000
self.Coalition=Coalition or"blue"
self.Samset=Samset or GroupSet
self.ActiveTimer=ActiveTimer or 600
self.ActiveGroups={}
self.Groupset=GroupSet
self.DefendHarms=true
self.DefendMavs=true
self.DefenseLowProb=70
self.DefenseHighProb=90
self.UseEmOnOff=true
if UseEmOnOff==false then self.UseEmOnOff=UseEmOnOff end
self:I("*** SHORAD - Started Version 0.3.4")
self.lid=string.format("SHORAD %s | ",self.name)
self:_InitState()
self:HandleEvent(EVENTS.Shot,self.HandleEventShot)
self:SetStartState("Running")
self:AddTransition("*","WakeUpShorad","*")
self:AddTransition("*","CalculateHitZone","*")
self:AddTransition("*","ShootAndScoot","*")
return self
end
function SHORAD:_InitState()
self:T(self.lid.." _InitState")
local table={}
local set=self.Groupset
self:T({set=set})
local aliveset=set:GetAliveSet()
for _,_group in pairs(aliveset)do
if self.UseEmOnOff then
_group:EnableEmission(false)
_group:OptionAlarmStateRed()
else
_group:OptionAlarmStateGreen()
end
_group:OptionDisperseOnAttack(30)
end
for i=1,100 do
math.random()
end
return self
end
function SHORAD:AddScootZones(ZoneSet,Number,Random,Formation)
self:T(self.lid.." AddScootZones")
self.SkateZones=ZoneSet
self.SkateNumber=Number or 3
self.shootandscoot=true
self.scootrandomcoord=Random
self.scootformation=Formation or"Cone"
return self
end
function SHORAD:SwitchDebug(onoff)
self:T({onoff})
if onoff then
self:SwitchDebugOn()
else
self:SwitchDebugOff()
end
return self
end
function SHORAD:SwitchDebugOn()
self.debug=true
BASE:TraceOn()
BASE:TraceClass("SHORAD")
return self
end
function SHORAD:SwitchDebugOff()
self.debug=false
BASE:TraceOff()
return self
end
function SHORAD:SwitchHARMDefense(onoff)
self:T({onoff})
local onoff=onoff or true
self.DefendHarms=onoff
return self
end
function SHORAD:SwitchAGMDefense(onoff)
self:T({onoff})
local onoff=onoff or true
self.DefendMavs=onoff
return self
end
function SHORAD:SetDefenseLimits(low,high)
self:T({low,high})
local low=low or 70
local high=high or 90
if(low<0)or(low>100)or(low>high)then
low=70
end
if(high<0)or(high>100)or(high<low)then
high=90
end
self.DefenseLowProb=low
self.DefenseHighProb=high
return self
end
function SHORAD:SetActiveTimer(seconds)
self:T(self.lid.." SetActiveTimer")
local timer=seconds or 600
if timer<0 then
timer=600
end
self.ActiveTimer=timer
return self
end
function SHORAD:SetDefenseRadius(meters)
self:T(self.lid.." SetDefenseRadius")
local radius=meters or 20000
if radius<0 then
radius=20000
end
self.Radius=radius
return self
end
function SHORAD:SetUsingEmOnOff(switch)
self:T(self.lid.." SetUsingEmOnOff")
self.UseEmOnOff=switch or false
return self
end
function SHORAD:_CheckHarms(WeaponName)
self:T(self.lid.." _CheckHarms")
self:T({WeaponName})
local hit=false
if self.DefendHarms then
for _,_name in pairs(SHORAD.Harms)do
if string.find(WeaponName,_name,1,true)then hit=true end
end
end
return hit
end
function SHORAD:_CheckMavs(WeaponName)
self:T(self.lid.." _CheckMavs")
self:T({WeaponName})
local hit=false
if self.DefendMavs then
for _,_name in pairs(SHORAD.Mavs)do
if string.find(WeaponName,_name,1,true)then hit=true end
end
end
return hit
end
function SHORAD:_CheckCoalition(Coalition)
self:T(self.lid.." _CheckCoalition")
local owncoalition=self.Coalition
local othercoalition=""
if Coalition==0 then
othercoalition="neutral"
elseif Coalition==1 then
othercoalition="red"
else
othercoalition="blue"
end
self:T({owncoalition=owncoalition,othercoalition=othercoalition})
if owncoalition~=othercoalition then
return true
else
return false
end
end
function SHORAD:_CheckShotAtShorad(TargetGroupName)
self:T(self.lid.." _CheckShotAtShorad")
local tgtgrp=TargetGroupName
local shorad=self.Groupset
local shoradset=shorad:GetAliveSet()
local returnname=false
for _,_groups in pairs(shoradset)do
local groupname=_groups:GetName()
if string.find(groupname,tgtgrp,1,true)then
returnname=true
end
end
return returnname
end
function SHORAD:_CheckShotAtSams(TargetGroupName)
self:T(self.lid.." _CheckShotAtSams")
local tgtgrp=TargetGroupName
local shorad=self.Samset
local shoradset=shorad:GetSet()
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:_ShotIsDetected()
self:T(self.lid.." _ShotIsDetected")
if self.debug then return true end
local IsDetected=false
local DetectionProb=math.random(self.DefenseLowProb,self.DefenseHighProb)
local ActualDetection=math.random(1,100)
if ActualDetection<=DetectionProb then
IsDetected=true
end
return IsDetected
end
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
local targetgroup=TargetGroup
local targetvec2=nil
if targetcat==Object.Category.UNIT then
targetvec2=GROUP:FindByName(targetgroup):GetVec2()
elseif targetcat==Object.Category.STATIC then
targetvec2=STATIC:FindByName(targetgroup,false):GetVec2()
else
local samset=self.Samset
local sam=samset:GetRandom()
targetvec2=sam:GetVec2()
end
local targetzone=ZONE_RADIUS:New("Shorad",targetvec2,Radius)
local groupset=self.Groupset
local shoradset=groupset:GetAliveSet()
local function SleepShorad(group)
if group and group:IsAlive()then
local groupname=group:GetName()
self.ActiveGroups[groupname]=nil
if self.UseEmOnOff then
group:EnableEmission(false)
else
group:OptionAlarmStateGreen()
end
local text=string.format("Sleeping SHORAD %s",group:GetName())
self:T(text)
local m=MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
if self.shootandscoot then
self:__ShootAndScoot(1,group)
end
end
end
local TDiff=4
for _,_group in pairs(shoradset)do
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)
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)
if self.UseEmOnOff then
_group:EnableEmission(true)
end
_group:OptionAlarmStateRed()
if self.ActiveGroups[groupname]==nil then
self.ActiveGroups[groupname]={Timing=ActiveTimer}
local endtime=timer.getTime()+(ActiveTimer*math.random(75,100)/100)
self.ActiveGroups[groupname].Timer=TIMER:New(SleepShorad,_group):Start(endtime)
if self.shootandscoot then
self:__ShootAndScoot(TDiff,_group)
TDiff=TDiff+1
end
end
end
end
return self
end
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
self:T("**** Calculating hit zone")
if SEADWeapon and SEADWeapon:isExist()then
local position=SEADWeapon:getPosition()
local mheight=height
local wph=math.atan2(position.x.z,position.x.x)
if wph<0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
local wpndata=SEAD.HarmData["AGM_88"]
local mveloc=math.floor(wpndata[2]*340.29)
local c1=(2*mheight*9.81)/(mveloc^2)
local c2=(mveloc^2)/9.81
local Ropt=c2*math.sqrt(c1+1)
if height<=5000 then
Ropt=Ropt*0.72
elseif height<=7500 then
Ropt=Ropt*0.82
elseif height<=10000 then
Ropt=Ropt*0.87
elseif height<=12500 then
Ropt=Ropt*0.98
end
for n=1,3 do
local dist=Ropt-((n-1)*20000)
local predpos=pos0:Translate(dist,wph)
if predpos then
local targetzone=ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset=self.Groupset
local tgtcoord=targetzone:GetRandomPointVec2()
local tgtgrp=seadset:FindNearestGroupFromPointVec2(tgtcoord)
local _targetgroup=nil
local _targetgroupname="none"
local _targetskill="Random"
if tgtgrp and tgtgrp:IsAlive()then
_targetgroup=tgtgrp
_targetgroupname=tgtgrp:GetName()
_targetskill=tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".._targetgroupname)
self:WakeUpShorad(_targetgroupname,self.Radius,self.ActiveTimer,Object.Category.UNIT,true)
end
end
end
end
return self
end
function SHORAD:onafterShootAndScoot(From,Event,To,Shorad)
self:T({From,Event,To})
local possibleZones={}
local mindist=self.minscootdist or 100
local maxdist=self.maxscootdist or 3000
if Shorad and Shorad:IsAlive()then
local NowCoord=Shorad:GetCoordinate()
for _,_zone in pairs(self.SkateZones.Set)do
local zone=_zone
local dist=NowCoord:Get2DDistance(zone:GetCoordinate())
if dist>=mindist and dist<=maxdist then
possibleZones[#possibleZones+1]=zone
if#possibleZones==self.SkateNumber then break end
end
end
if#possibleZones>0 and Shorad:GetVelocityKMH()<2 then
local rand=math.floor(math.random(1,#possibleZones*1000)/1000+0.5)
if rand==0 then rand=1 end
self:T(self.lid.." ShootAndScoot to zone "..rand)
local ToCoordinate=possibleZones[rand]:GetCoordinate()
if self.scootrandomcoord then
ToCoordinate=possibleZones[rand]:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND,land.SurfaceType.ROAD})
end
local formation=self.scootformation or"Cone"
Shorad:RouteGroundTo(ToCoordinate,20,formation,1)
end
end
return self
end
function SHORAD:HandleEventShot(EventData)
self:T({EventData})
self:T(self.lid.." HandleEventShot")
local ShootingWeapon=EventData.Weapon
local ShootingWeaponName=EventData.WeaponName
local weaponcoalition=EventData.IniGroup:GetCoalition()
if self:_CheckCoalition(weaponcoalition)then
local IsDetected=self:_ShotIsDetected()
local DetectedText="false"
if IsDetected then
DetectedText="true"
end
local text=string.format("%s Missile Launched = %s | Detected probability state is %s",self.lid,ShootingWeaponName,DetectedText)
self:T(text)
local m=MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
if(self:_CheckHarms(ShootingWeaponName)or self:_CheckMavs(ShootingWeaponName))and IsDetected then
local targetdata=EventData.Weapon:getTarget()
if not targetdata or self.debug then
if string.find(ShootingWeaponName,"AGM_88",1,true)then
self:I("**** Tracking AGM-88 with no target data.")
local pos0=EventData.IniUnit:GetCoordinate()
local fheight=EventData.IniUnit:GetHeight()
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
end
return self
end
local targetcat=Object.getCategory(targetdata)
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
self:T({targetdata})
local targetunit=nil
if targetcat==Object.Category.UNIT then
targetunit=UNIT:Find(targetdata)
elseif targetcat==Object.Category.STATIC then
local tgtcoord=COORDINATE:NewFromVec3(targetdata:getPoint())
local tgtgrp1=self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord1=tgtgrp1:GetCoordinate()
local tgtgrp2=self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord2=tgtgrp2:GetCoordinate()
local dist1=tgtcoord:Get2DDistance(tgtcoord1)
local dist2=tgtcoord:Get2DDistance(tgtcoord2)
if dist1<dist2 then
targetunit=tgtgrp1
targetcat=Object.Category.UNIT
else
targetunit=tgtgrp2
targetcat=Object.Category.UNIT
end
end
if targetunit and targetunit:IsAlive()then
local targetunitname=targetunit:GetName()
local targetgroup=nil
local targetgroupname="none"
if targetcat==Object.Category.UNIT then
if targetunit.ClassName=="UNIT"then
targetgroup=targetunit:GetGroup()
elseif targetunit.ClassName=="GROUP"then
targetgroup=targetunit
end
targetgroupname=targetgroup:GetName()
elseif targetcat==Object.Category.STATIC then
targetgroup=targetunit
targetgroupname=targetunitname
end
local text=string.format("%s Missile Target = %s",self.lid,tostring(targetgroupname))
self:T(text)
local m=MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
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,true)
end
end
end
end
return self
end
end
AICSAR={
ClassName="AICSAR",
version="0.1.16",
lid="",
coalition=coalition.side.BLUE,
template="",
helotemplate="",
alias="",
farp=nil,
farpzone=nil,
maxdistance=UTILS.NMToMeters(50),
pilotqueue={},
pilotindex=0,
helos={},
verbose=false,
rescuezoneradius=200,
rescued={},
autoonoff=true,
playerset=nil,
Messages={},
SRS=nil,
SRSRadio=false,
SRSFrequency=243,
SRSPath="\\",
SRSModulation=radio.modulation.AM,
SRSSoundPath=nil,
SRSPort=5002,
DCSRadio=false,
DCSFrequency=243,
DCSModulation=radio.modulation.AM,
DCSRadioGroup=nil,
limithelos=true,
helonumber=3,
gettext=nil,
locale="en",
SRSTTSRadio=false,
SRSGoogle=false,
SRSQ=nil,
SRSPilot=nil,
SRSPilotVoice=false,
SRSOperator=nil,
SRSOperatorVoice=false,
PilotStore=nil,
Speed=100,
Altitude=1500,
UseEventEject=false,
Delay=100,
}
AICSAR.Messages={
EN={
INITIALOK="Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
INITIALNOTOK="Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
PILOTDOWN="Mayday, mayday, mayday! Pilot down at ",
PILOTKIA="Pilot KIA!",
HELODOWN="CSAR Helo Down!",
PILOTRESCUED="Pilot rescued!",
PILOTINHELO="Pilot picked up!",
},
DE={
INITIALOK="Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!",
INITIALNOTOK="Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!",
PILOTDOWN="Mayday, mayday, mayday! Pilot abgestürzt: ",
PILOTKIA="Pilot gefallen!",
HELODOWN="CSAR Hubschrauber verloren!",
PILOTRESCUED="Pilot gerettet!",
PILOTINHELO="Pilot an Bord geholt!",
},
}
AICSAR.RadioMessages={
EN={
INITIALOK="initialok.ogg",
INITIALNOTOK="initialnotok.ogg",
PILOTDOWN="pilotdown.ogg",
PILOTKIA="pilotkia.ogg",
HELODOWN="helodown.ogg",
PILOTRESCUED="pilotrescued.ogg",
PILOTINHELO="pilotinhelo.ogg",
},
}
AICSAR.RadioLength={
EN={
INITIALOK=4.1,
INITIALNOTOK=4.6,
PILOTDOWN=2.6,
PILOTKIA=1.1,
HELODOWN=2.1,
PILOTRESCUED=3.5,
PILOTINHELO=2.6,
},
}
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
local self=BASE:Inherit(self,FSM:New())
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in AICSAR!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="Red Cross"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="IFRC"
elseif self.coalition==coalition.side.BLUE then
self.alias="CSAR"
end
end
end
self.template=Pilottemplate
self.helotemplate=Helotemplate
self.farp=FARP
self.farpzone=MASHZone
self.playerset=SET_CLIENT:New():FilterActive(true):FilterCategories("helicopter"):FilterStart()
self.UseEventEject=false
self.Delay=300
self.SRS=nil
self.SRSRadio=false
self.SRSTTSRadio=false
self.SRSGoogle=false
self.SRSQ=nil
self.SRSFrequency=243
self.SRSPath="\\"
self.SRSModulation=radio.modulation.AM
self.SRSSoundPath=nil
self.SRSPort=5002
self.DCSRadio=false
self.DCSFrequency=243
self.DCSModulation=radio.modulation.AM
self.DCSRadioGroup=nil
self.DCSRadioQueue=nil
self.MGRS_Accuracy=2
self.limithelos=true
self.helonumber=3
self:InitLocalization()
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self.PilotStore=FIFO:New()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","PilotDown","*")
self:AddTransition("*","PilotPickedUp","*")
self:AddTransition("*","PilotUnloaded","*")
self:AddTransition("*","PilotRescued","*")
self:AddTransition("*","PilotKIA","*")
self:AddTransition("*","HeloDown","*")
self:AddTransition("*","HeloOnDuty","*")
self:AddTransition("*","Stop","Stopped")
self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EjectEventHandler)
self:__Start(math.random(2,5))
local text=string.format("%sAICSAR Version %s Starting",self.lid,self.version)
self:I(text)
return self
end
function AICSAR:InitLocalization()
self:T(self.lid.."InitLocalization")
self.gettext=TEXTANDSOUND:New(self.ClassName,"en")
self.gettext:AddEntry("en","INITIALOK",AICSAR.Messages.EN.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
self.gettext:AddEntry("en","INITIALNOTOK",AICSAR.Messages.EN.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
self.gettext:AddEntry("en","HELODOWN",AICSAR.Messages.EN.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
self.gettext:AddEntry("en","PILOTDOWN",AICSAR.Messages.EN.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
self.gettext:AddEntry("en","PILOTINHELO",AICSAR.Messages.EN.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
self.gettext:AddEntry("en","PILOTKIA",AICSAR.Messages.EN.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
self.gettext:AddEntry("en","PILOTRESCUED",AICSAR.Messages.EN.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
self.gettext:AddEntry("de","INITIALOK",AICSAR.Messages.DE.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
self.gettext:AddEntry("de","INITIALNOTOK",AICSAR.Messages.DE.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
self.gettext:AddEntry("de","HELODOWN",AICSAR.Messages.DE.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
self.gettext:AddEntry("de","PILOTDOWN",AICSAR.Messages.DE.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
self.gettext:AddEntry("de","PILOTINHELO",AICSAR.Messages.DE.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
self.gettext:AddEntry("de","PILOTKIA",AICSAR.Messages.DE.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
self.gettext:AddEntry("de","PILOTRESCUED",AICSAR.Messages.DE.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
self.locale="en"
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.SRS:SetLabel("ACSR")
self.SRS:SetCoalition(self.coalition)
self.SRSModulation=Modulation or radio.modulation.AM
local soundpath=os.getenv('TMP').."\\DCS\\Mission\\l10n\\DEFAULT"
self.SRSSoundPath=SoundPath or soundpath
self.SRSPort=Port or MSRS.port or 5002
if OnOff then
self.SRS=MSRS:New(Path,Frequency,Modulation)
self.SRS:SetPort(self.SRSPort)
end
return self
end
function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Culture,Gender,GoogleCredentials)
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.SRSModulation=Modulation or radio.modulation.AM
self.SRSPort=Port or MSRS.port or 5002
if OnOff then
self.SRS=MSRS:New(self.SRSPath,Frequency,Modulation)
self.SRS:SetPort(self.SRSPort)
self.SRS:SetCoalition(self.coalition)
self.SRS:SetLabel("ACSR")
self.SRS:SetVoice(Voice)
self.SRS:SetCulture(Culture)
self.SRS:SetGender(Gender)
if GoogleCredentials then
self.SRS:SetProviderOptionsGoogle(GoogleCredentials,GoogleCredentials)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
self.SRSGoogle=true
end
self.SRSQ=MSRSQUEUE:New(self.alias)
end
return self
end
function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender)
self:T(self.lid.."SetPilotTTSVoice")
self.SRSPilotVoice=true
self.SRSPilot=MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation)
self.SRSPilot:SetCoalition(self.coalition)
self.SRSPilot:SetVoice(Voice)
self.SRSPilot:SetCulture(Culture or"en-US")
self.SRSPilot:SetGender(Gender or"male")
self.SRSPilot:SetLabel("PILOT")
if self.SRSGoogle then
local poptions=self.SRS:GetProviderOptions(MSRS.Provider.GOOGLE)
self.SRSPilot:SetProviderOptionsGoogle(poptions.credentials,poptions.key)
self.SRSPilot:SetProvider(MSRS.Provider.GOOGLE)
end
return self
end
function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender)
self:T(self.lid.."SetOperatorTTSVoice")
self.SRSOperatorVoice=true
self.SRSOperator=MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation)
self.SRSOperator:SetCoalition(self.coalition)
self.SRSOperator:SetVoice(Voice)
self.SRSOperator:SetCulture(Culture or"en-GB")
self.SRSOperator:SetGender(Gender or"female")
self.SRSOperator:SetLabel("RESCUE")
if self.SRSGoogle then
local poptions=self.SRS:GetProviderOptions(MSRS.Provider.GOOGLE)
self.SRSOperator:SetProviderOptionsGoogle(poptions.credentials,poptions.key)
self.SRSOperator:SetProvider(MSRS.Provider.GOOGLE)
end
return self
end
function AICSAR:SetDCSRadio(OnOff,Frequency,Modulation,Group)
self:T(self.lid.."SetDCSRadio")
self:T(self.lid.."SetDCSRadio to "..tostring(OnOff))
self.DCSRadio=OnOff and true
self.DCSFrequency=Frequency or 243
self.DCSModulation=Modulation or radio.modulation.AM
self.DCSRadioGroup=Group
if self.DCSRadio then
self.DCSRadioQueue=RADIOQUEUE:New(Frequency,Modulation,"AI-CSAR")
self.DCSRadioQueue:Start(5,5)
self.DCSRadioQueue:SetRadioPower(1000)
self.DCSRadioQueue:SetSenderCoordinate(Group:GetCoordinate())
else
if self.DCSRadioQueue then
self.DCSRadioQueue:Stop()
end
end
return self
end
function AICSAR:DCSRadioBroadcast(Soundfile,Duration,Subtitle)
self:T(self.lid.."DCSRadioBroadcast")
local radioqueue=self.DCSRadioQueue
radioqueue:NewTransmission(Soundfile,Duration,nil,2,nil,Subtitle,10)
return self
end
function AICSAR:_EjectEventHandler(EventData)
local _event=EventData
if _event.IniPlayerName then
self.PilotStore:Push(_event.IniPlayerName)
self:T(self.lid.."Pilot Ejected: ".._event.IniPlayerName)
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
end
return self
end
function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition)
local distancetofarp=_LandingPos:Get2DDistance(self.farp:GetCoordinate())
local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale)
local text=""
local setting={}
setting.MGRS_Accuracy=self.MGRS_Accuracy
local location=_LandingPos:ToStringMGRS(setting)
local msgtxt=Text..location.."!"
location=string.gsub(location,"MGRS ","")
location=string.gsub(location,"%s+","")
location=string.gsub(location,"([%a%d])","%1;")
location=string.gsub(location,"0","zero")
location=string.gsub(location,"9","niner")
location="MGRS;"..location
if self.SRSGoogle then
location=string.format("<say-as interpret-as='characters'>%s</say-as>",location)
end
text=Text..location.."!"
local ttstext=Text..location.."! Repeat! "..location
if _coalition==self.coalition then
if self.verbose then
MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSPilotVoice then
self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1)
else
self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1)
end
end
end
if _coalition==self.coalition and distancetofarp<=self.maxdistance then
self:T(self.lid.."Spawning new Pilot")
self.pilotindex=self.pilotindex+1
local newpilot=SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template,self.pilotindex))
newpilot:InitDelayOff()
newpilot:OnSpawnGroup(
function(grp)
self.pilotqueue[self.pilotindex]=grp
end
)
newpilot:SpawnFromCoordinate(_LandingPos)
self:__PilotDown(2,_LandingPos,true)
elseif _coalition==self.coalition and distancetofarp>self.maxdistance then
self:T(self.lid.."Pilot out of reach")
self:__PilotDown(2,_LandingPos,false)
end
return self
end
function AICSAR:_EventHandler(EventData,FromEject)
self:T(self.lid.."OnEventLandingAfterEjection ID="..EventData.id)
if self.autoonoff then
if self.playerset:CountAlive()>0 then
return self
end
end
if self.UseEventEject and(not FromEject)then return self end
local _event=EventData
local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _country=_event.initiator:getCountry()
local _coalition=coalition.getCountryCoalition(_country)
local distancetofarp=_LandingPos:Get2DDistance(self.farp:GetCoordinate())
local Text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTDOWN",self.locale)
local text=""
local setting={}
setting.MGRS_Accuracy=self.MGRS_Accuracy
local location=_LandingPos:ToStringMGRS(setting)
local msgtxt=Text..location.."!"
location=string.gsub(location,"MGRS ","")
location=string.gsub(location,"%s+","")
location=string.gsub(location,"([%a%d])","%1;")
location=string.gsub(location,"0","zero")
location=string.gsub(location,"9","niner")
location="MGRS;"..location
if self.SRSGoogle then
location=string.format("<say-as interpret-as='characters'>%s</say-as>",location)
end
text=Text..location.."!"
local ttstext=Text..location.."! Repeat! "..location
if _coalition==self.coalition then
if self.verbose then
MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSPilotVoice then
self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1)
else
self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1)
end
end
end
if _coalition==self.coalition and distancetofarp<=self.maxdistance then
self:T(self.lid.."Spawning new Pilot")
self.pilotindex=self.pilotindex+1
local newpilot=SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template,self.pilotindex))
newpilot:InitDelayOff()
newpilot:OnSpawnGroup(
function(grp)
self.pilotqueue[self.pilotindex]=grp
end
)
newpilot:SpawnFromCoordinate(_LandingPos)
Unit.destroy(_event.initiator)
self:__PilotDown(2,_LandingPos,true)
elseif _coalition==self.coalition and distancetofarp>self.maxdistance then
self:T(self.lid.."Pilot out of reach")
self:__PilotDown(2,_LandingPos,false)
end
return self
end
function AICSAR:_GetFlight()
self:T(self.lid.."_GetFlight")
local newhelo=SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000))
:InitDelayOff()
:InitUnControlled(true)
:OnSpawnGroup(
function(Group)
self:__HeloOnDuty(1,Group)
end
)
:Spawn()
local nhelo=FLIGHTGROUP:New(newhelo)
nhelo:SetHomebase(self.farp)
nhelo:Activate()
return nhelo
end
function AICSAR:_InitMission(Pilot,Index)
self:T(self.lid.."_InitMission")
local pickupzone=ZONE_GROUP:New(Pilot:GetName(),Pilot,self.rescuezoneradius)
local opstransport=OPSTRANSPORT:New(Pilot,pickupzone,self.farpzone)
local helo=self:_GetFlight()
helo.AICSARReserved=true
helo:SetDefaultAltitude(self.Altitude or 1500)
helo:SetDefaultSpeed(self.Speed or 100)
helo:AddOpsTransport(opstransport)
local function AICPickedUp(Helo,Cargo,Index)
self:__PilotPickedUp(2,Helo,Cargo,Index)
end
local function AICHeloDead(Helo,Index)
self:__HeloDown(2,Helo,Index)
end
local function AICHeloUnloaded(Helo,OpsGroup)
self:__PilotUnloaded(2,Helo,OpsGroup)
end
function helo:OnAfterLoading(From,Event,To)
AICPickedUp(helo,helo:GetCargoGroups(),Index)
helo:__LoadingDone(5)
end
function helo:OnAfterDead(From,Event,To)
AICHeloDead(helo,Index)
end
function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo)
AICHeloUnloaded(helo,OpsGroupCargo)
helo:__UnloadingDone(5)
end
self.helos[Index]=helo
return self
end
function AICSAR:_CheckInMashZone(Pilot)
self:T(self.lid.."_CheckInMashZone")
if Pilot:IsInZone(self.farpzone)then
return true
else
return false
end
end
function AICSAR:SetDefaultSpeed(Knots)
self:T(self.lid.."SetDefaultSpeed")
self.Speed=Knots or 100
return self
end
function AICSAR:SetDefaultAltitude(Feet)
self:T(self.lid.."SetDefaultAltitude")
self.Altitude=Feet or 1500
return self
end
function AICSAR:_CheckHelos()
self:T(self.lid.."_CheckHelos")
for _index,_helo in pairs(self.helos)do
local helo=_helo
if helo and helo.ClassName=="FLIGHTGROUP"then
local state=helo:GetState()
local name=helo:GetName()
self:T("Helo group "..name.." in state "..state)
if state=="Arrived"then
helo:__Stop(5)
self.helos[_index]=nil
end
else
self.helos[_index]=nil
end
end
return self
end
function AICSAR:_CountHelos()
self:T(self.lid.."_CountHelos")
local count=0
for _index,_helo in pairs(self.helos)do
count=count+1
end
return count
end
function AICSAR:_CheckQueue(OpsGroup)
self:T(self.lid.."_CheckQueue")
for _index,_pilot in pairs(self.pilotqueue)do
local classname=_pilot.ClassName and _pilot.ClassName or"NONE"
local name=_pilot.GroupName and _pilot.GroupName or"NONE"
local playername="John Doe"
local helocount=self:_CountHelos()
if _pilot and _pilot.ClassName and _pilot.ClassName=="GROUP"then
local flightgroup=self.helos[_index]
if self:_CheckInMashZone(_pilot)then
self:T("Pilot".._pilot.GroupName.." rescued!")
if OpsGroup then
OpsGroup:Despawn(10)
else
_pilot:Destroy(true,10)
end
self.pilotqueue[_index]=nil
self.rescued[_index]=true
if self.PilotStore:Count()>0 then
playername=self.PilotStore:Pull()
end
self:__PilotRescued(2,playername)
if flightgroup then
flightgroup.AICSARReserved=false
end
end
if not _pilot.AICSAR then
if self.limithelos and helocount>=self.helonumber then
break
end
_pilot.AICSAR={}
_pilot.AICSAR.Status="Initiated"
_pilot.AICSAR.Boarded=false
self:_InitMission(_pilot,_index)
break
else
if flightgroup then
local state=flightgroup:GetState()
_pilot.AICSAR.Status=state
end
end
end
end
return self
end
function AICSAR:onafterStart(From,Event,To)
self:T({From,Event,To})
self:__Status(3)
return self
end
function AICSAR:onafterStatus(From,Event,To)
self:T({From,Event,To})
self:_CheckHelos()
self:__Status(30)
return self
end
function AICSAR:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.LandingAfterEjection)
if self.DCSRadioQueue then
self.DCSRadioQueue:Stop()
end
return self
end
function AICSAR:onafterPilotDown(From,Event,To,Coordinate,InReach)
self:T({From,Event,To})
local CoordinateText=Coordinate:ToStringMGRS()
local inreach=tostring(InReach)
if InReach then
local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("INITIALOK",self.locale)
self:T(text)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSOperatorVoice then
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
else
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
end
else
local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("INITIALNOTOK",self.locale)
self:T(text)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSOperatorVoice then
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
else
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
end
end
self:_CheckQueue()
return self
end
function AICSAR:onafterPilotKIA(From,Event,To)
self:T({From,Event,To})
local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTKIA",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
return self
end
function AICSAR:onafterHeloDown(From,Event,To,Helo,Index)
self:T({From,Event,To})
local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("HELODOWN",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
if self.SRSOperatorVoice then
self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1)
else
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
end
local findex=0
local fhname=Helo:GetName()
if Index and Index>0 then
findex=Index
else
for _index,_helo in pairs(self.helos)do
local helo=_helo
local hname=helo:GetName()
if fhname==hname then
findex=_index
break
end
end
end
if findex>0 and not self.rescued[findex]then
local pilot=self.pilotqueue[findex]
self.helos[findex]=nil
if pilot.AICSAR.Boarded then
self:T("Helo Down: Found DEAD Pilot ID "..findex.." with name "..pilot:GetName())
self:__PilotKIA(2)
self.pilotqueue[findex]=nil
else
self:T("Helo Down: Found ALIVE Pilot ID "..findex.." with name "..pilot:GetName())
self:_InitMission(pilot,findex)
end
end
return self
end
function AICSAR:onafterPilotRescued(From,Event,To,PilotName)
self:T({From,Event,To})
local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTRESCUED",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
return self
end
function AICSAR:onafterPilotUnloaded(From,Event,To,Helo,OpsGroup)
self:T({From,Event,To})
self:_CheckQueue(OpsGroup)
return self
end
function AICSAR:onafterPilotPickedUp(From,Event,To,Helo,CargoTable,Index)
self:T({From,Event,To})
local text,Soundfile,Soundlength,Subtitle=self.gettext:GetEntry("PILOTINHELO",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound=SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
elseif self.SRSTTSRadio then
self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1)
end
local findex=0
local fhname=Helo:GetName()
if Index and Index>0 then
findex=Index
else
for _index,_helo in pairs(self.helos)do
local helo=_helo
local hname=helo:GetName()
if fhname==hname then
findex=_index
break
end
end
end
if findex>0 then
local pilot=self.pilotqueue[findex]
self:T("Boarded: Found Pilot ID "..findex.." with name "..pilot:GetName())
pilot.AICSAR.Boarded=true
end
return self
end
AMMOTRUCK={
ClassName="AMMOTRUCK",
lid="",
version="0.0.12",
alias="",
debug=false,
trucklist={},
targetlist={},
coalition=nil,
truckset=nil,
targetset=nil,
remunitionqueue={},
waitingtargets={},
ammothreshold=5,
remunidist=20000,
monitor=-60,
unloadtime=600,
waitingtime=1800,
routeonroad=true,
reloads=5,
}
AMMOTRUCK.State={
IDLE="idle",
DRIVING="driving",
ARRIVED="arrived",
UNLOADING="unloading",
RETURNING="returning",
WAITING="waiting",
RELOADING="reloading",
OUTOFAMMO="outofammo",
REQUESTED="requested",
}
function AMMOTRUCK:New(Truckset,Targetset,Coalition,Alias,Homezone)
local self=BASE:Inherit(self,FSM:New())
self.truckset=Truckset
self.targetset=Targetset
self.coalition=Coalition
self.alias=Alias
self.debug=false
self.remunitionqueue={}
self.trucklist={}
self.targetlist={}
self.ammothreshold=5
self.remunidist=20000
self.homezone=Homezone
self.waitingtime=1800
self.usearmygroup=false
self.hasarmygroup=false
self.lid=string.format("AMMOTRUCK %s | %s | ",self.version,self.alias)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Monitor","*")
self:AddTransition("*","RouteTruck","*")
self:AddTransition("*","TruckArrived","*")
self:AddTransition("*","TruckUnloading","*")
self:AddTransition("*","TruckReturning","*")
self:AddTransition("*","TruckHome","*")
self:AddTransition("*","Stop","Stopped")
self:__Start(math.random(5,10))
self:I(self.lid.."Started")
return self
end
function AMMOTRUCK:CheckDrivingTrucks(dataset)
self:T(self.lid.." CheckDrivingTrucks")
local data=dataset
for _,_data in pairs(data)do
local truck=_data
local coord=truck.group:GetCoordinate()
local tgtcoord=truck.targetcoordinate
local dist=coord:Get2DDistance(tgtcoord)
if dist<=150 then
truck.statusquo=AMMOTRUCK.State.ARRIVED
truck.timestamp=timer.getAbsTime()
truck.coordinate=coord
self:__TruckArrived(1,truck)
end
local Tnow=timer.getAbsTime()
if Tnow-truck.timestamp>30 then
local group=truck.group
if self.usearmygroup then
group=truck.group:GetGroup()
end
local currspeed=group:GetVelocityKMH()
if truck.lastspeed then
if truck.lastspeed==0 and currspeed==0 then
self:T(truck.group:GetName().." Is not moving!")
truck.timestamp=timer.getAbsTime()
if self.routeonroad then
group:RouteGroundOnRoad(truck.targetcoordinate,30,2,"Vee")
else
group:RouteGroundTo(truck.targetcoordinate,30,"Vee",2)
end
end
truck.lastspeed=currspeed
else
truck.lastspeed=currspeed
truck.timestamp=timer.getAbsTime()
end
self:I({truck=truck.group:GetName(),currspeed=currspeed,lastspeed=truck.lastspeed})
end
end
return self
end
function AMMOTRUCK:GetAmmoStatus(Group)
local ammotot,shells,rockets,bombs,missiles,narti=Group:GetAmmunition()
return rockets+missiles+narti
end
function AMMOTRUCK:CheckWaitingTargets(dataset)
self:T(self.lid.." CheckWaitingTargets")
local data=dataset
for _,_data in pairs(data)do
local truck=_data
local Tnow=timer.getAbsTime()
local Tdiff=Tnow-truck.timestamp
if Tdiff>self.waitingtime then
local hasammo=self:GetAmmoStatus(truck.group)
if hasammo<=self.ammothreshold then
truck.statusquo=AMMOTRUCK.State.OUTOFAMMO
else
truck.statusquo=AMMOTRUCK.State.IDLE
end
end
end
return self
end
function AMMOTRUCK:CheckReturningTrucks(dataset)
self:T(self.lid.." CheckReturningTrucks")
local data=dataset
local tgtcoord=self.homezone:GetCoordinate()
local radius=self.homezone:GetRadius()
for _,_data in pairs(data)do
local truck=_data
local coord=truck.group:GetCoordinate()
local dist=coord:Get2DDistance(tgtcoord)
self:T({name=truck.name,radius=radius,distance=dist})
if dist<=radius then
truck.statusquo=AMMOTRUCK.State.IDLE
truck.timestamp=timer.getAbsTime()
truck.coordinate=coord
truck.reloads=self.reloads or 5
self:__TruckHome(1,truck)
end
end
return self
end
function AMMOTRUCK:FindTarget(name)
self:T(self.lid.." FindTarget")
local data=nil
local dataset=self.targetlist
for _,_entry in pairs(dataset)do
local entry=_entry
if entry.name==name then
data=entry
break
end
end
return data
end
function AMMOTRUCK:FindTruck(name)
self:T(self.lid.." FindTruck")
local data=nil
local dataset=self.trucklist
for _,_entry in pairs(dataset)do
local entry=_entry
if entry.name==name then
data=entry
break
end
end
return data
end
function AMMOTRUCK:CheckArrivedTrucks(dataset)
self:T(self.lid.." CheckArrivedTrucks")
local data=dataset
for _,_data in pairs(data)do
local truck=_data
truck.statusquo=AMMOTRUCK.State.UNLOADING
truck.timestamp=timer.getAbsTime()
self:__TruckUnloading(2,truck)
local aridata=self:FindTarget(truck.targetname)
if aridata then
aridata.statusquo=AMMOTRUCK.State.RELOADING
aridata.timestamp=timer.getAbsTime()
end
end
return self
end
function AMMOTRUCK:CheckUnloadingTrucks(dataset)
self:T(self.lid.." CheckUnloadingTrucks")
local data=dataset
for _,_data in pairs(data)do
local truck=_data
local Tnow=timer.getAbsTime()
local Tpassed=Tnow-truck.timestamp
local hasammo=self:GetAmmoStatus(truck.targetgroup)
if Tpassed>self.unloadtime and hasammo>self.ammothreshold then
truck.statusquo=AMMOTRUCK.State.RETURNING
truck.timestamp=timer.getAbsTime()
self:__TruckReturning(2,truck)
local aridata=self:FindTarget(truck.targetname)
if aridata then
aridata.statusquo=AMMOTRUCK.State.IDLE
aridata.timestamp=timer.getAbsTime()
end
end
end
return self
end
function AMMOTRUCK:CheckTargetsAlive()
self:T(self.lid.." CheckTargetsAlive")
local arilist=self.targetlist
for _,_ari in pairs(arilist)do
local ari=_ari
if ari.group and ari.group:IsAlive()then
else
self.targetlist[ari.name]=nil
end
end
local aritable=self.targetset:GetSetObjects()
for _,_ari in pairs(aritable)do
local ari=_ari
if ari and ari:IsAlive()and not self.targetlist[ari:GetName()]then
local name=ari:GetName()
local newari={}
newari.name=name
newari.group=ari
newari.statusquo=AMMOTRUCK.State.IDLE
newari.timestamp=timer.getAbsTime()
newari.coordinate=ari:GetCoordinate()
local hasammo=self:GetAmmoStatus(ari)
newari.ammo=hasammo
self.targetlist[name]=newari
end
end
return self
end
function AMMOTRUCK:CheckTrucksAlive()
self:T(self.lid.." CheckTrucksAlive")
local trucklist=self.trucklist
for _,_truck in pairs(trucklist)do
local truck=_truck
if truck.group and truck.group:IsAlive()then
else
local tgtname=truck.targetname
local targetdata=self:FindTarget(tgtname)
if targetdata then
if targetdata.statusquo~=AMMOTRUCK.State.IDLE then
targetdata.statusquo=AMMOTRUCK.State.IDLE
end
end
self.trucklist[truck.name]=nil
end
end
local trucktable=self.truckset:GetSetObjects()
for _,_truck in pairs(trucktable)do
local truck=_truck
if truck and truck:IsAlive()and not self.trucklist[truck:GetName()]then
local name=truck:GetName()
local newtruck={}
newtruck.name=name
newtruck.group=truck
if self.hasarmygroup then
if truck.ClassName and truck.ClassName=="GROUP"then
local trucker=ARMYGROUP:New(truck)
trucker:Activate()
newtruck.group=trucker
end
end
newtruck.statusquo=AMMOTRUCK.State.IDLE
newtruck.timestamp=timer.getAbsTime()
newtruck.coordinate=truck:GetCoordinate()
newtruck.reloads=self.reloads or 5
self.trucklist[name]=newtruck
end
end
return self
end
function AMMOTRUCK:onafterStart(From,Event,To)
self:T({From,Event,To})
if ARMYGROUP and self.usearmygroup then
self.hasarmygroup=true
else
self.hasarmygroup=false
end
if self.debug then
BASE:TraceOn()
BASE:TraceClass("AMMOTRUCK")
end
self:CheckTargetsAlive()
self:CheckTrucksAlive()
self:__Monitor(-30)
return self
end
function AMMOTRUCK:onafterMonitor(From,Event,To)
self:T({From,Event,To})
self:CheckTargetsAlive()
self:CheckTrucksAlive()
local remunition=false
local remunitionqueue={}
local waitingtargets={}
for _,_ari in pairs(self.targetlist)do
local data=_ari
if data.group and data.group:IsAlive()then
data.ammo=self:GetAmmoStatus(data.group)
data.timestamp=timer.getAbsTime()
local text=string.format("Ari %s | Ammo %d | State %s",data.name,data.ammo,data.statusquo)
self:T(text)
if data.ammo<=self.ammothreshold and(data.statusquo==AMMOTRUCK.State.IDLE or data.statusquo==AMMOTRUCK.State.OUTOFAMMO)then
data.statusquo=AMMOTRUCK.State.OUTOFAMMO
remunitionqueue[#remunitionqueue+1]=data
remunition=true
elseif data.statusquo==AMMOTRUCK.State.WAITING then
waitingtargets[#waitingtargets+1]=data
end
else
self.targetlist[data.name]=nil
end
end
local idletrucks={}
local drivingtrucks={}
local unloadingtrucks={}
local arrivedtrucks={}
local returningtrucks={}
local found=false
for _,_truckdata in pairs(self.trucklist)do
local data=_truckdata
if data.group and data.group:IsAlive()then
local text=string.format("Truck %s | State %s",data.name,data.statusquo)
self:T(text)
if data.statusquo==AMMOTRUCK.State.IDLE then
idletrucks[#idletrucks+1]=data
found=true
elseif data.statusquo==AMMOTRUCK.State.DRIVING then
drivingtrucks[#drivingtrucks+1]=data
elseif data.statusquo==AMMOTRUCK.State.ARRIVED then
arrivedtrucks[#arrivedtrucks+1]=data
elseif data.statusquo==AMMOTRUCK.State.UNLOADING then
unloadingtrucks[#unloadingtrucks+1]=data
elseif data.statusquo==AMMOTRUCK.State.RETURNING then
returningtrucks[#returningtrucks+1]=data
if data.reloads>0 or data.reloads==-1 then
idletrucks[#idletrucks+1]=data
found=true
end
end
else
self.truckset[data.name]=nil
end
end
local n=0
if found and remunition then
for _,_truckdata in pairs(idletrucks)do
local truckdata=_truckdata
local truckcoord=truckdata.group:GetCoordinate()
for _,_aridata in pairs(remunitionqueue)do
local aridata=_aridata
local aricoord=aridata.coordinate
local distance=truckcoord:Get2DDistance(aricoord)
if distance<=self.remunidist and aridata.statusquo==AMMOTRUCK.State.OUTOFAMMO and n<=#idletrucks then
n=n+1
aridata.statusquo=AMMOTRUCK.State.REQUESTED
self:__RouteTruck(n*5,truckdata,aridata)
break
end
end
end
end
if#drivingtrucks>0 then
self:CheckDrivingTrucks(drivingtrucks)
end
if#arrivedtrucks>0 then
self:CheckArrivedTrucks(arrivedtrucks)
end
if#unloadingtrucks>0 then
self:CheckUnloadingTrucks(unloadingtrucks)
end
if#returningtrucks>0 then
self:CheckReturningTrucks(returningtrucks)
end
if#waitingtargets>0 then
self:CheckWaitingTargets(waitingtargets)
end
self:__Monitor(self.monitor)
return self
end
function AMMOTRUCK:onafterRouteTruck(From,Event,To,Truckdata,Aridata)
self:T({From,Event,To,Truckdata.name,Aridata.name})
local truckdata=Truckdata
local aridata=Aridata
local tgtgrp=aridata.group
local tgtzone=ZONE_GROUP:New(aridata.name,tgtgrp,30)
local tgtcoord=tgtzone:GetRandomCoordinate(15)
if self.hasarmygroup then
local mission=AUFTRAG:NewONGUARD(tgtcoord)
local oldmission=truckdata.group:GetMissionCurrent()
if oldmission then oldmission:Cancel()end
mission:SetTime(5)
mission:SetTeleport(false)
truckdata.group:AddMission(mission)
elseif self.routeonroad then
truckdata.group:RouteGroundOnRoad(tgtcoord,30)
else
truckdata.group:RouteGroundTo(tgtcoord,30)
end
truckdata.statusquo=AMMOTRUCK.State.DRIVING
truckdata.targetgroup=tgtgrp
truckdata.targetname=aridata.name
truckdata.targetcoordinate=tgtcoord
aridata.statusquo=AMMOTRUCK.State.WAITING
aridata.timestamp=timer.getAbsTime()
return self
end
function AMMOTRUCK:onafterTruckUnloading(From,Event,To,Truckdata)
local m=MESSAGE:New("Truck "..Truckdata.name.." unloading!",15,"AmmoTruck"):ToCoalitionIf(self.coalition,self.debug)
local truck=Truckdata
local coord=truck.group:GetCoordinate()
local heading=truck.group:GetHeading()
heading=heading<180 and(360-heading)or(heading-180)
local cid=self.coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA
cid=self.coalition==coalition.side.NEUTRAL and country.id.UN_PEACEKEEPERS or cid
local ammo={}
for i=1,5 do
ammo[i]=SPAWNSTATIC:NewFromType("ammo_cargo","Cargos",cid)
:InitCoordinate(coord:Translate((15+((i-1)*4)),heading))
:Spawn(0,"AmmoCrate-"..math.random(1,10000))
end
local function destroyammo(ammo)
for _,_crate in pairs(ammo)do
_crate:Destroy(false)
end
end
local scheduler=SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime)
if truck.reloads~=-1 then
truck.reloads=truck.reloads-1
end
return self
end
function AMMOTRUCK:onafterTruckReturning(From,Event,To,Truck)
self:T({From,Event,To,Truck.name})
local truckdata=Truck
local tgtzone=self.homezone
local tgtcoord=tgtzone:GetRandomCoordinate()
if self.hasarmygroup then
local mission=AUFTRAG:NewONGUARD(tgtcoord)
local oldmission=truckdata.group:GetMissionCurrent()
if oldmission then oldmission:Cancel()end
mission:SetTime(5)
mission:SetTeleport(false)
truckdata.group:AddMission(mission)
elseif self.routeonroad then
truckdata.group:RouteGroundOnRoad(tgtcoord,30,1,"Cone")
else
truckdata.group:RouteGroundTo(tgtcoord,30,"Cone",1)
end
return self
end
function AMMOTRUCK:onafterStop(From,Event,To)
self:T({From,Event,To})
return self
end
AUTOLASE={
ClassName="AUTOLASE",
lid="",
verbose=0,
alias="",
debug=false,
smokemenu=true,
RoundingPrecision=0,
increasegroundawareness=true,
MonitorFrequency=30,
}
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())
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
elseif Coalition=="red"then
self.coalition=coalition.side.RED
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
else
self:E("ERROR: Unknown coalition in AUTOLASE!")
end
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="Lion"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="Wolf"
elseif self.coalition==coalition.side.BLUE then
self.alias="Fox"
end
end
end
local self=BASE:Inherit(self,INTEL:New(RecceSet,Coalition,Alias))
self.RecceSet=RecceSet
self.DetectVisual=true
self.DetectOptical=true
self.DetectRadar=true
self.DetectIRST=true
self.DetectRWR=true
self.DetectDLINK=true
self.LaserCodes=UTILS.GenerateLaserCodes()
self.LaseDistance=5000
self.LaseDuration=300
self.GroupsByThreat={}
self.UnitsByThreat={}
self.RecceNames={}
self.RecceLaserCode={}
self.RecceSmokeColor={}
self.RecceUnitNames={}
self.maxlasing=4
self.CurrentLasing={}
self.lasingindex=0
self.deadunitnotes={}
self.usepilotset=false
self.reporttimeshort=10
self.reporttimelong=30
self.smoketargets=false
self.smokecolor=SMOKECOLOR.Red
self.smokeoffset=nil
self.notifypilots=true
self.targetsperrecce={}
self.RecceUnits={}
self.forcecooldown=true
self.cooldowntime=60
self.useSRS=false
self.SRSPath=""
self.SRSFreq=251
self.SRSMod=radio.modulation.AM
self.NoMenus=false
self.minthreatlevel=0
self.blacklistattributes={}
self:SetLaserCodes({1688,1130,4785,6547,1465,4578})
self.playermenus={}
self.smokemenu=true
self.threatmenu=true
self.RoundingPrecision=0
self.increasegroundawareness=true
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","*")
self:AddTransition("*","TargetLost","*")
self:AddTransition("*","TargetDestroyed","*")
self:AddTransition("*","RecceKIA","*")
self:AddTransition("*","LaserTimeout","*")
self:AddTransition("*","Cancel","*")
if PilotSet then
self.usepilotset=true
self.pilotset=PilotSet
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
end
self:SetClusterAnalysis(false,false)
self:__Start(2)
self:__Monitor(math.random(5,10))
return self
end
function AUTOLASE: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
local lasemenu=MENU_GROUP_COMMAND:New(Group,"Status",lasetopm,self.ShowStatus,self,Group,Unit)
if self.smokemenu then
local smoke=(self.smoketargets==true)and"off"or"on"
local smoketext=string.format("Switch smoke targets to %s",smoke)
local smokemenu=MENU_GROUP_COMMAND:New(Group,smoketext,lasetopm,self.SetSmokeTargets,self,(not self.smoketargets))
end
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)
if unit and unit:IsAlive()then
local name=unit:GetName()
local mname=string.gsub(name,".%d+.%d+$","")
local code=self:GetLaserCode(name)
local unittop=MENU_GROUP:New(Group,"Change laser code for "..mname,lasetopm)
for _,_code in pairs(self.LaserCodes)do
local text=tostring(_code)
if _code==code then text=text.."(*)"end
local changemenu=MENU_GROUP_COMMAND:New(Group,text,unittop,self.SetRecceLaserCode,self,name,_code,true)
end
end
end
grouptable[GroupName]=true
end
end
end
else
if not self.NoMenus then
self.Menu=MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self)
end
end
return self
end
function AUTOLASE:_EventHandler(EventData)
self:SetPilotMenu()
return self
end
function AUTOLASE:SetMinThreatLevel(Level)
local level=Level or 0
if level<0 or level>10 then level=0 end
self.minthreatlevel=level
return self
end
function AUTOLASE:AddBlackListAttributes(Attributes)
local attributes=Attributes
if type(attributes)~="table"then
attributes={attributes}
end
for _,_attr in pairs(attributes)do
table.insert(self.blacklistattributes,_attr)
end
return self
end
function AUTOLASE:GetLaserCode(RecceName)
local code=1688
if self.RecceLaserCode[RecceName]==nil then
code=self.LaserCodes[math.random(#self.LaserCodes)]
self.RecceLaserCode[RecceName]=code
else
code=self.RecceLaserCode[RecceName]
end
return code
end
function AUTOLASE:GetSmokeColor(RecceName)
local color=self.smokecolor
if self.RecceSmokeColor[RecceName]==nil then
self.RecceSmokeColor[RecceName]=color
else
color=self.RecceSmokeColor[RecceName]
end
return color
end
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey)
if OnOff then
self.useSRS=true
self.SRSPath=Path or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRSFreq=Frequency or 271
self.SRSMod=Modulation or radio.modulation.AM
self.Gender=Gender or MSRS.gender or"male"
self.Culture=Culture or MSRS.culture or"en-US"
self.Port=Port or MSRS.port or 5002
self.Voice=Voice
self.PathToGoogleKey=PathToGoogleKey
self.Volume=Volume or 1.0
self.Label=Label
self.SRS=MSRS:New(self.SRSPath,self.SRSFreq,self.SRSMod)
self.SRS:SetCoalition(self.coalition)
self.SRS:SetLabel(self.MenuName or self.Name)
self.SRS:SetGender(self.Gender)
self.SRS:SetCulture(self.Culture)
self.SRS:SetPort(self.Port)
self.SRS:SetVoice(self.Voice)
self.SRS:SetCoalition(self.coalition)
self.SRS:SetVolume(self.Volume)
if self.PathToGoogleKey then
self.SRS:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
end
self.SRSQueue=MSRSQUEUE:New(self.alias)
else
self.useSRS=false
self.SRS=nil
self.SRSQueue=nil
end
return self
end
function AUTOLASE:SetMaxLasingTargets(Number)
self.maxlasing=Number or 4
return self
end
function AUTOLASE:SetNotifyPilots(OnOff)
self.notifypilots=OnOff and true
return self
end
function AUTOLASE:SetRecceLaserCode(RecceName,Code,Refresh)
local code=Code or 1688
self.RecceLaserCode[RecceName]=code
if Refresh then
self:SetPilotMenu()
if self.notifypilots then
if string.find(RecceName,"#")then
RecceName=string.match(RecceName,"^(.*)#")
end
self:NotifyPilots(string.format("Code for %s set to: %d",RecceName,Code),15)
end
end
return self
end
function AUTOLASE:SetRecceSmokeColor(RecceName,Color)
local color=Color or self.smokecolor
self.RecceSmokeColor[RecceName]=color
return self
end
function AUTOLASE:SetLaserCoolDown(OnOff,Seconds)
self.forcecooldown=OnOff and true
self.cooldowntime=Seconds or 60
return self
end
function AUTOLASE:SetReportingTimes(long,short)
self.reporttimeshort=short or 10
self.reporttimelong=long or 30
return self
end
function AUTOLASE:SetLasingParameters(Distance,Duration)
self.LaseDistance=Distance or 5000
self.LaseDuration=Duration or 300
return self
end
function AUTOLASE:SetSmokeTargets(OnOff,Color)
self.smoketargets=OnOff
self.smokecolor=Color or SMOKECOLOR.Red
local smktxt=OnOff==true and"on"or"off"
local Message="Smoking targets is now "..smktxt.."!"
self:NotifyPilots(Message,10)
return self
end
function AUTOLASE: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)
local lasedistance=self.LaseDistance
local unitheight=Unit:GetHeight()
local coord=Unit:GetCoordinate()
local landheight=coord:GetLandHeight()
local asl=unitheight-landheight
if asl>100 then
local absquare=lasedistance^2+asl^2
lasedistance=math.sqrt(absquare)
end
return lasedistance
end
function AUTOLASE:CleanCurrentLasing()
local lasingtable=self.CurrentLasing
local newtable={}
local newreccecount={}
local lasing=0
for _ind,_entry in pairs(lasingtable)do
local entry=_entry
if not newreccecount[entry.reccename]then
newreccecount[entry.reccename]=0
end
end
for _,_recce in pairs(self.RecceSet:GetSetObjects())do
local recce=_recce
if recce and recce:IsAlive()then
local unit=recce:GetUnit(1)
local name=unit:GetName()
if not self.RecceUnits[name]then
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
for _ind,_entry in pairs(lasingtable)do
local entry=_entry
local valid=0
local reccedead=false
local unitdead=false
local lostsight=false
local timeout=false
local Tnow=timer.getAbsTime()
local recce=entry.lasingunit
if recce and recce:IsAlive()then
valid=valid+1
else
reccedead=true
self:__RecceKIA(2,entry.reccename)
end
local unit=entry.lasedunit
if unit and unit:IsAlive()==true then
valid=valid+1
else
unitdead=true
if not self.deadunitnotes[entry.unitname]then
self.deadunitnotes[entry.unitname]=true
self:__TargetDestroyed(2,entry.unitname,entry.reccename)
end
end
if not reccedead and not unitdead then
if self:CanLase(recce,unit)then
valid=valid+1
else
lostsight=true
entry.laserspot:LaseOff()
self:__TargetLost(2,entry.unitname,entry.reccename)
end
end
local timestamp=entry.timestamp
if Tnow-timestamp<self.LaseDuration and not lostsight then
valid=valid+1
else
timeout=true
entry.laserspot:LaseOff()
self.RecceUnits[entry.reccename].cooldown=true
self.RecceUnits[entry.reccename].timestamp=timer.getAbsTime()
if not lostsight then
self:__LaserTimeout(2,entry.unitname,entry.reccename)
end
end
if valid==4 then
self.lasingindex=self.lasingindex+1
newtable[self.lasingindex]=entry
newreccecount[entry.reccename]=newreccecount[entry.reccename]+1
lasing=lasing+1
end
end
self.CurrentLasing=newtable
self.targetsperrecce=newreccecount
return lasing
end
function AUTOLASE:ShowStatus(Group,Unit)
local report=REPORT:New("Autolase")
local reccetable=self.RecceSet:GetSetObjects()
for _,_recce in pairs(reccetable)do
if _recce and _recce:IsAlive()then
local unit=_recce:GetUnit(1)
local name=unit:GetName()
if string.find(name,"#")then
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
local reccename=entry.reccename
if string.find(reccename,"#")then
reccename=string.match(reccename,"^(.*)#")
end
local typename=entry.unittype
local code=entry.lasercode
local locationstring=entry.location
local playername=nil
if Unit and Unit:IsAlive()then
playername=Unit:GetPlayerName()
elseif Group and Group:IsAlive()then
playername=Group:GetPlayerName()
end
if playername then
local settings=_DATABASE:GetPlayerSettings(playername)
if settings then
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
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)
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
if Unit and Unit:IsAlive()then
local m=MESSAGE:New(report:Text(),reporttime,"Info"):ToUnit(Unit)
elseif Group and Group:IsAlive()then
local m=MESSAGE:New(report:Text(),reporttime,"Info"):ToGroup(Group)
else
local m=MESSAGE:New(report:Text(),reporttime,"Info"):ToCoalition(self.coalition)
end
return self
end
function AUTOLASE:NotifyPilots(Message,Duration)
if self.usepilotset then
local pilotset=self.pilotset:GetSetObjects()
for _,_pilot in pairs(pilotset)do
local pilot=_pilot
if pilot and pilot:IsAlive()then
local Group=pilot:GetGroup()
local m=MESSAGE:New(Message,Duration,"Autolase"):ToGroup(Group)
end
end
elseif not self.debug then
local m=MESSAGE:New(Message,Duration,"Autolase"):ToCoalition(self.coalition)
else
local m=MESSAGE:New(Message,Duration,"Autolase"):ToAll()
end
if self.debug then self:I(Message)end
return self
end
function AUTOLASE:NotifyPilotsWithSRS(Message)
if self.useSRS then
self.SRSQueue:NewTransmission(Message,nil,self.SRS,nil,2)
end
if self.debug then self:I(Message)end
return self
end
function AUTOLASE:CheckIsLased(unitname)
local outcome=false
for _,_laserspot in pairs(self.CurrentLasing)do
local spot=_laserspot
if spot.unitname==unitname then
outcome=true
break
end
end
return outcome
end
function AUTOLASE:CanLase(Recce,Unit)
local function HasNoBlackListAttribute(Unit)
local nogos=self.blacklistattributes or{}
local having=true
local unit=Unit
for _,_attribute in pairs(nogos)do
if unit:HasAttribute(_attribute)then
having=false
break
end
end
return having
end
local canlase=false
if Recce and Recce:IsAlive()==true then
local name=Recce:GetName()
local cooldown=self.RecceUnits[name].cooldown and self.forcecooldown
if cooldown then
local Tdiff=timer.getAbsTime()-self.RecceUnits[name].timestamp
if Tdiff<self.cooldowntime then
return false
else
self.RecceUnits[name].cooldown=false
end
end
local reccecoord=Recce:GetCoordinate()
local unitcoord=Unit:GetCoordinate()
local islos=reccecoord:IsLOS(unitcoord,2.5)
local distance=math.floor(reccecoord:Get3DDistance(unitcoord))
local lasedistance=self:GetLosFromUnit(Recce)
if distance<=lasedistance and islos and HasNoBlackListAttribute(Unit)then
canlase=true
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 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
function AUTOLASE:onafterMonitor(From,Event,To)
self:T({From,Event,To})
local countlases=self:CleanCurrentLasing()
self:SetPilotMenu()
local detecteditems=self.Contacts or{}
local groupsbythreat={}
local report=REPORT:New("Detections")
local lines=0
for _,_contact in pairs(detecteditems)do
local contact=_contact
local grp=contact.group
local coord=contact.position
local reccename=contact.recce or"none"
local threat=contact.threatlevel or 0
local reccegrp=UNIT:FindByName(reccename)
if reccegrp then
local reccecoord=reccegrp:GetCoordinate()
local distance=math.floor(reccecoord:Get3DDistance(coord))
local text=string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute,contact.groupname,math.floor(distance/1000),contact.threatlevel)
report:Add(text)
self:T(text)
if self.debug then self:I(text)end
lines=lines+1
local lasedistance=self:GetLosFromUnit(reccegrp)
if grp:IsGround()and lasedistance>=distance and threat>=self.minthreatlevel then
table.insert(groupsbythreat,{contact.group,contact.threatlevel})
self.RecceNames[contact.groupname]=contact.recce
end
end
end
self.GroupsByThreat=groupsbythreat
if self.verbose>2 and lines>0 then
local m=MESSAGE:New(report:Text(),self.reporttimeshort,"Autolase"):ToAll()
end
table.sort(self.GroupsByThreat,function(a,b)
local aNum=a[2]
local bNum=b[2]
return aNum>bNum
end)
local unitsbythreat={}
for _,_entry in pairs(self.GroupsByThreat)do
local group=_entry[1]
if group and group:IsAlive()then
local units=group:GetUnits()
local reccename=self.RecceNames[group:GetName()]
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
local threat=unit:GetThreatLevel()
local coord=unit:GetCoordinate()
if threat>=self.minthreatlevel then
local unitname=unit:GetName()
if unit:HasAttribute("RADAR_BAND1_FOR_ARM")or unit:HasAttribute("RADAR_BAND2_FOR_ARM")or unit:HasAttribute("Optical Tracker")then
threat=11
end
table.insert(unitsbythreat,{unit,threat})
self.RecceUnitNames[unitname]=reccename
end
end
end
end
end
self.UnitsByThreat=unitsbythreat
table.sort(self.UnitsByThreat,function(a,b)
local aNum=a[2]
local bNum=b[2]
return aNum>bNum
end)
local unitreport=REPORT:New("Detected Units")
local lines=0
for _,_entry in pairs(self.UnitsByThreat)do
local threat=_entry[2]
local unit=_entry[1]
local unitname=unit:GetName()
local text=string.format("Unit %s | Threatlevel %d | Detected by %s",unitname,threat,self.RecceUnitNames[unitname])
unitreport:Add(text)
lines=lines+1
self:T(text)
if self.debug then self:I(text)end
end
if self.verbose>2 and lines>0 then
local m=MESSAGE:New(unitreport:Text(),self.reporttimeshort,"Autolase"):ToAll()
end
for _,_detectingunit in pairs(self.RecceUnits)do
local reccename=_detectingunit.name
local recce=_detectingunit.unit
local reccecount=self.targetsperrecce[reccename]or 0
local targets=0
for _,_entry in pairs(self.UnitsByThreat)do
local unit=_entry[1]
local unitname=unit:GetName()
local canlase=self:CanLase(recce,unit)
if targets+reccecount<self.maxlasing and not self:CheckIsLased(unitname)and unit:IsAlive()and canlase then
targets=targets+1
local code=self:GetLaserCode(reccename)
local spot=SPOT:New(recce)
spot:LaseOn(unit,code,self.LaseDuration)
local locationstring=unit:GetCoordinate():ToStringLLDDM()
if _SETTINGS:IsA2G_MGRS()then
local precision=_SETTINGS:GetMGRS_Accuracy()
local settings={}
settings.MGRS_Accuracy=precision
locationstring=unit:GetCoordinate():ToStringMGRS(settings)
elseif _SETTINGS:IsA2G_LL_DMS()then
locationstring=unit:GetCoordinate():ToStringLLDMS(_SETTINGS)
elseif _SETTINGS:IsA2G_BR()then
locationstring=unit:GetCoordinate():ToStringBULLS(self.coalition,_SETTINGS)
end
local laserspot={
laserspot=spot,
lasedunit=unit,
lasingunit=recce,
lasercode=code,
location=locationstring,
timestamp=timer.getAbsTime(),
unitname=unitname,
reccename=reccename,
unittype=unit:GetTypeName(),
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
self.lasingindex=self.lasingindex+1
self.CurrentLasing[self.lasingindex]=laserspot
self:__Lasing(2,laserspot)
end
end
end
local nextloop=-self.MonitorFrequency or-30
self:__Monitor(nextloop)
return self
end
function AUTOLASE:onbeforeRecceKIA(From,Event,To,RecceName)
self:T({From,Event,To,RecceName})
if self.notifypilots or self.debug then
if string.find(RecceName,"#")then
RecceName=string.match(RecceName,"^(.*)#")
end
local text=string.format("Recce %s KIA!",RecceName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
function AUTOLASE:onbeforeTargetDestroyed(From,Event,To,UnitName,RecceName)
self:T({From,Event,To,UnitName,RecceName})
if self.notifypilots or self.debug then
local text=string.format("Unit %s destroyed! Good job!",UnitName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
function AUTOLASE:onbeforeTargetLost(From,Event,To,UnitName,RecceName)
self:T({From,Event,To,UnitName,RecceName})
if self.notifypilots or self.debug then
if string.find(RecceName,"#")then
RecceName=string.match(RecceName,"^(.*)#")
end
local text=string.format("%s lost sight of unit %s.",RecceName,UnitName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
function AUTOLASE:onbeforeLaserTimeout(From,Event,To,UnitName,RecceName)
self:T({From,Event,To,UnitName,RecceName})
if self.notifypilots or self.debug then
if string.find(RecceName,"#")then
RecceName=string.match(RecceName,"^(.*)#")
end
local text=string.format("%s laser timeout on unit %s.",RecceName,UnitName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
function AUTOLASE:onbeforeLasing(From,Event,To,LaserSpot)
self:T({From,Event,To,LaserSpot.unittype})
if self.notifypilots or self.debug then
local laserspot=LaserSpot
local name=laserspot.reccename if string.find(name,"#")then
name=string.match(name,"^(.*)#")
end
local text=string.format("%s is lasing %s code %d\nat %s",name,laserspot.unittype,laserspot.lasercode,laserspot.location)
self:NotifyPilots(text,self.reporttimeshort+5)
end
return self
end
function AUTOLASE:onbeforeCancel(From,Event,To)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:__Stop(2)
return self
end
do
ZONE_GOAL_CARGO={
ClassName="ZONE_GOAL_CARGO",
}
ZONE_GOAL_CARGO.States={}
function ZONE_GOAL_CARGO:New(Zone,Coalition)
local self=BASE:Inherit(self,ZONE_GOAL:New(Zone))
self:F({Zone=Zone,Coalition=Coalition})
self:SetCoalition(Coalition)
do
end
do
end
do
end
do
end
self:AddTransition("*","Guard","Guarded")
self:AddTransition("*","Empty","Empty")
self:AddTransition({"Guarded","Empty"},"Attack","Attacked")
self:AddTransition({"Guarded","Attacked","Empty"},"Capture","Captured")
return self
end
function ZONE_GOAL_CARGO:SetCoalition(Coalition)
self.Coalition=Coalition
end
function ZONE_GOAL_CARGO:GetCoalition()
return self.Coalition
end
function ZONE_GOAL_CARGO:GetCoalitionName()
if self.Coalition==coalition.side.BLUE then
return"Blue"
end
if self.Coalition==coalition.side.RED then
return"Red"
end
if self.Coalition==coalition.side.NEUTRAL then
return"Neutral"
end
return""
end
function ZONE_GOAL_CARGO:IsGuarded()
local IsGuarded=self.Zone:IsAllInZoneOfCoalition(self.Coalition)
self:F({IsGuarded=IsGuarded})
return IsGuarded
end
function ZONE_GOAL_CARGO:IsEmpty()
local IsEmpty=self.Zone:IsNoneInZone()
self:F({IsEmpty=IsEmpty})
return IsEmpty
end
function ZONE_GOAL_CARGO:IsCaptured()
local IsCaptured=self.Zone:IsAllInZoneOfOtherCoalition(self.Coalition)
self:F({IsCaptured=IsCaptured})
return IsCaptured
end
function ZONE_GOAL_CARGO:IsAttacked()
local IsAttacked=self.Zone:IsSomeInZoneOfCoalition(self.Coalition)
self:F({IsAttacked=IsAttacked})
return IsAttacked
end
function ZONE_GOAL_CARGO:Mark()
local Coord=self.Zone:GetCoordinate()
local ZoneName=self:GetZoneName()
local State=self:GetState()
if self.MarkRed and self.MarkBlue then
self:F({MarkRed=self.MarkRed,MarkBlue=self.MarkBlue})
Coord:RemoveMark(self.MarkRed)
Coord:RemoveMark(self.MarkBlue)
end
if self.Coalition==coalition.side.BLUE then
self.MarkBlue=Coord:MarkToCoalitionBlue("Guard Zone: "..ZoneName.."\nStatus: "..State)
self.MarkRed=Coord:MarkToCoalitionRed("Capture Zone: "..ZoneName.."\nStatus: "..State)
else
self.MarkRed=Coord:MarkToCoalitionRed("Guard Zone: "..ZoneName.."\nStatus: "..State)
self.MarkBlue=Coord:MarkToCoalitionBlue("Capture Zone: "..ZoneName.."\nStatus: "..State)
end
end
function ZONE_GOAL_CARGO:onenterGuarded()
if self.Coalition==coalition.side.BLUE then
else
end
self:Mark()
end
function ZONE_GOAL_CARGO:onenterCaptured()
local NewCoalition=self.Zone:GetCoalition()
self:F({NewCoalition=NewCoalition})
self:SetCoalition(NewCoalition)
self:Mark()
end
function ZONE_GOAL_CARGO:onenterEmpty()
self:Mark()
end
function ZONE_GOAL_CARGO:onenterAttacked()
self:Mark()
end
function ZONE_GOAL_CARGO:onafterGuard()
if not self.SmokeScheduler then
self.SmokeScheduler=self:ScheduleRepeat(1,1,0.1,nil,self.StatusSmoke,self)
end
if not self.ScheduleStatusZone then
self.ScheduleStatusZone=self:ScheduleRepeat(15,15,0.1,nil,self.StatusZone,self)
end
end
function ZONE_GOAL_CARGO:IsCaptured()
local IsCaptured=self.Zone:IsAllInZoneOfOtherCoalition(self.Coalition)
self:F({IsCaptured=IsCaptured})
return IsCaptured
end
function ZONE_GOAL_CARGO:IsAttacked()
local IsAttacked=self.Zone:IsSomeInZoneOfCoalition(self.Coalition)
self:F({IsAttacked=IsAttacked})
return IsAttacked
end
function ZONE_GOAL_CARGO:StatusZone()
local State=self:GetState()
self:F({State=self:GetState()})
self.Zone:Scan()
if State~="Guarded"and self:IsGuarded()then
self:Guard()
end
if State~="Empty"and self:IsEmpty()then
self:Empty()
end
if State~="Attacked"and self:IsAttacked()then
self:Attack()
end
if State~="Captured"and self:IsCaptured()then
self:Capture()
end
end
end
TIRESIAS={
ClassName="TIRESIAS",
debug=false,
version="0.0.6",
Interval=20,
GroundSet=nil,
VehicleSet=nil,
AAASet=nil,
SAMSet=nil,
ExceptionSet=nil,
AAARange=60,
HeloSwitchRange=10,
PlaneSwitchRange=25,
SAMSwitchRange=75,
SwitchAAA=true,
SwitchSAM=false,
MantisHandlingSAM=true
}
function TIRESIAS:New()
local self=BASE:Inherit(self,FSM:New())
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self.lid=string.format("TIRESIAS %s | ",self.version)
self:I(self.lid.."Managing ground groups!")
self:__Start(1)
return self
end
function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles,SAMMiles)
self.HeloSwitchRange=HeloMiles or 10
self.PlaneSwitchRange=PlaneMiles or 25
self.SAMSwitchRange=SAMMiles or 75
return self
end
function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA)
self.AAARange=FiringRange or 60
self.SwitchAAA=(SwitchAAA==false)and false or true
return self
end
function TIRESIAS:SetSwitchSAM(OnOff,MantisPresent)
if OnOff==nil or OnOff==false then
self.SwitchSAM=false
else
self.SwitchSAM=true
end
self.MantisHandlingSAM=MantisPresent or true
return self
end
function TIRESIAS:AddExceptionSet(Set)
self:T(self.lid.."AddExceptionSet")
if not self.ExceptionSet then self.ExceptionSet=SET_GROUP:New():Clear(false)end
local exceptions=self.ExceptionSet
Set:ForEachGroupAlive(
function(grp)
if not grp.Tiresias then
grp.Tiresias={
type="Exception",
exception=true,
}
exceptions:AddGroup(grp,true)
end
BASE:T("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
return true
end
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
return true
end
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
return false
end
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
return false
end
end
function TIRESIAS:_InitGroups()
self:T(self.lid.."_InitGroups")
local EngageRange=self.AAARange
local SwitchAAA=self.SwitchAAA
local SwitchSAM=self.SwitchSAM
self.AAASet:ForEachGroupAlive(
function(grp)
if not grp.Tiresias 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==false then
grp:SetCommandInvisible(true)
grp.Tiresias.invisible=true
if SwitchAAA then
grp:SetAIOff()
grp:EnableEmission(false)
grp.Tiresias.AIOff=true
end
end
end
end
)
self.VehicleSet:ForEachGroupAlive(
function(grp)
if not grp.Tiresias 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==false then
grp:SetCommandInvisible(true)
grp:SetAIOff()
grp.Tiresias.invisible=true
grp.Tiresias.AIOff=true
end
end
end
)
self.SAMSet:ForEachGroupAlive(
function(grp)
if not grp.Tiresias then
grp:SetCommandInvisible(true)
grp.Tiresias={
type="SAM",
invisible=true,
exception=not SwitchSAM,
AIOff=SwitchSAM,
}
end
if grp.Tiresias and(not grp.Tiresias.exception==true)then
if grp.Tiresias and grp.Tiresias.invisible==false then
grp:SetCommandInvisible(true)
grp.Tiresias.invisible=true
grp:SetAIOnOff(SwitchSAM)
end
end
BASE:I(string.format("Init/Switch off SAM %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
end
)
return self
end
function TIRESIAS:_EventHandler(EventData)
self:T(string.format("%s Event = %d",self.lid,EventData.id))
local event=EventData
if event.id==EVENTS.PlayerEnterAircraft or event.id==EVENTS.PlayerEnterUnit then
local 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
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 samzone=ZONE_GROUP:New("ZoneSAM-"..group:GetName(),group,UTILS.NMToMeters(self.SAMSwitchRange))
local ground=SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
local sam=SET_GROUP:New():FilterFunction(self._FilterSAM):FilterZones({samzone}):FilterOnce()
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)
end
local SwitchAAA=self.SwitchAAA
local SwitchSAM=self.SwitchSAM
local MantisHandling=self.MantisHandlingSAM
if ground:CountAlive()>0 then
ground:ForEachGroupAlive(
function(grp)
local name=grp:GetName()
if grp:GetCoalition()~=group:GetCoalition()
and grp.Tiresias and grp.Tiresias.type and(not grp.Tiresias.exception==true)then
if grp.Tiresias.invisible==true then
grp:SetCommandInvisible(false)
grp.Tiresias.invisible=false
end
if grp.Tiresias.type=="Vehicle"and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then
grp:SetAIOn()
grp.Tiresias.AIOff=false
end
if(SwitchAAA)and(grp.Tiresias.type=="AAA")and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then
grp:SetAIOn()
grp:EnableEmission(true)
grp.Tiresias.AIOff=false
end
BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception)))
else
BASE:T("TIRESIAS - This group "..tostring(name).." has not been initialized or is an exception!")
end
end
)
end
if sam:CountAlive()>0 and self.SwitchSAM==true then
sam:ForEachGroupAlive(
function(grp)
local name=grp:GetName()
BASE:I(string.format("%s - %s",name,tostring(SwitchSAM)))
UTILS.PrintTableToLog(grp.Tiresias)
if SwitchSAM and grp.Tiresias.type=="SAM"and grp.Tiresias.AIOff and grp.Tiresias.AIOff==true then
BASE:I("First check passed")
if grp.Tiresias.exception~=true then
BASE:I("Second check passed")
grp:SetAIOn()
if not MantisHandling then
grp:EnableEmission(true)
end
grp.Tiresias.AIOff=false
BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception)))
end
end
end
)
end
return self
end
function TIRESIAS:onafterStart(From,Event,To)
self:T({From,Event,To})
local VehicleSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart()
local AAASet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart()
local SAMSet=SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart()
local OpsGroupSet=SET_OPSGROUP:New():FilterActive(true):FilterStart()
self.FlightSet=SET_GROUP:New():FilterCategories({"plane","helicopter"}):FilterStart()
local EngageRange=self.AAARange
local ExceptionSet=self.ExceptionSet
if self.ExceptionSet then
function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object)
BASE:I("TIRESIAS: EXCEPTION Object Added: "..Object:GetName())
if Object and Object:IsAlive()then
Object.Tiresias={
type="Exception",
exception=true,
}
Object:SetAIOn()
Object:SetCommandInvisible(false)
Object:EnableEmission(true)
end
end
local OGS=OpsGroupSet:GetAliveSet()
for _,_OG in pairs(OGS or{})do
local OG=_OG
local grp=OG:GetGroup()
ExceptionSet:AddGroup(grp,true)
end
function OpsGroupSet:OnAfterAdded(From,Event,To,ObjectName,Object)
local grp=Object:GetGroup()
ExceptionSet:AddGroup(grp,true)
end
end
function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object)
BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName())
if Object and Object:IsAlive()then
Object:SetAIOff()
Object:SetCommandInvisible(true)
Object.Tiresias={
type="Vehicle",
invisible=true,
AIOff=true,
exception=false,
}
end
end
local SwitchAAA=self.SwitchAAA
function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive()then
BASE:I("TIRESIAS: AAA Object Added: "..Object:GetName())
Object:OptionEngageRange(EngageRange)
Object:SetCommandInvisible(true)
if SwitchAAA then
Object:SetAIOff()
Object:EnableEmission(false)
end
Object.Tiresias={
type="AAA",
invisible=true,
range=EngageRange,
exception=false,
AIOff=SwitchAAA,
}
end
end
local SwitchSAM=self.SwitchSAM
local MantisManages=self.MantisHandlingSAM
function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive()then
BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName())
Object:SetCommandInvisible(true)
if SwitchSAM then
Object:SetAIOff()
Object:EnableEmission(not MantisManages)
end
Object.Tiresias={
type="SAM",
invisible=true,
exception=not SwitchSAM,
AIOff=SwitchSAM,
}
end
end
self.VehicleSet=VehicleSet
self.AAASet=AAASet
self.SAMSet=SAMSet
self.OpsGroupSet=OpsGroupSet
self:_InitGroups()
self:__Status(1)
return self
end
function TIRESIAS:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
if self:GetState()=="Stopped"then
return false
end
return self
end
function TIRESIAS:onafterStatus(From,Event,To)
self:T({From,Event,To})
if self.debug then
local count=self.VehicleSet:CountAlive()
local AAAcount=self.AAASet:CountAlive()
local SAMcount=self.SAMSet:CountAlive()
local text=string.format("Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",count+AAAcount+SAMcount,count,AAAcount,SAMcount)
self:I(text)
end
self:_InitGroups()
if self.FlightSet:CountAlive()>0 then
local Set=self.FlightSet:GetAliveSet()
for _,_plane in pairs(Set)do
local plane=_plane
local radius=self.PlaneSwitchRange
if plane:IsHelicopter()then
radius=self.HeloSwitchRange
end
self:_SwitchOnGroups(_plane,radius)
end
end
if self:GetState()~="Stopped"then
self:__Status(self.Interval)
end
return self
end
function TIRESIAS:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
return self
end
STRATEGO={
ClassName="STRATEGO",
debug=false,
drawzone=false,
markzone=false,
version="0.3.1",
portweight=3,
POIweight=1,
maxrunways=3,
coalition=nil,
colors=nil,
airbasetable={},
nonconnectedab={},
easynames={},
maxdist=150,
disttable={},
routexists={},
routefactor=5,
OpsZones={},
NeutralBenefit=100,
Budget=0,
usebudget=false,
CaptureUnits=3,
CaptureThreatlevel=1,
CaptureObjectCategories={Object.Category.UNIT},
ExcludeShips=true,
}
STRATEGO.Type={
AIRBASE="AIRBASE",
PORT="PORT",
POI="POI",
FARP="FARP",
SHIP="SHIP",
}
function STRATEGO:New(Name,Coalition,MaxDist)
local self=BASE:Inherit(self,FSM:New())
self.coalition=Coalition
self.coalitiontext=UTILS.GetCoalitionName(Coalition)
self.name=Name or"Hannibal"
self.maxdist=MaxDist or 150
self.disttable={}
self.routexists={}
self.ExcludeShips=true
self.lid=string.format("STRATEGO %s %s | ",self.name,self.version)
self.bases=SET_AIRBASE:New():FilterOnce()
self.ports=SET_ZONE:New():FilterPrefixes("Port"):FilterOnce()
self.POIs=SET_ZONE:New():FilterPrefixes("POI"):FilterOnce()
self.colors={
[1]={0,1,0},
[2]={1,0,0},
[3]={0,0,1},
[4]={1,0.65,0},
}
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Update","*")
self:AddTransition("*","NodeEvent","*")
self:AddTransition("Running","Stop","Stopped")
return self
end
function STRATEGO:onafterStart(From,Event,To)
self:T(self.lid.."Start")
self:AnalyseBases()
self:AnalysePOIs(self.ports,self.portweight,"PORT")
self:AnalysePOIs(self.POIs,self.POIweight,"POI")
for i=self.maxrunways,1,-1 do
self:AnalyseRoutes(i,i*self.routefactor,self.colors[(i%3)+1],i)
end
self:AnalyseUnconnected(self.colors[4])
self:I(self.lid.."Advisory ready.")
self:__Update(180)
return self
end
function STRATEGO:onafterUpdate(From,Event,To)
self:T(self.lid.."Update")
self:UpdateNodeCoalitions()
if self:GetState()=="Running"then
self:__Update(180)
end
return self
end
function STRATEGO:SetUsingBudget(Usebudget,StartBudget)
self:T(self.lid.."SetUsingBudget")
self.usebudget=Usebudget
self.Budget=StartBudget
return self
end
function STRATEGO:SetDebug(Debug,DrawZones,MarkZones)
self:T(self.lid.."SetDebug")
self.debug=Debug
self.drawzone=DrawZones
self.markzone=MarkZones
return self
end
function STRATEGO:SetStrategoZone(Zone)
self.StrategoZone=Zone
return self
end
function STRATEGO:SetWeights(MaxRunways,PortWeight,POIWeight,RouteFactor)
self:T(self.lid.."SetWeights")
self.portweight=PortWeight or 3
self.POIweight=POIWeight or 1
self.maxrunways=MaxRunways or 3
self.routefactor=RouteFactor or 5
return self
end
function STRATEGO:SetNeutralBenefit(NeutralBenefit)
self:T(self.lid.."SetNeutralBenefit")
self.NeutralBenefit=NeutralBenefit or 100
return self
end
function STRATEGO:SetCaptureOptions(CaptureUnits,CaptureThreatlevel,CaptureCategories)
self:T(self.lid.."SetCaptureOptions")
self.CaptureUnits=CaptureUnits or 3
self.CaptureThreatlevel=CaptureThreatlevel or 1
self.CaptureObjectCategories=CaptureCategories or{Object.Category.UNIT}
return self
end
function STRATEGO:AnalyseBases()
self:T(self.lid.."AnalyseBases")
local colors=self.colors
local debug=self.debug
local airbasetable=self.airbasetable
local nonconnectedab=self.nonconnectedab
local easynames=self.easynames
local zone=self.StrategoZone
self.bases:ForEach(
function(afb)
local ab=afb
local abvec2=ab:GetVec2()
if self.ExcludeShips and ab:IsShip()then return end
if zone~=nil then
if not zone:IsVec2InZone(abvec2)then
return
end
end
local abname=ab:GetName()
local runways=ab:GetRunways()
local numrwys=#runways
if numrwys>=1 then numrwys=numrwys*0.5 end
local abzone=ab:GetZone()
if not abzone then
abzone=ZONE_RADIUS:New(abname,ab:GetVec2(),500)
end
local coa=ab:GetCoalition()
if coa==nil then return end
coa=coa+1
local abtype=STRATEGO.Type.AIRBASE
if ab:IsShip()then
numrwys=1
abtype=STRATEGO.Type.SHIP
end
if ab:IsHelipad()then
numrwys=1
abtype=STRATEGO.Type.FARP
end
local coord=ab:GetCoordinate()
if debug then
abzone:DrawZone(-1,colors[coa],1,colors[coa],0.3,1)
coord:TextToAll(tostring(numrwys),-1,{0,0,0},1,colors[coa],0.3,20)
end
local opszone=self:GetNewOpsZone(abname,coa-1)
local tbl={
name=abname,
baseweight=numrwys,
weight=0,
coalition=coa-1,
port=false,
zone=abzone,
coord=coord,
type=abtype,
opszone=opszone,
connections=0,
}
airbasetable[abname]=tbl
nonconnectedab[abname]=true
local name=string.gsub(abname,"[%p%s]",".")
easynames[name]=abname
end
)
return self
end
function STRATEGO:UpdateNodeCoalitions()
self:T(self.lid.."UpdateNodeCoalitions")
local newtable={}
for _id,_data in pairs(self.airbasetable)do
local data=_data
if data.type==STRATEGO.Type.AIRBASE or data.type==STRATEGO.Type.FARP or data.type==STRATEGO.Type.SHIP then
data.coalition=AIRBASE:FindByName(data.name):GetCoalition()or 0
else
data.coalition=data.opszone:GetOwner()or 0
end
newtable[_id]=_data
end
self.airbasetable=nil
self.airbasetable=newtable
return self
end
function STRATEGO:GetNewOpsZone(Zone,Coalition)
self:T(self.lid.."GetNewOpsZone")
local opszone=OPSZONE:New(Zone,Coalition or 0)
opszone:SetCaptureNunits(self.CaptureUnits)
opszone:SetCaptureThreatlevel(self.CaptureThreatlevel)
opszone:SetObjectCategories(self.CaptureObjectCategories)
opszone:SetDrawZone(self.drawzone)
opszone:SetMarkZone(self.markzone)
opszone:Start()
local function Captured(opszone,coalition)
self:__NodeEvent(1,opszone,coalition)
end
function opszone:OnBeforeCaptured(From,Event,To,Coalition)
Captured(opszone,Coalition)
end
return opszone
end
function STRATEGO:AnalysePOIs(Set,Weight,Key)
self:T(self.lid.."AnalysePOIs")
local colors=self.colors
local debug=self.debug
local airbasetable=self.airbasetable
local nonconnectedab=self.nonconnectedab
local easynames=self.easynames
Set:ForEach(
function(port)
local zone=port
local zname=zone:GetName()
local coord=zone:GetCoordinate()
if debug then
zone:DrawZone(-1,colors[1],1,colors[1],0.3,1)
coord:TextToAll(tostring(Weight),-1,{0,0,0},1,colors[1],0.3,20)
end
local opszone=self:GetNewOpsZone(zone)
local tbl={
name=zname,
baseweight=Weight,
weight=0,
coalition=coalition.side.NEUTRAL,
port=true,
zone=zone,
coord=coord,
type=Key,
opszone=opszone,
connections=0,
}
airbasetable[zname]=tbl
nonconnectedab[zname]=true
local name=string.gsub(zname,"[%p%s]",".")
easynames[name]=zname
end
)
return self
end
function STRATEGO:GetToFrom(StartPoint,EndPoint)
self:T(self.lid.."GetToFrom "..tostring(StartPoint).." "..tostring(EndPoint))
local pstart=string.gsub(StartPoint,"[%p%s]",".")
local pend=string.gsub(EndPoint,"[%p%s]",".")
local fromto=pstart..";"..pend
local tofrom=pend..";"..pstart
return fromto,tofrom
end
function STRATEGO:GetRoutesFromNode(StartPoint)
self:T(self.lid.."GetRoutesFromNode")
local pstart=string.gsub(StartPoint,"[%p%s]",".")
local found=false
pstart=pstart..";"
local routes={}
local listed={}
for _,_data in pairs(self.routexists)do
if string.find(_data,pstart,1,true)and not listed[_data]then
local target=string.gsub(_data,pstart,"")
local fname=self.easynames[target]
table.insert(routes,fname)
found=true
listed[_data]=true
end
end
return found,routes
end
function STRATEGO:AddRoutesManually(Startpoint,Endpoint,Color,Linetype,Draw)
self:T(self.lid.."AddRoutesManually")
local fromto,tofrom=self:GetToFrom(Startpoint,Endpoint)
local startcoordinate=self.airbasetable[Startpoint].coord
local targetcoordinate=self.airbasetable[Endpoint].coord
local dist=UTILS.Round(targetcoordinate:Get2DDistance(startcoordinate),-2)/1000
local color=Color or{136/255,0,1}
local linetype=Linetype or 5
local data={
start=Startpoint,
target=Endpoint,
dist=dist,
}
self.disttable[fromto]=data
self.disttable[tofrom]=data
table.insert(self.routexists,fromto)
table.insert(self.routexists,tofrom)
self.nonconnectedab[Endpoint]=false
self.nonconnectedab[Startpoint]=false
local factor=self.airbasetable[Startpoint].baseweight*self.routefactor
self.airbasetable[Startpoint].weight=self.airbasetable[Startpoint].weight+factor
self.airbasetable[Endpoint].weight=self.airbasetable[Endpoint].weight+factor
self.airbasetable[Endpoint].connections=self.airbasetable[Endpoint].connections+2
self.airbasetable[Startpoint].connections=self.airbasetable[Startpoint].connections+2
if self.debug or Draw then
startcoordinate:LineToAll(targetcoordinate,-1,color,1,linetype,nil,string.format("%dkm",dist))
end
return self
end
function STRATEGO:AnalyseRoutes(tgtrwys,factor,color,linetype)
self:T(self.lid.."AnalyseRoutes")
for _,_ab in pairs(self.airbasetable)do
if _ab.baseweight>=1 then
local startpoint=_ab.name
local startcoord=_ab.coord
for _,_data in pairs(self.airbasetable)do
local fromto,tofrom=self:GetToFrom(startpoint,_data.name)
if _data.name==startpoint then
elseif _data.baseweight==tgtrwys and not(self.routexists[fromto]or self.routexists[tofrom])then
local tgtc=_data.coord
local dist=UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000
if dist<=self.maxdist then
local data={
start=startpoint,
target=_data.name,
dist=dist,
}
self.disttable[fromto]=data
self.disttable[tofrom]=data
table.insert(self.routexists,fromto)
table.insert(self.routexists,tofrom)
self.nonconnectedab[_data.name]=false
self.nonconnectedab[startpoint]=false
self.airbasetable[startpoint].weight=self.airbasetable[startpoint].weight+factor
self.airbasetable[_data.name].weight=self.airbasetable[_data.name].weight+factor
self.airbasetable[startpoint].connections=self.airbasetable[startpoint].connections+1
self.airbasetable[_data.name].connections=self.airbasetable[_data.name].connections+1
if self.debug then
startcoord:LineToAll(tgtc,-1,color,1,linetype,nil,string.format("%dkm",dist))
end
end
end
end
end
end
return self
end
function STRATEGO:AnalyseUnconnected(Color)
self:T(self.lid.."AnalyseUnconnected")
for _name,_noconnect in pairs(self.nonconnectedab)do
if _noconnect then
local startpoint=_name
local startcoord=self.airbasetable[_name].coord
local shortest=1000*1000
local closest=nil
local closestcoord=nil
for _,_data in pairs(self.airbasetable)do
if _name~=_data.name then
local tgtc=_data.coord
local dist=UTILS.Round(tgtc:Get2DDistance(startcoord),-2)/1000
if dist<shortest and self.nonconnectedab[_data.name]==false then
shortest=dist
closest=_data.name
closestcoord=tgtc
end
end
end
if closest then
if self.debug then
startcoord:LineToAll(closestcoord,-1,Color,1,3,nil,string.format("%dkm",shortest))
end
self.airbasetable[startpoint].weight=self.airbasetable[startpoint].weight+1
self.airbasetable[closest].weight=self.airbasetable[closest].weight+1
self.airbasetable[startpoint].connections=self.airbasetable[startpoint].connections+2
self.airbasetable[closest].connections=self.airbasetable[closest].connections+2
local data={
start=startpoint,
target=closest,
dist=shortest,
}
local fromto,tofrom=self:GetToFrom(startpoint,closest)
self.disttable[fromto]=data
self.disttable[tofrom]=data
table.insert(self.routexists,fromto)
table.insert(self.routexists,tofrom)
end
end
end
return self
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
if Coalition then
if _data.coalition~=Coalition then
okay=false
end
end
if _data.weight>=weight and okay then
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
end
return airbases[weight],weight,highest,highname
end
function STRATEGO:GetNextHighestWeightNodes(Weight,Coalition)
self:T(self.lid.."GetNextHighestWeightNodes")
local weight=0
local airbases={}
for _name,_data in pairs(self.airbasetable)do
local okay=true
if Coalition then
if _data.coalition~=Coalition then
okay=false
end
end
if _data.weight>=weight and _data.weight<Weight and okay then
weight=_data.weight
if not airbases[weight]then airbases[weight]={}end
table.insert(airbases[weight],_name)
end
end
return airbases[weight],weight
end
function STRATEGO:SetNodeWeight(Name,Weight)
self:T(self.lid.."SetNodeWeight")
if Name and Weight and self.airbasetable[Name]then
self.airbasetable[Name].weight=Weight or 0
return true
else
return false
end
end
function STRATEGO:SetNodeBaseWeight(Name,Weight)
self:T(self.lid.."SetNodeBaseWeight")
if Name and Weight and self.airbasetable[Name]then
self.airbasetable[Name].baseweight=Weight or 0
return true
else
return false
end
end
function STRATEGO:GetNodeWeight(Name)
self:T(self.lid.."GetNodeWeight")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].weight or 0
else
return 0
end
end
function STRATEGO:GetNodeBaseWeight(Name)
self:T(self.lid.."GetNodeBaseWeight")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].baseweight or 0
else
return 0
end
end
function STRATEGO:GetNodeCoalition(Name)
self:T(self.lid.."GetNodeCoalition")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].coalition or coalition.side.NEUTRAL
else
return coalition.side.NEUTRAL
end
end
function STRATEGO:GetNodeType(Name)
self:T(self.lid.."GetNodeType")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].type
else
return nil
end
end
function STRATEGO:GetNodeZone(Name)
self:T(self.lid.."GetNodeZone")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].zone
else
return nil
end
end
function STRATEGO:GetNodeOpsZone(Name)
self:T(self.lid.."GetNodeOpsZone")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].opszone
else
return nil
end
end
function STRATEGO:GetNodeCoordinate(Name)
self:T(self.lid.."GetNodeCoordinate")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].coord
else
return nil
end
end
function STRATEGO:IsAirbase(Name)
self:T(self.lid.."IsAirbase")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].type==STRATEGO.Type.AIRBASE
else
return false
end
end
function STRATEGO:IsPort(Name)
self:T(self.lid.."IsPort")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].type==STRATEGO.Type.PORT
else
return false
end
end
function STRATEGO:IsPOI(Name)
self:T(self.lid.."IsPOI")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].type==STRATEGO.Type.POI
else
return false
end
end
function STRATEGO:IsFARP(Name)
self:T(self.lid.."IsFARP")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].type==STRATEGO.Type.FARP
else
return false
end
end
function STRATEGO:IsShip(Name)
self:T(self.lid.."IsShip")
if Name and self.airbasetable[Name]then
return self.airbasetable[Name].type==STRATEGO.Type.SHIP
else
return false
end
end
function STRATEGO:FindClosestConsolidationTarget(Startpoint,BaseWeight)
self:T(self.lid.."FindClosestConsolidationTarget for "..Startpoint.." Weight "..BaseWeight or 0)
local shortest=1000*1000
local target=nil
local weight=0
local coa=nil
if not BaseWeight then BaseWeight=self.maxrunways-1 end
local startpoint=string.gsub(Startpoint,"[%p%s]",".")
for _,_route in pairs(self.routexists)do
if string.find(_route,startpoint,1,true)then
local dist=self.disttable[_route].dist
local tname=string.gsub(_route,startpoint,"")
local tname=string.gsub(tname,";","")
local cname=self.easynames[tname]
local targetweight=self.airbasetable[cname].baseweight
coa=self.airbasetable[cname].coalition
if(dist<shortest)and(coa~=self.coalition)and(BaseWeight>=targetweight)then
self:T("Found Consolidation Target: "..cname)
shortest=dist
target=cname
weight=self.airbasetable[cname].weight
coa=coa
end
end
end
return shortest,target,weight,coa
end
function STRATEGO:FindClosestStrategicTarget(Startpoint,Weight)
self:T(self.lid.."FindClosestStrategicTarget for "..Startpoint.." Weight "..Weight or 0)
local shortest=1000*1000
local target=nil
local weight=0
local coa=nil
if not Weight then Weight=self.maxrunways end
local startpoint=string.gsub(Startpoint,"[%p%s]",".")
for _,_route in pairs(self.routexists)do
if string.find(_route,startpoint,1,true)then
local dist=self.disttable[_route].dist
local tname=string.gsub(_route,startpoint,"")
local tname=string.gsub(tname,";","")
local cname=self.easynames[tname]
local coa=self.airbasetable[cname].coalition
local tweight=self.airbasetable[cname].baseweight
local ttweight=self.airbasetable[cname].weight
if(dist<shortest)and(coa~=self.coalition)and(tweight>=Weight)then
self:T("Found Strategic Target: "..cname)
shortest=dist
target=cname
weight=self.airbasetable[cname].weight
coa=self.airbasetable[cname].coalition
end
end
end
return shortest,target,weight,coa
end
function STRATEGO:FindStrategicTargets()
self:T(self.lid.."FindStrategicTargets")
local targets={}
for _,_data in pairs(self.airbasetable)do
local data=_data
if data.coalition==self.coalition then
local dist,name,points,coa=self:FindClosestStrategicTarget(data.name,data.weight)
if points>0 then
self:T({dist=dist,name=name,points=points,coa=coa})
end
if points~=0 then
local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
self:T("Enemycoa = "..enemycoa)
if coa==coalition.side.NEUTRAL then
local tdata={}
tdata.name=name
tdata.dist=dist
tdata.points=points+self.NeutralBenefit
tdata.coalition=coa
tdata.coalitionname=UTILS.GetCoalitionName(coa)
tdata.coordinate=self.airbasetable[name].coord
table.insert(targets,tdata)
else
local tdata={}
tdata.name=name
tdata.dist=dist
tdata.points=points
tdata.coalition=coa
tdata.coalitionname=UTILS.GetCoalitionName(coa)
tdata.coordinate=self.airbasetable[name].coord
table.insert(targets,tdata)
end
end
end
end
return targets
end
function STRATEGO:FindConsolidationTargets()
self:T(self.lid.."FindConsolidationTargets")
local targets={}
for _,_data in pairs(self.airbasetable)do
local data=_data
if data.coalition==self.coalition then
local dist,name,points,coa=self:FindClosestConsolidationTarget(data.name,self.maxrunways-1)
if points>0 then
self:T({dist=dist,name=name,points=points,coa=coa})
end
if points~=0 then
local enemycoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
self:T("Enemycoa = "..enemycoa)
if coa==coalition.side.NEUTRAL then
local tdata={}
tdata.name=name
tdata.dist=dist
tdata.points=points+self.NeutralBenefit
tdata.coalition=coa
tdata.coalitionname=UTILS.GetCoalitionName(coa)
tdata.coordinate=self.airbasetable[name].coord
table.insert(targets,tdata)
else
local tdata={}
tdata.name=name
tdata.dist=dist
tdata.points=points
tdata.coalition=coa
tdata.coalitionname=UTILS.GetCoalitionName(coa)
tdata.coordinate=self.airbasetable[name].coord
table.insert(targets,tdata)
end
end
end
end
return targets
end
function STRATEGO:FindNeighborNodes(Name,Enemies,Friends)
self:T(self.lid.."FindNeighborNodes")
local neighbors={}
local name=string.gsub(Name,"[%p%s]",".")
local shortestdist=1000*1000
local nearest=nil
for _route,_data in pairs(self.disttable)do
if string.find(_route,name,1,true)then
local dist=self.disttable[_route]
local tname=string.gsub(_route,name,"")
local tname=string.gsub(tname,";","")
local cname=self.easynames[tname]
if cname then
local encoa=self.coalition==coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
if Enemies==true then
if self.airbasetable[cname].coalition==encoa then
neighbors[cname]=dist
end
elseif Friends==true then
if self.airbasetable[cname].coalition~=encoa then
neighbors[cname]=dist
end
else
neighbors[cname]=dist
end
if neighbors[cname]and dist.dist<shortestdist then
shortestdist=dist.dist
nearest=cname
end
end
end
end
return neighbors,nearest,shortestdist
end
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(dist<closedist)then
closedist=dist
closest=_name
end
end
end
return closest
end
function STRATEGO:FindRoute(Start,End,Hops,Draw,Color,LineType,NoOptimize)
self:T(self.lid.."FindRoute")
local Route={}
local InRoute={}
local hops=Hops or 4
local routecomplete=false
local reverse=false
local function Checker(neighbors)
for _name,_data in pairs(neighbors)do
if _name==End then
return End
end
end
return nil
end
local function DrawRoute(Route)
for i=1,#Route-1 do
local p1=Route[i]
local p2=Route[i+1]
local c1=self.airbasetable[p1].coord
local c2=self.airbasetable[p2].coord
local line=LineType or 6
local color=Color or{0,0,0}
c1:LineToAll(c2,-1,color,1,line)
end
end
Route[#Route+1]=Start
InRoute[Start]=true
local nodes=self:FindNeighborNodes(Start)
local endpoint=Checker(nodes)
if endpoint then
Route[#Route+1]=endpoint
routecomplete=true
else
local spoint=Start
for i=1,hops do
local Next=self:_GetNextClosest(spoint,End,InRoute)
if Next~=nil then
Route[#Route+1]=Next
InRoute[Next]=true
local nodes=self:FindNeighborNodes(Next)
local endpoint=Checker(nodes)
if endpoint then
Route[#Route+1]=endpoint
routecomplete=true
break
else
spoint=Next
end
else
break
end
end
end
local function OptimizeRoute(Route)
local foundcut=false
local largestcut=0
local cut={}
for i=1,#Route do
local found,nodes=self:GetRoutesFromNode(Route[i])
for _,_name in pairs(nodes or{})do
for j=i+2,#Route do
if _name==Route[j]then
if j-i>largestcut then
largestcut=j-i
cut={i=i,j=j}
foundcut=true
end
end
end
end
end
if foundcut then
local newroute={}
for i=1,#Route do
if i<=cut.i or i>=cut.j then
table.insert(newroute,Route[i])
end
end
return newroute
end
return Route,foundcut
end
if routecomplete==true and NoOptimize~=true then
local foundcut=true
while foundcut~=false do
Route,foundcut=OptimizeRoute(Route)
end
else
Route,routecomplete=self:FindRoute(End,Start,Hops,Draw,Color,LineType)
reverse=true
end
if(self.debug or Draw)then DrawRoute(Route)end
return Route,routecomplete,reverse
end
function STRATEGO:AddBudget(Number)
self:T(self.lid.."AddBudget")
self.Budget=self.Budget+Number
return self
end
function STRATEGO:SubtractBudget(Number)
self:T(self.lid.."SubtractBudget")
self.Budget=self.Budget-Number
return self
end
function STRATEGO:GetBudget()
self:T(self.lid.."GetBudget")
return self.Budget
end
function STRATEGO:FindAffordableStrategicTarget()
self:T(self.lid.."FindAffordableStrategicTarget")
local Stargets=self:FindStrategicTargets()
local budget=self.Budget
local ftarget=nil
local Targets={}
for _,_data in pairs(Stargets)do
local data=_data
self:T("Considering Strategic Target "..data.name)
if data.points<=budget then
table.insert(Targets,data)
self:T(self.lid.."Affordable strategic target: "..data.name)
end
end
if#Targets==0 then
self:T(self.lid.."No suitable target found!")
return nil
end
if#Targets>1 then
ftarget=Targets[math.random(1,#Targets)]
else
ftarget=Targets[1]
end
if ftarget then
self:T(self.lid.."Final affordable strategic target: "..ftarget.name)
return ftarget
else
return nil
end
end
function STRATEGO:FindAffordableConsolidationTarget()
self:T(self.lid.."FindAffordableConsolidationTarget")
local Ctargets=self:FindConsolidationTargets()
local budget=self.Budget
local ftarget=nil
local Targets={}
for _,_data in pairs(Ctargets)do
local data=_data
self:T("Considering Consolidation Target "..data.name)
if data.points<=budget then
table.insert(Targets,data)
self:T(self.lid.."Affordable consolidation target: "..data.name)
end
end
if#Targets==0 then
self:T(self.lid.."No suitable target found!")
return nil
end
if#Targets>1 then
ftarget=Targets[math.random(1,#Targets)]
else
ftarget=Targets[1]
end
if ftarget then
self:T(self.lid.."Final affordable consolidation target: "..ftarget.name)
return ftarget
else
return nil
end
end
function STRATEGO:_FloodNext(next,filled,unfilled)
local start=self:FindNeighborNodes(next)
for _name,_ in pairs(start)do
if filled[_name]~=true then
self:T("Flooding ".._name)
filled[_name]=true
unfilled[_name]=nil
self:_FloodNext(_name,filled,unfilled)
end
end
return self
end
function STRATEGO:_FloodFill(Start,ABTable)
self:T("Start = "..tostring(Start))
if Start==nil then return end
local filled={}
local unfilled={}
if ABTable then
unfilled=ABTable
else
for _name,_ in pairs(self.airbasetable)do
unfilled[_name]=true
end
end
filled[Start]=true
unfilled[Start]=nil
local start=self:FindNeighborNodes(Start)
for _name,_ in pairs(start)do
if filled[_name]~=true then
self:T("Flooding ".._name)
filled[_name]=true
unfilled[_name]=nil
self:_FloodNext(_name,filled,unfilled)
end
end
return filled,unfilled
end
function STRATEGO:_FloodTest(connect,draw)
local function GetElastic(bases)
local vec2table={}
for _name,_ in pairs(bases)do
local coord=self.airbasetable[_name].coord
local vec2=coord:GetVec2()
table.insert(vec2table,vec2)
end
local zone=ZONE_ELASTIC:New("STRATEGO-Floodtest-"..math.random(1,10000),vec2table)
return zone
end
local function DrawElastic(filled,drawit)
local zone=GetElastic(filled)
if drawit then
zone:SetColor({1,1,1},1)
zone:SetDrawCoalition(-1)
zone:Update(1,true)
end
return zone
end
local _,_,weight,name=self:GetHighestWeightNodes()
local filled,unfilled=self:_FloodFill(name)
local allin=true
if table.length(unfilled)>0 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<mindist then
mindist=dist
corner1=_edge
corner2=_edge2
found=true
end
end
end
if found then
local Corner=COORDINATE:NewFromVec2(corner1)
local Corner2=COORDINATE:NewFromVec2(corner2)
Corner:LineToAll(Corner2,-1,{1,1,1},1,1,true,"Island2Island")
local cornername
local cornername2
for _name,_data in pairs(self.airbasetable)do
local zone=_data.zone
if zone:IsVec2InZone(corner1)then
cornername=_name
self:T("Corner1 = ".._name)
end
if zone:IsVec2InZone(corner2)then
cornername2=_name
self:T("Corner2 = ".._name)
end
if cornername and cornername2 and connect==true then
self:AddRoutesManually(cornername,cornername2,Color,Linetype,self.debug)
end
end
end
end
end
return allin,filled,unfilled
end
CLIENTWATCH={}
CLIENTWATCH.ClassName="CLIENTWATCH"
CLIENTWATCH.Debug=false
CLIENTWATCH.DebugEventData=false
CLIENTWATCH.lid=nil
CLIENTWATCHTools={}
CLIENTWATCH.version="1.0.1"
function CLIENTWATCH:New(client)
local self=BASE:Inherit(self,FSM:New())
self:SetStartState("Idle")
self:AddTransition("*","Spawn","*")
self.FilterCoalition=nil
self.FilterCategory=nil
if not client then
if self.Debug then self:I({"New client instance created. ClientType = All clients"})end
self:HandleEvent(EVENTS.Birth)
function self:OnEventBirth(eventdata)
if(eventdata.IniCategory==0 or eventdata.IniCategory==1)and eventdata.IniPlayerName
and(not self.FilterCoalition or self.FilterCoalition==eventdata.IniCoalition)
and(not self.FilterCategory or self.FilterCategory==eventdata.IniCategory)then
if self.Debug then
self:I({"Client spawned in.",IniCategory=eventdata.IniCategory})
end
local clientWatchDebug=self.Debug
local clientObject=CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
self:Spawn(clientObject,eventdata)
end
end
elseif type(client)=="table"or type(client)=="string"then
if type(client)=="table"then
if client.ClassName=="CLIENT"then
if self.Debug then self:I({"New client instance created. ClientType = Wrapper.CLIENT",client})end
self.ClientName=client:GetName()
self:HandleEvent(EVENTS.Birth)
function self:OnEventBirth(eventdata)
if(eventdata.IniCategory==0 or eventdata.IniCategory==1)and eventdata.IniPlayerName
and(not self.FilterCoalition or self.FilterCoalition==eventdata.IniCoalition)
and(not self.FilterCategory or self.FilterCategory==eventdata.IniCategory)then
if self.ClientName==eventdata.IniUnitName then
if self.Debug then
self:I({"Client spawned in.",IniCategory=eventdata.IniCategory})
end
local clientWatchDebug=self.Debug
local clientObject=CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
self:Spawn(clientObject,eventdata)
end
end
end
else
if self.Debug then self:I({"New client instance created. ClientType = Multiple Prefixes",client})end
local tableValid=true
for _,entry in pairs(client)do
if type(entry)~="string"then
tableValid=false
self:E({"The base handler failed to start because at least one entry in param1's table is not a string!",InvalidEntry=entry})
return nil
end
end
if tableValid then
self:HandleEvent(EVENTS.Birth)
function self:OnEventBirth(eventdata)
for _,entry in pairs(client)do
if(eventdata.IniCategory==0 or eventdata.IniCategory==1)and eventdata.IniPlayerName
and(not self.FilterCoalition or self.FilterCoalition==eventdata.IniCoalition)
and(not self.FilterCategory or self.FilterCategory==eventdata.IniCategory)then
if string.match(eventdata.IniUnitName,entry)or string.match(eventdata.IniGroupName,entry)then
if self.Debug then
self:I({"Client spawned in.",IniCategory=eventdata.IniCategory})
end
local clientWatchDebug=self.Debug
local clientObject=CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
self:Spawn(clientObject,eventdata)
break
end
end
end
end
end
end
else
if self.Debug then self:I({"New client instance created. ClientType = Single Prefix",client})end
self:HandleEvent(EVENTS.Birth)
function self:OnEventBirth(eventdata)
if(eventdata.IniCategory==0 or eventdata.IniCategory==1)and eventdata.IniPlayerName
and(not self.FilterCoalition or self.FilterCoalition==eventdata.IniCoalition)
and(not self.FilterCategory or self.FilterCategory==eventdata.IniCategory)then
if string.match(eventdata.IniUnitName,client)or string.match(eventdata.IniGroupName,client)then
if self.Debug then
self:I({"Client spawned in.",IniCategory=eventdata.IniCategory})
end
local clientWatchDebug=self.Debug
local clientObject=CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
self:Spawn(clientObject,eventdata)
end
end
end
end
else
self:E({"The base handler failed to start because param1 is not a CLIENT object or a prefix string!",param1=client})
return nil
end
return self
end
function CLIENTWATCH:FilterByCoalition(value)
if value==1 or value=="red"then
self.FilterCoalition=1
else
self.FilterCoalition=2
end
return self
end
function CLIENTWATCH:FilterByCategory(value)
if value==1 or value=="helicopter"then
self.FilterCategory=1
else
self.FilterCategory=0
end
return self
end
function CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
local self=BASE:Inherit(self,FSM:New())
self:SetStartState("Alive")
self:AddTransition("Alive","Despawn","Dead")
self.Unit=eventdata.IniUnit
self.Group=self.Unit:GetGroup()
self.Client=self.Unit:GetClient()
self.PlayerName=self.Unit:GetPlayerName()
self.UnitName=self.Unit:GetName()
self.GroupName=self.Group:GetName()
self:AddTransition("*","Hit","*")
self:AddTransition("*","Kill","*")
self:AddTransition("*","Score","*")
self:AddTransition("*","Shot","*")
self:AddTransition("*","ShootingStart","*")
self:AddTransition("*","ShootingEnd","*")
self:AddTransition("*","Land","*")
self:AddTransition("*","Takeoff","*")
self:AddTransition("*","RunwayTakeoff","*")
self:AddTransition("*","RunwayTouch","*")
self:AddTransition("*","Refueling","*")
self:AddTransition("*","RefuelingStop","*")
self:AddTransition("*","PlayerLeaveUnit","*")
self:AddTransition("*","Crash","*")
self:AddTransition("*","Dead","*")
self:AddTransition("*","PilotDead","*")
self:AddTransition("*","UnitLost","*")
self:AddTransition("*","Ejection","*")
self:AddTransition("*","HumanFailure","*")
self:AddTransition("*","HumanAircraftRepairFinish","*")
self:AddTransition("*","HumanAircraftRepairStart","*")
self:AddTransition("*","EngineShutdown","*")
self:AddTransition("*","EngineStartup","*")
self:AddTransition("*","WeaponAdd","*")
self:AddTransition("*","WeaponDrop","*")
self:AddTransition("*","WeaponRearm","*")
self:HandleEvent(EVENTS.Hit)
self:HandleEvent(EVENTS.Kill)
self:HandleEvent(EVENTS.Score)
self:HandleEvent(EVENTS.Shot)
self:HandleEvent(EVENTS.ShootingStart)
self:HandleEvent(EVENTS.ShootingEnd)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Takeoff)
self:HandleEvent(EVENTS.RunwayTakeoff)
self:HandleEvent(EVENTS.RunwayTouch)
self:HandleEvent(EVENTS.Refueling)
self:HandleEvent(EVENTS.RefuelingStop)
self:HandleEvent(EVENTS.PlayerLeaveUnit)
self:HandleEvent(EVENTS.Crash)
self:HandleEvent(EVENTS.Dead)
self:HandleEvent(EVENTS.PilotDead)
self:HandleEvent(EVENTS.UnitLost)
self:HandleEvent(EVENTS.Ejection)
self:HandleEvent(EVENTS.HumanFailure)
self:HandleEvent(EVENTS.HumanAircraftRepairFinish)
self:HandleEvent(EVENTS.HumanAircraftRepairStart)
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.EngineStartup)
self:HandleEvent(EVENTS.WeaponAdd)
self:HandleEvent(EVENTS.WeaponDrop)
self:HandleEvent(EVENTS.WeaponRearm)
function self:OnEventHit(EventData)
if EventData.TgtUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered hit event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Hit(EventData)
end
end
function self:OnEventKill(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered kill event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Kill(EventData)
end
end
function self:OnEventScore(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered score event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Score(EventData)
end
end
function self:OnEventShot(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered shot event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Shot(EventData)
end
end
function self:OnEventShootingStart(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered shooting start event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:ShootingStart(EventData)
end
end
function self:OnEventShootingEnd(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered shooting end event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:ShootingEnd(EventData)
end
end
function self:OnEventLand(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered land event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Land(EventData)
end
end
function self:OnEventTakeoff(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered takeoff event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Takeoff(EventData)
end
end
function self:OnEventRunwayTakeoff(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered runway takeoff event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:RunwayTakeoff(EventData)
end
end
function self:OnEventRunwayTouch(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered runway touch event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:RunwayTouch(EventData)
end
end
function self:OnEventRefueling(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered refueling event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Refueling(EventData)
end
end
function self:OnEventRefuelingStop(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered refueling event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:RefuelingStop(EventData)
end
end
function self:OnEventPlayerLeaveUnit(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered leave unit event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:PlayerLeaveUnit(EventData)
self._deadRoutine()
end
end
function self:OnEventCrash(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered crash event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Crash(EventData)
self._deadRoutine()
end
end
function self:OnEventDead(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered dead event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Dead(EventData)
self._deadRoutine()
end
end
function self:OnEventPilotDead(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered pilot dead event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:PilotDead(EventData)
self._deadRoutine()
end
end
function self:OnEventUnitLost(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered unit lost event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:UnitLost(EventData)
self._deadRoutine()
end
end
function self:OnEventEjection(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered ejection event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:Ejection(EventData)
self._deadRoutine()
end
end
function self:OnEventHumanFailure(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered human failure event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:HumanFailure(EventData)
if not self.Unit:IsAlive()then
self._deadRoutine()
end
end
end
function self:OnEventHumanAircraftRepairFinish(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered repair finished event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:HumanAircraftRepairFinish(EventData)
end
end
function self:OnEventHumanAircraftRepairStart(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered repair start event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:HumanAircraftRepairStart(EventData)
end
end
function self:OnEventEngineShutdown(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered engine shutdown event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:EngineShutdown(EventData)
end
end
function self:OnEventEngineStartup(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered engine startup event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:EngineStartup(EventData)
end
end
function self:OnEventWeaponAdd(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered weapon add event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:WeaponAdd(EventData)
end
end
function self:OnEventWeaponDrop(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered weapon drop event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:WeaponDrop(EventData)
end
end
function self:OnEventWeaponRearm(EventData)
if EventData.IniUnitName==self.UnitName then
if clientWatchDebug then
self:I({"Client triggered weapon rearm event.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self:WeaponRearm(EventData)
end
end
self.FallbackTimer=TIMER:New(function()
if not self.Unit:IsAlive()then
if clientWatchDebug then
self:I({"Client is registered as dead without an event trigger. Running fallback dead routine.",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})
end
self._deadRoutine()
end
end)
self.FallbackTimer:Start(5,5)
function self._deadRoutine()
if clientWatchDebug then self:I({"Client dead routine triggered. Shutting down tracking...",Player=self.PlayerName,Group=self.GroupName,Unit=self.UnitName})end
self:UnHandleEvent(EVENTS.Hit)
self:UnHandleEvent(EVENTS.Kill)
self:UnHandleEvent(EVENTS.Score)
self:UnHandleEvent(EVENTS.Shot)
self:UnHandleEvent(EVENTS.ShootingStart)
self:UnHandleEvent(EVENTS.ShootingEnd)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.RunwayTakeoff)
self:UnHandleEvent(EVENTS.RunwayTouch)
self:UnHandleEvent(EVENTS.Refueling)
self:UnHandleEvent(EVENTS.RefuelingStop)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.UnitLost)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.HumanFailure)
self:UnHandleEvent(EVENTS.HumanAircraftRepairFinish)
self:UnHandleEvent(EVENTS.HumanAircraftRepairStart)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.EngineStartup)
self:UnHandleEvent(EVENTS.WeaponAdd)
self:UnHandleEvent(EVENTS.WeaponDrop)
self:UnHandleEvent(EVENTS.WeaponRearm)
self.FallbackTimer:Stop()
self:Despawn()
end
self:I({"Detected client spawn and applied internal functions and events.",PlayerName=self.PlayerName,UnitName=self.UnitName,GroupName=self.GroupName})
return self
end
AIRBOSS={
ClassName="AIRBOSS",
Debug=false,
lid=nil,
theatre=nil,
carrier=nil,
carriertype=nil,
carrierparam={},
alias=nil,
airbase=nil,
waypoints={},
currentwp=nil,
beacon=nil,
TACANon=nil,
TACANchannel=nil,
TACANmode=nil,
TACANmorse=nil,
ICLSon=nil,
ICLSchannel=nil,
ICLSmorse=nil,
LSORadio=nil,
LSOFreq=nil,
LSOModu=nil,
MarshalRadio=nil,
MarshalFreq=nil,
MarshalModu=nil,
TowerFreq=nil,
radiotimer=nil,
zoneCCA=nil,
zoneCCZ=nil,
players={},
menuadded={},
BreakEntry={},
BreakEarly={},
BreakLate={},
Abeam={},
Ninety={},
Wake={},
Final={},
Groove={},
Platform={},
DirtyUp={},
Bullseye={},
defaultcase=nil,
case=nil,
defaultoffset=nil,
holdingoffset=nil,
recoverytimes={},
flights={},
Qpattern={},
Qmarshal={},
Qwaiting={},
Qspinning={},
RQMarshal={},
RQLSO={},
TQMarshal=0,
TQLSO=0,
Nmaxpattern=nil,
Nmaxmarshal=nil,
NmaxSection=nil,
NmaxStack=nil,
handleai=nil,
xtVoiceOvers=nil,
xtVoiceOversAI=nil,
tanker=nil,
Corientation=nil,
Corientlast=nil,
Cposition=nil,
defaultskill=nil,
adinfinitum=nil,
magvar=nil,
Tcollapse=nil,
recoverywindow=nil,
usersoundradio=nil,
Tqueue=nil,
dTqueue=nil,
dTstatus=nil,
menumarkzones=nil,
menusmokezones=nil,
playerscores=nil,
autosave=nil,
autosavefile=nil,
autosavepath=nil,
marshalradius=nil,
airbossnice=nil,
staticweather=nil,
windowcount=0,
LSOdT=nil,
senderac=nil,
radiorelayLSO=nil,
radiorelayMSH=nil,
turnintowind=nil,
detour=nil,
squadsetAI=nil,
excludesetAI=nil,
menusingle=nil,
collisiondist=nil,
holdtimestamp=nil,
Tmessage=nil,
soundfolder=nil,
soundfolderLSO=nil,
soundfolderMSH=nil,
despawnshutdown=nil,
dTbeacon=nil,
Tbeacon=nil,
LSOCall=nil,
MarshalCall=nil,
lowfuelAI=nil,
emergency=nil,
respawnAI=nil,
gle={},
lue={},
trapsheet=nil,
trappath=nil,
trapprefix=nil,
initialmaxalt=nil,
welcome=nil,
skipperMenu=nil,
skipperSpeed=nil,
skipperTime=nil,
skipperOffset=nil,
skipperUturn=nil,
}
AIRBOSS.AircraftCarrier={
AV8B="AV8BNA",
HORNET="FA-18C_hornet",
A4EC="A-4E-C",
F14A="F-14A-135-GR",
F14B="F-14B",
F14A_AI="F-14A",
FA18C="F/A-18C",
T45C="T-45",
S3B="S-3B",
S3BTANKER="S-3B Tanker",
E2D="E-2C",
C2A="C2A_Greyhound",
RHINOE="FA-18E",
RHINOF="FA-18F",
GROWLER="EA-18G",
}
AIRBOSS.CarrierType={
ROOSEVELT="CVN_71",
LINCOLN="CVN_72",
WASHINGTON="CVN_73",
TRUMAN="CVN_75",
STENNIS="Stennis",
FORRESTAL="Forrestal",
VINSON="VINSON",
HERMES="HERMES81",
INVINCIBLE="hms_invincible",
TARAWA="LHA_Tarawa",
AMERICA="USS America LHA-6",
JCARLOS="L61",
CANBERRA="L02",
KUZNETSOV="KUZNECOW",
}
AIRBOSS.PatternStep={
UNDEFINED="Undefined",
REFUELING="Refueling",
SPINNING="Spinning",
COMMENCING="Commencing",
HOLDING="Holding",
WAITING="Waiting for free Marshal stack",
PLATFORM="Platform",
ARCIN="Arc Turn In",
ARCOUT="Arc Turn Out",
DIRTYUP="Dirty Up",
BULLSEYE="Bullseye",
INITIAL="Initial",
BREAKENTRY="Break Entry",
EARLYBREAK="Early Break",
LATEBREAK="Late Break",
ABEAM="Abeam",
NINETY="Ninety",
WAKE="Wake",
FINAL="Turn Final",
GROOVE_XX="Groove X",
GROOVE_IM="Groove In the Middle",
GROOVE_IC="Groove In Close",
GROOVE_AR="Groove At the Ramp",
GROOVE_IW="Groove In the Wires",
GROOVE_AL="Groove Abeam Landing Spot",
GROOVE_LC="Groove Level Cross",
BOLTER="Bolter Pattern",
EMERGENCY="Emergency Landing",
DEBRIEF="Debrief",
}
AIRBOSS.GroovePos={
X0="X0",
XX="XX",
IM="IM",
IC="IC",
AR="AR",
AL="AL",
LC="LC",
IW="IW",
}
AIRBOSS.Difficulty={
EASY="Flight Student",
NORMAL="Naval Aviator",
HARD="TOPGUN Graduate",
}
AIRBOSS.MenuF10={}
AIRBOSS.MenuF10Root=nil
AIRBOSS.version="1.3.3"
function AIRBOSS:New(carriername,alias)
local self=BASE:Inherit(self,FSM:New())
self:F2({carriername=carriername,alias=alias})
self.carrier=UNIT:FindByName(carriername)
if self.carrier==nil then
local text=string.format("ERROR: Carrier unit %s could not be found! Make sure this UNIT is defined in the mission editor and check the spelling of the unit name carefully.",carriername)
MESSAGE:New(text,120):ToAll()
self:E(text)
return nil
end
self.lid=string.format("AIRBOSS %s | ",carriername)
self.theatre=env.mission.theatre
self:T2(self.lid..string.format("Theatre = %s.",tostring(self.theatre)))
self.carriertype=self.carrier:GetTypeName()
self.alias=alias or carriername
self.airbase=AIRBASE:FindByName(carriername)
self.beacon=BEACON:New(self.carrier)
self:_GetTowerFrequency()
self.playerscores={}
self:_InitWaypoints()
self.currentwp=1
self:_PatrolRoute()
self:SetMarshalRadio()
self:SetAirbossRadio()
self:SetLSORadio()
self:SetLSOCallInterval()
self.radiotimer=SCHEDULER:New()
self:SetMagneticDeclination()
self:SetICLS()
self:SetTACAN()
self:SetBeaconRefresh()
self:SetMaxLandingPattern()
self:SetMaxMarshalStacks()
self:SetMaxSectionSize()
self:SetMaxSectionDistance()
self:SetMaxFlightsPerStack()
self:SetHandleAION()
self:SetExtraVoiceOvers(false)
self:SetExtraVoiceOversAI(false)
self:SetAirbossNiceGuy()
self:SetEmergencyLandings()
self:SetDespawnOnEngineShutdown(false)
self:SetRespawnAI(false)
self:SetStaticWeather()
self:SetRecoveryCase()
self:SetRecoveryTurnTime()
self:SetHoldingOffsetAngle()
self:SetMarshalRadius()
self:SetInitialMaxAlt()
self:SetDefaultPlayerSkill(AIRBOSS.Difficulty.EASY)
self:SetGlideslopeErrorThresholds()
self:SetLineupErrorThresholds()
self:SetCarrierControlledArea()
self:SetCarrierControlledZone()
self:SetPatrolAdInfinitum(true)
self:SetCollisionDistance()
self:SetQueueUpdateTime()
self:SetStatusUpdateTime()
self:SetDefaultMessageDuration()
self:SetMenuMarkZones()
self:SetMenuSmokeZones()
self:SetMenuSingleCarrier(false)
self:SetWelcomePlayers(true)
self.landingcoord=COORDINATE:New(0,0,0)
self.sterncoord=COORDINATE:New(0,0,0)
self.landingspotcoord=COORDINATE:New(0,0,0)
if self.carriertype==AIRBOSS.CarrierType.STENNIS then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.ROOSEVELT then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.LINCOLN then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then
self:_InitNimitz()
elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then
self:_InitForrestal()
elseif self.carriertype==AIRBOSS.CarrierType.VINSON then
self:_InitStennis()
elseif self.carriertype==AIRBOSS.CarrierType.HERMES then
self:_InitHermes()
elseif self.carriertype==AIRBOSS.CarrierType.INVINCIBLE then
self:_InitInvincible()
elseif self.carriertype==AIRBOSS.CarrierType.TARAWA then
self:_InitTarawa()
elseif self.carriertype==AIRBOSS.CarrierType.AMERICA then
self:_InitAmerica()
elseif self.carriertype==AIRBOSS.CarrierType.JCARLOS then
self:_InitJcarlos()
elseif self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:_InitCanberra()
elseif self.carriertype==AIRBOSS.CarrierType.KUZNETSOV then
self:_InitStennis()
else
self:E(self.lid..string.format("ERROR: Unknown carrier type %s!",tostring(self.carriertype)))
return nil
end
self:_InitVoiceOvers()
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(3)
end
if false then
local case=3
self.holdingoffset=30
self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red,5)
self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green,5)
self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White,45)
self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45)
self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45)
self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45)
self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Blue,45)
self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45)
self:_GetZoneHolding(case,1):SmokeZone(SMOKECOLOR.White,45)
self:_GetZoneHolding(case,2):SmokeZone(SMOKECOLOR.White,45)
self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Orange,45)
self:_GetZoneCommence(case,1):SmokeZone(SMOKECOLOR.Red,45)
self:_GetZoneCommence(case,2):SmokeZone(SMOKECOLOR.Red,45)
self:_GetZoneAbeamLandingSpot():SmokeZone(SMOKECOLOR.Red,5)
self:_GetZoneLandingSpot():SmokeZone(SMOKECOLOR.Red,5)
end
if false then
local FB=self:GetFinalBearing(false)
local hdg=self:GetHeading(false)
local stern=self:_GetSternCoord()
local bow=stern:Translate(self.carrierparam.totlength,hdg,true)
local rwy=stern:Translate(self.carrierparam.rwylength,FB,true)
local function flareme()
self:GetCoordinate():FlareYellow()
stern:FlareYellow()
bow:FlareYellow()
local r1=stern:Translate(self.carrierparam.rwywidth*0.5,FB+90,true)
local r2=stern:Translate(self.carrierparam.rwywidth*0.5,FB-90,true)
rwy:FlareRed()
local cR=stern:Translate(self.carrierparam.totwidthstarboard,hdg+90,true)
local cL=stern:Translate(self.carrierparam.totwidthport,hdg-90,true)
if self.carrier:GetTypeName()~=AIRBOSS.CarrierType.INVINCIBLE or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName()~=AIRBOSS.CarrierType.CANBERRA then
local w1=stern:Translate(self.carrierparam.wire1,FB,true)
local w2=stern:Translate(self.carrierparam.wire2,FB,true)
local w3=stern:Translate(self.carrierparam.wire3,FB,true)
local w4=stern:Translate(self.carrierparam.wire4,FB,true)
w1:FlareWhite()
w2:FlareYellow()
w3:FlareWhite()
w4:FlareYellow()
else
local ALSPT=self:_GetZoneAbeamLandingSpot()
ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(120))
local LSPT=self:_GetZoneLandingSpot()
LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight)
local PLSC=self:_GetLandingSpotCoordinate()
PLSC:FlareWhite()
end
local cbox=self:_GetZoneCarrierBox()
local rbox=self:_GetZoneRunwayBox()
cbox:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight)
rbox:FlareZone(FLARECOLOR.White,5,nil,self.carrierparam.deckheight)
end
SCHEDULER:New(nil,flareme,{},1,3,nil,180)
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Load","Stopped")
self:AddTransition("Stopped","Start","Idle")
self:AddTransition("*","Idle","Idle")
self:AddTransition("Idle","RecoveryStart","Recovering")
self:AddTransition("Recovering","RecoveryStop","Idle")
self:AddTransition("Recovering","RecoveryPause","Paused")
self:AddTransition("Paused","RecoveryUnpause","Recovering")
self:AddTransition("*","Status","*")
self:AddTransition("*","RecoveryCase","*")
self:AddTransition("*","PassingWaypoint","*")
self:AddTransition("*","LSOGrade","*")
self:AddTransition("*","Marshal","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function AIRBOSS:SetWelcomePlayers(Switch)
self.welcome=Switch
return self
end
function AIRBOSS:SetCarrierControlledArea(Radius)
Radius=UTILS.NMToMeters(Radius or 50)
self.zoneCCA=ZONE_UNIT:New("Carrier Controlled Area",self.carrier,Radius)
return self
end
function AIRBOSS:SetCarrierControlledZone(Radius)
Radius=UTILS.NMToMeters(Radius or 5)
self.zoneCCZ=ZONE_UNIT:New("Carrier Controlled Zone",self.carrier,Radius)
return self
end
function AIRBOSS:SetCollisionDistance(Distance)
self.collisiondist=UTILS.NMToMeters(Distance or 5)
return self
end
function AIRBOSS:SetRecoveryCase(Case)
self.defaultcase=Case or 1
self.case=self.defaultcase
return self
end
function AIRBOSS:SetHoldingOffsetAngle(Offset)
self.defaultoffset=Offset or 0
self.holdingoffset=self.defaultoffset
return self
end
function AIRBOSS:SetMenuRecovery(Duration,WindOnDeck,Uturn,Offset)
self.skipperMenu=true
self.skipperTime=Duration or 30
self.skipperSpeed=WindOnDeck or 25
self.skipperOffset=Offset or 30
if Uturn then
self.skipperUturn=true
else
self.skipperUturn=false
end
return self
end
function AIRBOSS:AddRecoveryWindow(starttime,stoptime,case,holdingoffset,turnintowind,speed,uturn)
local Tnow=timer.getAbsTime()
if starttime and type(starttime)=="number"then
starttime=UTILS.SecondsToClock(Tnow+starttime)
end
if stoptime and type(stoptime)=="number"then
stoptime=UTILS.SecondsToClock(Tnow+stoptime)
end
starttime=starttime or UTILS.SecondsToClock(Tnow)
local Tstart=UTILS.ClockToSeconds(starttime)
local Tstop=stoptime and UTILS.ClockToSeconds(stoptime)or Tstart+90*60
if Tstart>Tstop then
self:E(string.format("ERROR: Recovery stop time %s lies before recovery start time %s! Recovery window rejected.",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop)))
return self
end
if Tstop<=Tnow then
string.format("WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow))
return self
end
case=case or self.defaultcase
holdingoffset=holdingoffset or self.defaultoffset
if case==1 then
holdingoffset=0
end
self.windowcount=self.windowcount+1
local recovery={}
recovery.START=Tstart
recovery.STOP=Tstop
recovery.CASE=case
recovery.OFFSET=holdingoffset
recovery.OPEN=false
recovery.OVER=false
recovery.WIND=turnintowind
recovery.SPEED=speed or 20
recovery.ID=self.windowcount
if uturn==nil or uturn==true then
recovery.UTURN=true
else
recovery.UTURN=false
end
table.insert(self.recoverytimes,recovery)
return recovery
end
function AIRBOSS:SetSquadronAI(SetGroup)
self.squadsetAI=SetGroup
return self
end
function AIRBOSS:SetExcludeAI(SetGroup)
self.excludesetAI=SetGroup
return self
end
function AIRBOSS:AddExcludeAI(Group)
self.excludesetAI=self.excludesetAI or SET_GROUP:New()
self.excludesetAI:AddGroup(Group)
return self
end
function AIRBOSS:CloseCurrentRecoveryWindow(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.CloseCurrentRecoveryWindow,self)
else
if self:IsRecovering()and self.recoverywindow and self.recoverywindow.OPEN then
self:RecoveryStop()
self.recoverywindow.OPEN=false
self.recoverywindow.OVER=true
self:DeleteRecoveryWindow(self.recoverywindow)
end
end
end
function AIRBOSS:DeleteAllRecoveryWindows(Delay)
for _,recovery in pairs(self.recoverytimes)do
self:I(self.lid..string.format("Deleting recovery window ID %s",tostring(recovery.ID)))
self:DeleteRecoveryWindow(recovery,Delay)
end
return self
end
function AIRBOSS:GetRecoveryWindowByID(id)
if id then
for _,_window in pairs(self.recoverytimes)do
local window=_window
if window and window.ID==id then
return window
end
end
end
return nil
end
function AIRBOSS:DeleteRecoveryWindow(Window,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.DeleteRecoveryWindow,self,Window)
else
for i,_recovery in pairs(self.recoverytimes)do
local recovery=_recovery
if Window and Window.ID==recovery.ID then
if Window.OPEN then
self:RecoveryStop()
else
table.remove(self.recoverytimes,i)
end
end
end
end
end
function AIRBOSS:SetRecoveryTurnTime(Interval)
self.dTturn=Interval or 300
return self
end
function AIRBOSS:SetMPWireCorrection(Dcorr)
self.mpWireCorrection=Dcorr or 12
return self
end
function AIRBOSS:SetQueueUpdateTime(TimeInterval)
self.dTqueue=TimeInterval or 30
return self
end
function AIRBOSS:SetLSOCallInterval(TimeInterval)
self.LSOdT=TimeInterval or 4
return self
end
function AIRBOSS:SetIntoWindLegacy(SwitchOn)
if SwitchOn==nil then
SwitchOn=true
end
self.intowindold=SwitchOn
return self
end
function AIRBOSS:SetAirbossNiceGuy(Switch)
if Switch==true or Switch==nil then
self.airbossnice=true
else
self.airbossnice=false
end
return self
end
function AIRBOSS:SetEmergencyLandings(Switch)
if Switch==true or Switch==nil then
self.emergency=true
else
self.emergency=false
end
return self
end
function AIRBOSS:SetDespawnOnEngineShutdown(Switch)
if Switch==true or Switch==nil then
self.despawnshutdown=true
else
self.despawnshutdown=false
end
return self
end
function AIRBOSS:SetRespawnAI(Switch)
if Switch==true or Switch==nil then
self.respawnAI=true
else
self.respawnAI=false
end
return self
end
function AIRBOSS:SetRefuelAI(LowFuelThreshold)
self.lowfuelAI=LowFuelThreshold or 10
return self
end
function AIRBOSS:SetInitialMaxAlt(MaxAltitude)
self.initialmaxalt=UTILS.FeetToMeters(MaxAltitude or 1300)
return self
end
function AIRBOSS:SetSoundfilesFolder(FolderPath)
if FolderPath then
local lastchar=string.sub(FolderPath,-1)
if lastchar~="/"then
FolderPath=FolderPath.."/"
end
end
self.soundfolder=FolderPath
self:I(self.lid..string.format("Setting sound files folder to: %s",self.soundfolder))
return self
end
function AIRBOSS:SetStatusUpdateTime(TimeInterval)
self.dTstatus=TimeInterval or 0.5
return self
end
function AIRBOSS:SetDefaultMessageDuration(Duration)
self.Tmessage=Duration or 10
return self
end
function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min,High,HIGH,Low,LOW)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self.gle._max=_max or 0.7
self.gle.High=High or 1.4
self.gle.HIGH=HIGH or 1.9
self.gle._min=_min or-0.5
self.gle.Low=Low or-1.2
self.gle.LOW=LOW or-1.5
else
self.gle._max=_max or 0.4
self.gle.High=High or 0.8
self.gle.HIGH=HIGH or 1.5
self.gle._min=_min or-0.3
self.gle.Low=Low or-0.6
self.gle.LOW=LOW or-0.9
end
return self
end
function AIRBOSS:SetLineupErrorThresholds(_max,_min,Left,LeftMed,LEFT,Right,RightMed,RIGHT)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self.lue._max=_max or 1.8
self.lue._min=_min or-1.8
self.lue.Left=Left or-2.8
self.lue.LeftMed=LeftMed or-3.8
self.lue.LEFT=LEFT or-4.5
self.lue.Right=Right or 2.8
self.lue.RightMed=RightMed or 3.8
self.lue.RIGHT=RIGHT or 4.5
else
self.lue._max=_max or 0.5
self.lue._min=_min or-0.5
self.lue.Left=Left or-1.0
self.lue.LeftMed=LeftMed or-2.0
self.lue.LEFT=LEFT or-3.0
self.lue.Right=Right or 1.0
self.lue.RightMed=RightMed or 2.0
self.lue.RIGHT=RIGHT or 3.0
end
return self
end
function AIRBOSS:SetMarshalRadius(Radius)
self.marshalradius=UTILS.NMToMeters(Radius or 2.8)
return self
end
function AIRBOSS:SetMenuSingleCarrier(Switch)
if Switch==true or Switch==nil then
self.menusingle=true
else
self.menusingle=false
end
return self
end
function AIRBOSS:SetMenuMarkZones(Switch)
if Switch==nil or Switch==true then
self.menumarkzones=true
else
self.menumarkzones=false
end
return self
end
function AIRBOSS:SetMenuSmokeZones(Switch)
if Switch==nil or Switch==true then
self.menusmokezones=true
else
self.menusmokezones=false
end
return self
end
function AIRBOSS:SetTrapSheet(Path,Prefix)
if io then
self.trapsheet=true
self.trappath=Path
self.trapprefix=Prefix
else
self:E(self.lid.."ERROR: io is not desanitized. Cannot save trap sheet.")
end
return self
end
function AIRBOSS:SetStaticWeather(Switch)
if Switch==nil or Switch==true then
self.staticweather=true
else
self.staticweather=false
end
return self
end
function AIRBOSS:SetTACANoff()
self.TACANon=false
return self
end
function AIRBOSS:SetTACAN(Channel,Mode,MorseCode)
self.TACANchannel=Channel or 74
self.TACANmode=Mode or"X"
self.TACANmorse=MorseCode or"STN"
self.TACANon=true
return self
end
function AIRBOSS:SetICLSoff()
self.ICLSon=false
return self
end
function AIRBOSS:SetICLS(Channel,MorseCode)
self.ICLSchannel=Channel or 1
self.ICLSmorse=MorseCode or"STN"
self.ICLSon=true
return self
end
function AIRBOSS:SetBeaconRefresh(TimeInterval)
self.dTbeacon=TimeInterval or(20*60)
return self
end
function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volume,AltBackend)
local Frequency=self.AirbossRadio.frequency
local Modulation=self.AirbossRadio.modulation
self.SRS=MSRS:New(PathToSRS,Frequency,Modulation,AltBackend)
self.SRS:SetCoalition(self:GetCoalition())
self.SRS:SetCoordinate(self:GetCoordinate())
self.SRS:SetCulture(Culture or"en-US")
self.SRS:SetGender(Gender or"male")
self.SRS:SetPath(PathToSRS)
self.SRS:SetPort(Port or 5002)
self.SRS:SetLabel(self.AirbossRadio.alias or"AIRBOSS")
self.SRS:SetCoordinate(self.carrier:GetCoordinate())
self.SRS:SetVolume(Volume or 1)
if GoogleCreds then
self.SRS:SetProviderOptionsGoogle(GoogleCreds,GoogleCreds)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
end
if Voice then
self.SRS:SetVoice(Voice)
end
self.SRS:SetVolume(Volume or 1.0)
self.SRSQ=MSRSQUEUE:New("AIRBOSS")
self.SRSQ:SetTransmitOnlyWithPlayers(true)
if not self.PilotRadio then
self:SetSRSPilotVoice()
end
return self
end
function AIRBOSS:SetLSORadio(Frequency,Modulation,Voice,Gender,Culture)
self.LSOFreq=(Frequency or 264)
Modulation=Modulation or"AM"
if Modulation=="FM"then
self.LSOModu=radio.modulation.FM
else
self.LSOModu=radio.modulation.AM
end
self.LSORadio={}
self.LSORadio.frequency=self.LSOFreq
self.LSORadio.modulation=self.LSOModu
self.LSORadio.alias="LSO"
self.LSORadio.voice=Voice
self.LSORadio.gender=Gender or"male"
self.LSORadio.culture=Culture or"en-US"
return self
end
function AIRBOSS:SetAirbossRadio(Frequency,Modulation,Voice,Gender,Culture)
self.AirbossFreq=Frequency or self:_GetTowerFrequency()or 127.5
Modulation=Modulation or"AM"
if type(Modulation)=="table"then
self.AirbossModu=Modulation
else
if Modulation=="FM"then
self.AirbossModu=radio.modulation.FM
else
self.AirbossModu=radio.modulation.AM
end
end
self.AirbossRadio={}
self.AirbossRadio.frequency=self.AirbossFreq
self.AirbossRadio.modulation=self.AirbossModu
self.AirbossRadio.alias="AIRBOSS"
self.AirbossRadio.voice=Voice
self.AirbossRadio.gender=Gender or"male"
self.AirbossRadio.culture=Culture or"en-US"
return self
end
function AIRBOSS:SetMarshalRadio(Frequency,Modulation,Voice,Gender,Culture)
self.MarshalFreq=Frequency or 305
Modulation=Modulation or"AM"
if Modulation=="FM"then
self.MarshalModu=radio.modulation.FM
else
self.MarshalModu=radio.modulation.AM
end
self.MarshalRadio={}
self.MarshalRadio.frequency=self.MarshalFreq
self.MarshalRadio.modulation=self.MarshalModu
self.MarshalRadio.alias="MARSHAL"
self.MarshalRadio.voice=Voice
self.MarshalRadio.gender=Gender or"male"
self.MarshalRadio.culture=Culture or"en-US"
return self
end
function AIRBOSS:SetRadioUnitName(unitname)
self.senderac=unitname
return self
end
function AIRBOSS:SetRadioRelayLSO(unitname)
self.radiorelayLSO=unitname
return self
end
function AIRBOSS:SetRadioRelayMarshal(unitname)
self.radiorelayMSH=unitname
return self
end
function AIRBOSS:SetUserSoundRadio()
self.usersoundradio=true
return self
end
function AIRBOSS:SoundCheckLSO(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,AIRBOSS.SoundCheckLSO,self)
else
local text="Playing LSO sound files:"
for _name,_call in pairs(self.LSOCall)do
local call=_call
text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle)
self:RadioTransmission(self.LSORadio,call,false)
if call.loud then
self:RadioTransmission(self.LSORadio,call,true)
end
end
self:T(self.lid..text)
end
end
function AIRBOSS:SoundCheckMarshal(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,AIRBOSS.SoundCheckMarshal,self)
else
local text="Playing Marshal sound files:"
for _name,_call in pairs(self.MarshalCall)do
local call=_call
text=text..string.format("\nFile=%s.%s, duration=%.2f sec, loud=%s, subtitle=\"%s\".",call.file,call.suffix,call.duration,tostring(call.loud),call.subtitle)
self:RadioTransmission(self.MarshalRadio,call,false)
if call.loud then
self:RadioTransmission(self.MarshalRadio,call,true)
end
end
self:T(self.lid..text)
end
end
function AIRBOSS:SetMaxLandingPattern(nmax)
nmax=nmax or 4
nmax=math.max(nmax,1)
nmax=math.min(nmax,6)
self.Nmaxpattern=nmax
return self
end
function AIRBOSS:SetMaxMarshalStacks(nmax)
self.Nmaxmarshal=nmax or 3
self.Nmaxmarshal=math.max(self.Nmaxmarshal,1)
return self
end
function AIRBOSS:SetMaxSectionSize(nmax)
nmax=nmax or 2
nmax=math.max(nmax,1)
nmax=math.min(nmax,4)
self.NmaxSection=nmax-1
return self
end
function AIRBOSS: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)
nmax=math.min(nmax,4)
self.NmaxStack=nmax
return self
end
function AIRBOSS:SetHandleAION()
self.handleai=true
return self
end
function AIRBOSS:SetExtraVoiceOvers(status)
self.xtVoiceOvers=status
return self
end
function AIRBOSS:SetExtraVoiceOversAI(status)
self.xtVoiceOversAI=status
return self
end
function AIRBOSS:SetHandleAIOFF()
self.handleai=false
return self
end
function AIRBOSS:SetRecoveryTanker(recoverytanker)
self.tanker=recoverytanker
return self
end
function AIRBOSS:SetAWACS(awacs)
self.awacs=awacs
return self
end
function AIRBOSS:SetDefaultPlayerSkill(skill)
self.defaultskill=skill or AIRBOSS.Difficulty.NORMAL
local gotit=false
for _,_skill in pairs(AIRBOSS.Difficulty)do
if _skill==self.defaultskill then
gotit=true
end
end
if not gotit then
self.defaultskill=AIRBOSS.Difficulty.NORMAL
self:E(self.lid..string.format("ERROR: Invalid default skill = %s. Resetting to Naval Aviator.",tostring(skill)))
end
return self
end
function AIRBOSS:SetAutoSave(path,filename)
self.autosave=true
self.autosavepath=path
self.autosavefile=filename
return self
end
function AIRBOSS:SetDebugModeON()
self.Debug=true
return self
end
function AIRBOSS:SetPatrolAdInfinitum(switch)
if switch==false then
self.adinfinitum=false
else
self.adinfinitum=true
end
return self
end
function AIRBOSS:SetMagneticDeclination(declination)
self.magvar=declination or UTILS.GetMagneticDeclination()
return self
end
function AIRBOSS:SetDebugModeOFF()
self.Debug=false
return self
end
function AIRBOSS:SetFunkManOn(Port,Host)
self.funkmanSocket=SOCKET:New(Port,Host)
return self
end
function AIRBOSS:GetNextRecoveryTime(InSeconds)
if self.recoverywindow then
if InSeconds then
return self.recoverywindow.START,self.recoverywindow.STOP
else
return UTILS.SecondsToClock(self.recoverywindow.START),UTILS.SecondsToClock(self.recoverywindow.STOP)
end
else
if InSeconds then
return-1,-1
else
return"?","?"
end
end
end
function AIRBOSS:IsRecovering()
return self:is("Recovering")
end
function AIRBOSS:IsIdle()
return self:is("Idle")
end
function AIRBOSS:IsPaused()
return self:is("Paused")
end
function AIRBOSS:_ActivateBeacons()
self:T(self.lid..string.format("Activating Beacons (TACAN=%s, ICLS=%s)",tostring(self.TACANon),tostring(self.ICLSon)))
if self.TACANon then
self:I(self.lid..string.format("Activating TACAN Channel %d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse))
self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true)
end
if self.ICLSon then
self:I(self.lid..string.format("Activating ICLS Channel %d (%s)",self.ICLSchannel,self.ICLSmorse))
self.beacon:ActivateICLS(self.ICLSchannel,self.ICLSmorse)
end
self.Tbeacon=timer.getTime()
end
function AIRBOSS:onafterStart(From,Event,To)
self:I(self.lid..string.format("Starting AIRBOSS v%s for carrier unit %s of type %s on map %s",AIRBOSS.version,self.carrier:GetName(),self.carriertype,self.theatre))
self:_ActivateBeacons()
self.Cposition=self:GetCoordinate()
self.Corientation=self.carrier:GetOrientationX()
self.Corientlast=self.Corientation
self.Tpupdate=timer.getTime()
if#self.recoverytimes==0 and false then
local Topen=timer.getAbsTime()+15*60
local Tclose=Topen+3*60*60
self:AddRecoveryWindow(UTILS.SecondsToClock(Topen),UTILS.SecondsToClock(Tclose))
end
self:_CheckRecoveryTimes()
self.Tqueue=timer.getTime()-60
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.RunwayTouch)
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Takeoff)
self:HandleEvent(EVENTS.Crash)
self:HandleEvent(EVENTS.Ejection)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft)
self:HandleEvent(EVENTS.MissionEnd)
self:HandleEvent(EVENTS.RemoveUnit)
self:HandleEvent(EVENTS.UnitLost,self.OnEventRemoveUnit)
self.StatusTimer=TIMER:New(self._Status,self):Start(2,0.5)
self:__Status(1)
end
function AIRBOSS:onafterStatus(From,Event,To)
local time=timer.getTime()
if time-self.Tqueue>self.dTqueue then
local clock=UTILS.SecondsToClock(timer.getAbsTime())
local eta=UTILS.SecondsToClock(self:_GetETAatNextWP())
local hdg=self:GetHeading()
local pos=self:GetCoordinate()
local speed=self.carrier:GetVelocityKNOTS()
if require then
self.magvar=pos:GetMagneticDeclination()
end
local collision=false
local holdtime=0
if self.holdtimestamp then
holdtime=timer.getTime()-self.holdtimestamp
end
local NextWP=self:_GetNextWaypoint()
local ExpectedSpeed=UTILS.MpsToKnots(NextWP:GetVelocity())
if speed<0.5 and ExpectedSpeed>0 and not(self.detour or self.turnintowind)then
if not self.holdtimestamp then
self:E(self.lid..string.format("Carrier came to an unexpected standstill. Trying to re-route in 3 min. Speed=%.1f knots, expected=%.1f knots",speed,ExpectedSpeed))
self.holdtimestamp=timer.getTime()
else
if holdtime>3*60 then
local coord=self:GetCoordinate():Translate(500,hdg+10)
self:CarrierResumeRoute(coord)
self.holdtimestamp=nil
end
end
end
local text=string.format("Time %s - Status %s (case=%d) - Speed=%.1f kts - Heading=%d - WP=%d - ETA=%s - Turning=%s - Collision Warning=%s - Detour=%s - Turn Into Wind=%s - Holdtime=%d sec",clock,self:GetState(),self.case,speed,hdg,self.currentwp,eta,tostring(self.turning),tostring(collision),tostring(self.detour),tostring(self.turnintowind),holdtime)
self:T(self.lid..text)
text="Players:"
local i=0
for _name,_player in pairs(self.players)do
i=i+1
local player=_player
text=text..string.format("\n%d.) %s: Step=%s, Unit=%s, Airframe=%s",i,tostring(player.name),tostring(player.step),tostring(player.unitname),tostring(player.actype))
end
if i==0 then
text=text.." none"
end
self:T(self.lid..text)
if collision then
if self.turnintowind then
self:CarrierResumeRoute(self.Creturnto)
if self:IsRecovering()and self.recoverywindow and self.recoverywindow.WIND then
self.recoverywindow.WIND=false
end
end
end
self:_CheckRecoveryTimes()
self:_ScanCarrierZone()
self:_CheckQueue()
self:_CheckCarrierTurning()
self:_CheckPatternUpdate()
self.Tqueue=time
end
if time-self.Tbeacon>self.dTbeacon then
self:_ActivateBeacons()
end
self:__Status(-30)
end
function AIRBOSS:_Status()
self:_CheckPlayerStatus()
self:_CheckAIStatus()
end
function AIRBOSS:_CheckAIStatus()
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.ai then
local fuel=flight.group:GetFuelMin()*100
local text=string.format("Group %s fuel=%.1f %%",flight.groupname,fuel)
self:T3(self.lid..text)
if self.lowfuelAI and fuel<self.lowfuelAI and not flight.refueling then
self:_RefuelAI(flight)
self:_RemoveFlightFromMarshalQueue(flight,true)
end
end
end
for _,_flight in pairs(self.Qpattern)do
local flight=_flight
if flight.ai then
for _,_element in pairs(flight.elements)do
local element=_element
local unit=element.unit
local lineup=self:_Lineup(unit,true)
local unitcoord=unit:GetCoord()
local dist=unitcoord:Get2DDistance(self:GetCoord())
local distance=UTILS.MetersToNM(dist)
local alt=UTILS.MetersToFeet(unitcoord.y)
if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then
self:RadioTransmission(self.LSORadio,self.LSOCall.CALLTHEBALL,nil,nil,nil,true)
self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()),self:_GetFuelState(unit)/1000)
self:RadioTransmission(self.LSORadio,self.LSOCall.ROGERBALL,nil,nil,0.5,true)
element.ballcall=true
flight.ballcall=true
end
end
end
end
end
function AIRBOSS:_CheckPlayerPatternDistance(player)
if player.step==AIRBOSS.PatternStep.INITIAL or
player.step==AIRBOSS.PatternStep.BREAKENTRY or
player.step==AIRBOSS.PatternStep.EARLYBREAK or
player.step==AIRBOSS.PatternStep.LATEBREAK or
player.step==AIRBOSS.PatternStep.ABEAM or
player.step==AIRBOSS.PatternStep.GROOVE_XX or
player.step==AIRBOSS.PatternStep.GROOVE_IM then
return
else
return
end
if#self.Qpattern==0 then
return
end
local function _checkclose(_unit1,_unit2)
local unit1=_unit1
local unit2=_unit2
if(not unit1)or(not unit2)then
return false
end
if unit1:GetName()==unit2:GetName()then
return false
end
if not unit2:InAir()then
return false
end
local c1=unit1:GetCoordinate()
local c2=unit2:GetCoordinate()
local vec12={x=c2.x-c1.x,y=0,z=c2.z-c1.z}
local dist=UTILS.VecNorm(vec12)
local vec1=unit1:GetOrientationX()
vec1.y=0
local rhdg=math.deg(math.acos(UTILS.VecDot(vec12,vec1)/UTILS.VecNorm(vec12)/UTILS.VecNorm(vec1)))
local dalt=math.abs(c2.y-c1.y)
local dcrit=UTILS.FeetToMeters(650)
if math.abs(rhdg)<10 and dist<dcrit and dalt<50 then
return true
else
return false
end
end
for _,_flight in pairs(self.Qpattern)do
local flight=_flight
for _,_element in pairs(flight.elements)do
local element=_element
local tooclose=_checkclose(player.unit,element.unit)
if tooclose then
local text=string.format("Player %s too close (<200 meters) to aircraft %s!",player.name,element.unit:GetName())
self:T2(self.lid..text)
end
end
end
end
function AIRBOSS:_CheckRecoveryTimes()
local time=timer.getAbsTime()
local Cnow=UTILS.SecondsToClock(time)
local text=string.format(self.lid.."Recovery time windows:")
if#self.recoverytimes==0 then
text=text.." none!"
end
local _sort=function(a,b)
return a.START<b.START
end
table.sort(self.recoverytimes,_sort)
local nextwindow=nil
local currwindow=nil
for _,_recovery in pairs(self.recoverytimes)do
local recovery=_recovery
local Cstart=UTILS.SecondsToClock(recovery.START)
local Cstop=UTILS.SecondsToClock(recovery.STOP)
local state=""
if time>=recovery.START then
if time<recovery.STOP then
if self:IsRecovering()then
state="in progress"
else
self:RecoveryStart(recovery.CASE,recovery.OFFSET)
state="starting now"
recovery.OPEN=true
end
currwindow=recovery
else
if self:IsRecovering()and not recovery.OVER then
local _,npattern=self:_GetQueueInfo(self.Qpattern)
if npattern>0 then
local extmin=5*npattern
recovery.STOP=recovery.STOP+extmin*60
local text=string.format("We still got flights in the pattern.\nRecovery time prolonged by %d minutes.\nNow get your act together and no more bolters!",extmin)
self:MessageToPattern(text,"AIRBOSS","99",10,false,nil)
else
self:RecoveryStop()
state="closing now"
recovery.OPEN=false
recovery.OVER=true
end
else
state="closed"
end
end
else
state="in the future"
if nextwindow==nil then
nextwindow=recovery
state="next in line"
end
end
text=text..string.format("\n- Start=%s Stop=%s Case=%d Offset=%d Open=%s Closed=%s Status=\"%s\"",Cstart,Cstop,recovery.CASE,recovery.OFFSET,tostring(recovery.OPEN),tostring(recovery.OVER),state)
end
self:T(self.lid..text)
self.recoverywindow=nil
if self:IsIdle()then
if nextwindow then
self:RecoveryCase(nextwindow.CASE,nextwindow.OFFSET)
if nextwindow.WIND and nextwindow.START-time<self.dTturn and not self.turnintowind then
local hdg=self:GetHeading()
local wind=self:GetHeadingIntoWind(nextwindow.SPEED)
local delta=self:_GetDeltaHeading(hdg,wind)
local uturn=delta>5
local _,vwind=self:GetWind()
if vwind<0.1 then
uturn=false
end
if not nextwindow.UTURN then
uturn=false
end
self:T(self.lid..string.format("Heading=%03d°, Wind=%03d° %.1f kts, Delta=%03d° ==> U-turn=%s",hdg,wind,UTILS.MpsToKnots(vwind),delta,tostring(uturn)))
local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn,60*60*24)
local v=UTILS.KnotsToMps(nextwindow.SPEED)
local vmax=self.carrier:GetSpeedMax()/3.6
v=math.min(v,vmax)
self:CarrierTurnIntoWind(t,v,uturn)
end
self.recoverywindow=nextwindow
else
self:RecoveryCase()
end
else
if currwindow then
self.recoverywindow=currwindow
else
self.recoverywindow=nextwindow
end
end
self:T2({"FF",recoverywindow=self.recoverywindow})
end
function AIRBOSS:_GetFlightLead(flight)
if flight.name~=flight.seclead then
local lead=self.players[flight.seclead]
return lead,false
else
return flight,true
end
end
function AIRBOSS:onbeforeRecoveryCase(From,Event,To,Case,Offset)
Case=Case or self.defaultcase
Offset=Offset or self.defaultoffset
if Case==self.case and Offset==self.holdingoffset then
return false
end
return true
end
function AIRBOSS:onafterRecoveryCase(From,Event,To,Case,Offset)
Case=Case or self.defaultcase
Offset=Offset or self.defaultoffset
local text=string.format("Switching recovery case %d ==> %d",self.case,Case)
if Case>1 then
text=text..string.format(" Holding offset angle %d degrees.",Offset)
end
MESSAGE:New(text,20,self.alias):ToAllIf(self.Debug)
self:T(self.lid..text)
self.case=Case
self.holdingoffset=Offset
for _,_flight in pairs(self.flights)do
local flight=_flight
if not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then
if flight.name~=flight.seclead then
local lead=self.players[flight.seclead]
if lead and not(self:_InQueue(self.Qmarshal,lead.group)or self:_InQueue(self.Qpattern,lead.group))then
flight.case=self.case
end
else
flight.case=self.case
end
end
end
end
function AIRBOSS:onafterRecoveryStart(From,Event,To,Case,Offset)
Case=Case or self.defaultcase
Offset=Offset or self.defaultoffset
self:_MarshalCallRecoveryStart(Case)
self:RecoveryCase(Case,Offset)
end
function AIRBOSS:onafterRecoveryStop(From,Event,To)
self:T(self.lid..string.format("Stopping aircraft recovery."))
self:_MarshalCallRecoveryStopped(self.case)
if self.turnintowind then
local coord=self.Creturnto
if self.recoverywindow and self.recoverywindow.UTURN==false then
coord=nil
end
self:CarrierResumeRoute(coord)
end
if self.recoverywindow and self.recoverywindow.OPEN==true then
self.recoverywindow.OPEN=false
self.recoverywindow.OVER=true
self:DeleteRecoveryWindow(self.recoverywindow)
end
self:_CheckRecoveryTimes()
end
function AIRBOSS:onafterRecoveryPause(From,Event,To,duration)
self:T(self.lid..string.format("Pausing aircraft recovery."))
if duration then
self:__RecoveryUnpause(duration)
local clock=UTILS.SecondsToClock(timer.getAbsTime()+duration)
self:_MarshalCallRecoveryPausedResumedAt(clock)
else
local text=string.format("aircraft recovery is paused until further notice.")
self:_MarshalCallRecoveryPausedNotice()
end
end
function AIRBOSS:onafterRecoveryUnpause(From,Event,To)
self:T(self.lid..string.format("Unpausing aircraft recovery."))
self:_MarshalCallResumeRecovery()
end
function AIRBOSS:onafterPassingWaypoint(From,Event,To,n)
self:I(self.lid..string.format("Carrier passed waypoint %d.",n))
end
function AIRBOSS:onafterIdle(From,Event,To)
self:T(self.lid..string.format("Carrier goes to idle."))
end
function AIRBOSS:onafterStop(From,Event,To)
self:I(self.lid..string.format("Stopping airboss script."))
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.RunwayTouch)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.MissionEnd)
self.CallScheduler:Clear()
end
function AIRBOSS:_InitStennis()
self.carrierparam.sterndist=-153
self.carrierparam.deckheight=18.30
self.carrierparam.totlength=310
self.carrierparam.totwidthport=40
self.carrierparam.totwidthstarboard=30
self.carrierparam.rwyangle=-9.1359
self.carrierparam.rwylength=225
self.carrierparam.rwywidth=20
self.carrierparam.wire1=46
self.carrierparam.wire2=46+12
self.carrierparam.wire3=46+24
self.carrierparam.wire4=46+35
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
self.Platform.name="Platform 5k"
self.Platform.Xmin=-UTILS.NMToMeters(22)
self.Platform.Xmax=nil
self.Platform.Zmin=-UTILS.NMToMeters(30)
self.Platform.Zmax=UTILS.NMToMeters(30)
self.Platform.LimitXmin=nil
self.Platform.LimitXmax=nil
self.Platform.LimitZmin=nil
self.Platform.LimitZmax=nil
self.DirtyUp.name="Dirty Up"
self.DirtyUp.Xmin=-UTILS.NMToMeters(21)
self.DirtyUp.Xmax=nil
self.DirtyUp.Zmin=-UTILS.NMToMeters(30)
self.DirtyUp.Zmax=UTILS.NMToMeters(30)
self.DirtyUp.LimitXmin=nil
self.DirtyUp.LimitXmax=nil
self.DirtyUp.LimitZmin=nil
self.DirtyUp.LimitZmax=nil
self.Bullseye.name="Bullseye"
self.Bullseye.Xmin=-UTILS.NMToMeters(11)
self.Bullseye.Xmax=nil
self.Bullseye.Zmin=-UTILS.NMToMeters(30)
self.Bullseye.Zmax=UTILS.NMToMeters(30)
self.Bullseye.LimitXmin=nil
self.Bullseye.LimitXmax=nil
self.Bullseye.LimitZmin=nil
self.Bullseye.LimitZmax=nil
self.BreakEntry.name="Break Entry"
self.BreakEntry.Xmin=-UTILS.NMToMeters(4)
self.BreakEntry.Xmax=nil
self.BreakEntry.Zmin=-UTILS.NMToMeters(0.5)
self.BreakEntry.Zmax=UTILS.NMToMeters(1.5)
self.BreakEntry.LimitXmin=0
self.BreakEntry.LimitXmax=nil
self.BreakEntry.LimitZmin=nil
self.BreakEntry.LimitZmax=nil
self.BreakEarly.name="Early Break"
self.BreakEarly.Xmin=-UTILS.NMToMeters(1)
self.BreakEarly.Xmax=UTILS.NMToMeters(5)
self.BreakEarly.Zmin=-UTILS.NMToMeters(2)
self.BreakEarly.Zmax=UTILS.NMToMeters(1)
self.BreakEarly.LimitXmin=0
self.BreakEarly.LimitXmax=nil
self.BreakEarly.LimitZmin=-UTILS.NMToMeters(0.2)
self.BreakEarly.LimitZmax=nil
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(2)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.8)
self.BreakLate.LimitZmax=nil
self.Abeam.name="Abeam Position"
self.Abeam.Xmin=-UTILS.NMToMeters(5)
self.Abeam.Xmax=UTILS.NMToMeters(5)
self.Abeam.Zmin=-UTILS.NMToMeters(2)
self.Abeam.Zmax=500
self.Abeam.LimitXmin=-200
self.Abeam.LimitXmax=nil
self.Abeam.LimitZmin=nil
self.Abeam.LimitZmax=nil
self.Ninety.name="Ninety"
self.Ninety.Xmin=-UTILS.NMToMeters(4)
self.Ninety.Xmax=0
self.Ninety.Zmin=-UTILS.NMToMeters(2)
self.Ninety.Zmax=nil
self.Ninety.LimitXmin=nil
self.Ninety.LimitXmax=nil
self.Ninety.LimitZmin=nil
self.Ninety.LimitZmax=-UTILS.NMToMeters(0.6)
self.Wake.name="Wake"
self.Wake.Xmin=-UTILS.NMToMeters(4)
self.Wake.Xmax=0
self.Wake.Zmin=-2000
self.Wake.Zmax=nil
self.Wake.LimitXmin=nil
self.Wake.LimitXmax=nil
self.Wake.LimitZmin=0
self.Wake.LimitZmax=nil
self.Final.name="Final"
self.Final.Xmin=-UTILS.NMToMeters(4)
self.Final.Xmax=0
self.Final.Zmin=-2000
self.Final.Zmax=nil
self.Final.LimitXmin=nil
self.Final.LimitXmax=nil
self.Final.LimitZmin=nil
self.Final.LimitZmax=nil
self.Groove.name="Groove"
self.Groove.Xmin=-UTILS.NMToMeters(4)
self.Groove.Xmax=nil
self.Groove.Zmin=-UTILS.NMToMeters(2)
self.Groove.Zmax=UTILS.NMToMeters(2)
self.Groove.LimitXmin=nil
self.Groove.LimitXmax=nil
self.Groove.LimitZmin=nil
self.Groove.LimitZmax=nil
end
function AIRBOSS:_InitNimitz()
self:_InitStennis()
self.carrierparam.sterndist=-164
self.carrierparam.deckheight=20.1494
self.carrierparam.totlength=332.8
self.carrierparam.totwidthport=45
self.carrierparam.totwidthstarboard=35
self.carrierparam.rwyangle=-9.1359
self.carrierparam.rwylength=250
self.carrierparam.rwywidth=25
self.carrierparam.wire1=55
self.carrierparam.wire2=67
self.carrierparam.wire3=79
self.carrierparam.wire4=92
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
end
function AIRBOSS:_InitForrestal()
self:_InitNimitz()
self.carrierparam.sterndist=-135.5
self.carrierparam.deckheight=20
self.carrierparam.totlength=315
self.carrierparam.totwidthport=45
self.carrierparam.totwidthstarboard=35
self.carrierparam.rwyangle=-9.1359
self.carrierparam.rwylength=212
self.carrierparam.rwywidth=25
self.carrierparam.wire1=44
self.carrierparam.wire2=54
self.carrierparam.wire3=64
self.carrierparam.wire4=74
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.wire3
end
function AIRBOSS:_InitHermes()
self:_InitStennis()
self.carrierparam.sterndist=-105
self.carrierparam.deckheight=12
self.carrierparam.totlength=228.19
self.carrierparam.totwidthport=20.5
self.carrierparam.totwidthstarboard=24.5
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=215
self.carrierparam.rwywidth=13
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=69
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitInvincible()
self:_InitStennis()
self.carrierparam.sterndist=-105
self.carrierparam.deckheight=12
self.carrierparam.totlength=228.19
self.carrierparam.totwidthport=20.5
self.carrierparam.totwidthstarboard=24.5
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=215
self.carrierparam.rwywidth=13
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=69
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitTarawa()
self:_InitStennis()
self.carrierparam.sterndist=-125
self.carrierparam.deckheight=21
self.carrierparam.totlength=245
self.carrierparam.totwidthport=10
self.carrierparam.totwidthstarboard=25
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=225
self.carrierparam.rwywidth=15
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=57
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitAmerica()
self:_InitStennis()
self.carrierparam.sterndist=-125
self.carrierparam.deckheight=20
self.carrierparam.totlength=257
self.carrierparam.totwidthport=11
self.carrierparam.totwidthstarboard=25
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=240
self.carrierparam.rwywidth=15
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=59
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitJcarlos()
self:_InitStennis()
self.carrierparam.sterndist=-125
self.carrierparam.deckheight=20
self.carrierparam.totlength=231
self.carrierparam.totwidthport=10
self.carrierparam.totwidthstarboard=22
self.carrierparam.rwyangle=0
self.carrierparam.rwylength=202
self.carrierparam.rwywidth=14
self.carrierparam.wire1=nil
self.carrierparam.wire2=nil
self.carrierparam.wire3=nil
self.carrierparam.wire4=nil
self.carrierparam.landingspot=89
self.carrierparam.landingdist=self.carrierparam.sterndist+self.carrierparam.landingspot
self.BreakLate.name="Late Break"
self.BreakLate.Xmin=-UTILS.NMToMeters(1)
self.BreakLate.Xmax=UTILS.NMToMeters(5)
self.BreakLate.Zmin=-UTILS.NMToMeters(1.6)
self.BreakLate.Zmax=UTILS.NMToMeters(1)
self.BreakLate.LimitXmin=0
self.BreakLate.LimitXmax=nil
self.BreakLate.LimitZmin=-UTILS.NMToMeters(0.5)
self.BreakLate.LimitZmax=nil
end
function AIRBOSS:_InitCanberra()
self:_InitJcarlos()
end
function AIRBOSS:SetVoiceOversMarshalByGabriella(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderMSH=mizfolder
else
self.soundfolderMSH=self.soundfolder
end
self:I(self.lid..string.format("Marshal Gabriella reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH)))
self.MarshalCall.AFFIRMATIVE.duration=0.65
self.MarshalCall.ALTIMETER.duration=0.60
self.MarshalCall.BRC.duration=0.67
self.MarshalCall.CARRIERTURNTOHEADING.duration=1.62
self.MarshalCall.CASE.duration=0.30
self.MarshalCall.CHARLIETIME.duration=0.77
self.MarshalCall.CLEAREDFORRECOVERY.duration=0.93
self.MarshalCall.DECKCLOSED.duration=0.73
self.MarshalCall.DEGREES.duration=0.48
self.MarshalCall.EXPECTED.duration=0.50
self.MarshalCall.FLYNEEDLES.duration=0.89
self.MarshalCall.HOLDATANGELS.duration=0.81
self.MarshalCall.HOURS.duration=0.41
self.MarshalCall.MARSHALRADIAL.duration=0.95
self.MarshalCall.N0.duration=0.41
self.MarshalCall.N1.duration=0.30
self.MarshalCall.N2.duration=0.34
self.MarshalCall.N3.duration=0.31
self.MarshalCall.N4.duration=0.34
self.MarshalCall.N5.duration=0.30
self.MarshalCall.N6.duration=0.33
self.MarshalCall.N7.duration=0.38
self.MarshalCall.N8.duration=0.35
self.MarshalCall.N9.duration=0.35
self.MarshalCall.NEGATIVE.duration=0.60
self.MarshalCall.NEWFB.duration=0.95
self.MarshalCall.OPS.duration=0.23
self.MarshalCall.POINT.duration=0.38
self.MarshalCall.RADIOCHECK.duration=1.27
self.MarshalCall.RECOVERY.duration=0.60
self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.25
self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.55
self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.55
self.MarshalCall.REPORTSEEME.duration=0.87
self.MarshalCall.RESUMERECOVERY.duration=1.55
self.MarshalCall.ROGER.duration=0.50
self.MarshalCall.SAYNEEDLES.duration=0.82
self.MarshalCall.STACKFULL.duration=5.70
self.MarshalCall.STARTINGRECOVERY.duration=1.61
end
function AIRBOSS:SetVoiceOversMarshalByRaynor(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderMSH=mizfolder
else
self.soundfolderMSH=self.soundfolder
end
self:I(self.lid..string.format("Marshal Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH)))
self.MarshalCall.AFFIRMATIVE.duration=0.70
self.MarshalCall.ALTIMETER.duration=0.60
self.MarshalCall.BRC.duration=0.60
self.MarshalCall.CARRIERTURNTOHEADING.duration=1.87
self.MarshalCall.CASE.duration=0.60
self.MarshalCall.CHARLIETIME.duration=0.81
self.MarshalCall.CLEAREDFORRECOVERY.duration=1.21
self.MarshalCall.DECKCLOSED.duration=0.86
self.MarshalCall.DEGREES.duration=0.55
self.MarshalCall.EXPECTED.duration=0.61
self.MarshalCall.FLYNEEDLES.duration=0.90
self.MarshalCall.HOLDATANGELS.duration=0.91
self.MarshalCall.HOURS.duration=0.54
self.MarshalCall.MARSHALRADIAL.duration=0.80
self.MarshalCall.N0.duration=0.38
self.MarshalCall.N1.duration=0.30
self.MarshalCall.N2.duration=0.30
self.MarshalCall.N3.duration=0.30
self.MarshalCall.N4.duration=0.32
self.MarshalCall.N5.duration=0.41
self.MarshalCall.N6.duration=0.48
self.MarshalCall.N7.duration=0.51
self.MarshalCall.N8.duration=0.38
self.MarshalCall.N9.duration=0.34
self.MarshalCall.NEGATIVE.duration=0.60
self.MarshalCall.NEWFB.duration=1.10
self.MarshalCall.OPS.duration=0.46
self.MarshalCall.POINT.duration=0.21
self.MarshalCall.RADIOCHECK.duration=0.95
self.MarshalCall.RECOVERY.duration=0.63
self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.36
self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.8
self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=2.75
self.MarshalCall.REPORTSEEME.duration=1.06
self.MarshalCall.RESUMERECOVERY.duration=1.41
self.MarshalCall.ROGER.duration=0.41
self.MarshalCall.SAYNEEDLES.duration=0.79
self.MarshalCall.STACKFULL.duration=4.70
self.MarshalCall.STARTINGRECOVERY.duration=2.06
end
function AIRBOSS:SetVoiceOversLSOByRaynor(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderLSO=mizfolder
else
self.soundfolderLSO=self.soundfolder
end
self:I(self.lid..string.format("LSO Raynor reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO)))
self.LSOCall.BOLTER.duration=0.75
self.LSOCall.CALLTHEBALL.duration=0.625
self.LSOCall.CHECK.duration=0.40
self.LSOCall.CLEAREDTOLAND.duration=0.85
self.LSOCall.COMELEFT.duration=0.60
self.LSOCall.DEPARTANDREENTER.duration=1.10
self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.30
self.LSOCall.EXPECTSPOT75.duration=1.85
self.LSOCall.EXPECTSPOT5.duration=1.3
self.LSOCall.FAST.duration=0.75
self.LSOCall.FOULDECK.duration=0.75
self.LSOCall.HIGH.duration=0.65
self.LSOCall.IDLE.duration=0.40
self.LSOCall.LONGINGROOVE.duration=1.25
self.LSOCall.LOW.duration=0.60
self.LSOCall.N0.duration=0.38
self.LSOCall.N1.duration=0.30
self.LSOCall.N2.duration=0.30
self.LSOCall.N3.duration=0.30
self.LSOCall.N4.duration=0.32
self.LSOCall.N5.duration=0.41
self.LSOCall.N6.duration=0.48
self.LSOCall.N7.duration=0.51
self.LSOCall.N8.duration=0.38
self.LSOCall.N9.duration=0.34
self.LSOCall.PADDLESCONTACT.duration=0.91
self.LSOCall.POWER.duration=0.45
self.LSOCall.RADIOCHECK.duration=0.90
self.LSOCall.RIGHTFORLINEUP.duration=0.70
self.LSOCall.ROGERBALL.duration=0.72
self.LSOCall.SLOW.duration=0.63
self.LSOCall.STABILIZED.duration=0.75
self.LSOCall.WAVEOFF.duration=0.55
self.LSOCall.WELCOMEABOARD.duration=0.80
end
function AIRBOSS:SetVoiceOversLSOByFF(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderLSO=mizfolder
else
self.soundfolderLSO=self.soundfolder
end
self:I(self.lid..string.format("LSO FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderLSO)))
self.LSOCall.BOLTER.duration=0.75
self.LSOCall.CALLTHEBALL.duration=0.60
self.LSOCall.CHECK.duration=0.45
self.LSOCall.CLEAREDTOLAND.duration=1.00
self.LSOCall.COMELEFT.duration=0.60
self.LSOCall.DEPARTANDREENTER.duration=1.10
self.LSOCall.EXPECTHEAVYWAVEOFF.duration=1.20
self.LSOCall.EXPECTSPOT75.duration=2.00
self.LSOCall.EXPECTSPOT5.duration=1.3
self.LSOCall.FAST.duration=0.70
self.LSOCall.FOULDECK.duration=0.62
self.LSOCall.HIGH.duration=0.65
self.LSOCall.IDLE.duration=0.45
self.LSOCall.LONGINGROOVE.duration=1.20
self.LSOCall.LOW.duration=0.50
self.LSOCall.N0.duration=0.40
self.LSOCall.N1.duration=0.25
self.LSOCall.N2.duration=0.37
self.LSOCall.N3.duration=0.37
self.LSOCall.N4.duration=0.39
self.LSOCall.N5.duration=0.39
self.LSOCall.N6.duration=0.40
self.LSOCall.N7.duration=0.40
self.LSOCall.N8.duration=0.37
self.LSOCall.N9.duration=0.40
self.LSOCall.PADDLESCONTACT.duration=1.00
self.LSOCall.POWER.duration=0.50
self.LSOCall.RADIOCHECK.duration=1.10
self.LSOCall.RIGHTFORLINEUP.duration=0.80
self.LSOCall.ROGERBALL.duration=1.00
self.LSOCall.SLOW.duration=0.65
self.LSOCall.SLOW.duration=0.59
self.LSOCall.STABILIZED.duration=0.90
self.LSOCall.WAVEOFF.duration=0.60
self.LSOCall.WELCOMEABOARD.duration=1.00
end
function AIRBOSS:SetVoiceOversMarshalByFF(mizfolder)
if mizfolder then
local lastchar=string.sub(mizfolder,-1)
if lastchar~="/"then
mizfolder=mizfolder.."/"
end
self.soundfolderMSH=mizfolder
else
self.soundfolderMSH=self.soundfolder
end
self:I(self.lid..string.format("Marshal FF reporting for duty! Soundfolder=%s",tostring(self.soundfolderMSH)))
self.MarshalCall.AFFIRMATIVE.duration=0.90
self.MarshalCall.ALTIMETER.duration=0.85
self.MarshalCall.BRC.duration=0.80
self.MarshalCall.CARRIERTURNTOHEADING.duration=2.48
self.MarshalCall.CASE.duration=0.40
self.MarshalCall.CHARLIETIME.duration=0.90
self.MarshalCall.CLEAREDFORRECOVERY.duration=1.25
self.MarshalCall.DECKCLOSED.duration=1.10
self.MarshalCall.DEGREES.duration=0.60
self.MarshalCall.EXPECTED.duration=0.55
self.MarshalCall.FLYNEEDLES.duration=0.90
self.MarshalCall.HOLDATANGELS.duration=1.10
self.MarshalCall.HOURS.duration=0.60
self.MarshalCall.MARSHALRADIAL.duration=1.10
self.MarshalCall.N0.duration=0.40
self.MarshalCall.N1.duration=0.25
self.MarshalCall.N2.duration=0.37
self.MarshalCall.N3.duration=0.37
self.MarshalCall.N4.duration=0.39
self.MarshalCall.N5.duration=0.39
self.MarshalCall.N6.duration=0.40
self.MarshalCall.N7.duration=0.40
self.MarshalCall.N8.duration=0.37
self.MarshalCall.N9.duration=0.40
self.MarshalCall.NEGATIVE.duration=0.80
self.MarshalCall.NEWFB.duration=1.35
self.MarshalCall.OPS.duration=0.48
self.MarshalCall.POINT.duration=0.33
self.MarshalCall.RADIOCHECK.duration=1.20
self.MarshalCall.RECOVERY.duration=0.70
self.MarshalCall.RECOVERYOPSSTOPPED.duration=1.65
self.MarshalCall.RECOVERYPAUSEDNOTICE.duration=2.9
self.MarshalCall.RECOVERYPAUSEDRESUMED.duration=3.40
self.MarshalCall.REPORTSEEME.duration=0.95
self.MarshalCall.RESUMERECOVERY.duration=1.75
self.MarshalCall.ROGER.duration=0.53
self.MarshalCall.SAYNEEDLES.duration=0.90
self.MarshalCall.STACKFULL.duration=6.35
self.MarshalCall.STARTINGRECOVERY.duration=2.65
end
function AIRBOSS:_InitVoiceOvers()
self.LSOCall={
BOLTER={file="LSO-BolterBolter",suffix="ogg",loud=false,subtitle="Bolter, Bolter",duration=0.75,subduration=5},
CALLTHEBALL={file="LSO-CallTheBall",suffix="ogg",loud=false,subtitle="Call the ball",duration=0.6,subduration=2},
CHECK={file="LSO-Check",suffix="ogg",loud=false,subtitle="Check",duration=0.45,subduration=2.5},
CLEAREDTOLAND={file="LSO-ClearedToLand",suffix="ogg",loud=false,subtitle="Cleared to land",duration=1.0,subduration=5},
COMELEFT={file="LSO-ComeLeft",suffix="ogg",loud=true,subtitle="Come left",duration=0.60,subduration=1},
RADIOCHECK={file="LSO-RadioCheck",suffix="ogg",loud=false,subtitle="Paddles, radio check",duration=1.1,subduration=5},
RIGHTFORLINEUP={file="LSO-RightForLineup",suffix="ogg",loud=true,subtitle="Right for line up",duration=0.80,subduration=1},
HIGH={file="LSO-High",suffix="ogg",loud=true,subtitle="You're high",duration=0.65,subduration=1},
LOW={file="LSO-Low",suffix="ogg",loud=true,subtitle="You're low",duration=0.50,subduration=1},
POWER={file="LSO-Power",suffix="ogg",loud=true,subtitle="Power",duration=0.50,subduration=1},
SLOW={file="LSO-Slow",suffix="ogg",loud=true,subtitle="You're slow",duration=0.65,subduration=1},
FAST={file="LSO-Fast",suffix="ogg",loud=true,subtitle="You're fast",duration=0.70,subduration=1},
ROGERBALL={file="LSO-RogerBall",suffix="ogg",loud=false,subtitle="Roger ball",duration=1.00,subduration=2},
WAVEOFF={file="LSO-WaveOff",suffix="ogg",loud=false,subtitle="Wave off",duration=0.6,subduration=5},
LONGINGROOVE={file="LSO-LongInTheGroove",suffix="ogg",loud=false,subtitle="You're long in the groove",duration=1.2,subduration=5},
FOULDECK={file="LSO-FoulDeck",suffix="ogg",loud=false,subtitle="Foul deck",duration=0.62,subduration=5},
DEPARTANDREENTER={file="LSO-DepartAndReenter",suffix="ogg",loud=false,subtitle="Depart and re-enter",duration=1.1,subduration=5},
PADDLESCONTACT={file="LSO-PaddlesContact",suffix="ogg",loud=false,subtitle="Paddles, contact",duration=1.0,subduration=5},
WELCOMEABOARD={file="LSO-WelcomeAboard",suffix="ogg",loud=false,subtitle="Welcome aboard",duration=1.0,subduration=5},
EXPECTHEAVYWAVEOFF={file="LSO-ExpectHeavyWaveoff",suffix="ogg",loud=false,subtitle="Expect heavy waveoff",duration=1.2,subduration=5},
EXPECTSPOT75={file="LSO-ExpectSpot75",suffix="ogg",loud=false,subtitle="Expect spot 7.5",duration=2.0,subduration=5},
EXPECTSPOT5={file="LSO-ExpectSpot5",suffix="ogg",loud=false,subtitle="Expect spot 5",duration=1.3,subduration=5},
STABILIZED={file="LSO-Stabilized",suffix="ogg",loud=false,subtitle="Stabilized",duration=0.9,subduration=5},
IDLE={file="LSO-Idle",suffix="ogg",loud=false,subtitle="Idle",duration=0.45,subduration=5},
N0={file="LSO-N0",suffix="ogg",loud=false,subtitle="",duration=0.40},
N1={file="LSO-N1",suffix="ogg",loud=false,subtitle="",duration=0.25},
N2={file="LSO-N2",suffix="ogg",loud=false,subtitle="",duration=0.37},
N3={file="LSO-N3",suffix="ogg",loud=false,subtitle="",duration=0.37},
N4={file="LSO-N4",suffix="ogg",loud=false,subtitle="",duration=0.39},
N5={file="LSO-N5",suffix="ogg",loud=false,subtitle="",duration=0.39},
N6={file="LSO-N6",suffix="ogg",loud=false,subtitle="",duration=0.40},
N7={file="LSO-N7",suffix="ogg",loud=false,subtitle="",duration=0.40},
N8={file="LSO-N8",suffix="ogg",loud=false,subtitle="",duration=0.37},
N9={file="LSO-N9",suffix="ogg",loud=false,subtitle="",duration=0.40},
CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35},
NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6},
SPINIT={file="AIRBOSS-SpinIt",suffix="ogg",loud=false,subtitle="",duration=0.73,subduration=5},
}
self.PilotCall={
N0={file="PILOT-N0",suffix="ogg",loud=false,subtitle="",duration=0.40},
N1={file="PILOT-N1",suffix="ogg",loud=false,subtitle="",duration=0.25},
N2={file="PILOT-N2",suffix="ogg",loud=false,subtitle="",duration=0.37},
N3={file="PILOT-N3",suffix="ogg",loud=false,subtitle="",duration=0.37},
N4={file="PILOT-N4",suffix="ogg",loud=false,subtitle="",duration=0.39},
N5={file="PILOT-N5",suffix="ogg",loud=false,subtitle="",duration=0.39},
N6={file="PILOT-N6",suffix="ogg",loud=false,subtitle="",duration=0.40},
N7={file="PILOT-N7",suffix="ogg",loud=false,subtitle="",duration=0.40},
N8={file="PILOT-N8",suffix="ogg",loud=false,subtitle="",duration=0.37},
N9={file="PILOT-N9",suffix="ogg",loud=false,subtitle="",duration=0.40},
POINT={file="PILOT-Point",suffix="ogg",loud=false,subtitle="",duration=0.33},
SKYHAWK={file="PILOT-Skyhawk",suffix="ogg",loud=false,subtitle="",duration=0.95,subduration=5},
HARRIER={file="PILOT-Harrier",suffix="ogg",loud=false,subtitle="",duration=0.58,subduration=5},
HAWKEYE={file="PILOT-Hawkeye",suffix="ogg",loud=false,subtitle="",duration=0.63,subduration=5},
TOMCAT={file="PILOT-Tomcat",suffix="ogg",loud=false,subtitle="",duration=0.66,subduration=5},
HORNET={file="PILOT-Hornet",suffix="ogg",loud=false,subtitle="",duration=0.56,subduration=5},
VIKING={file="PILOT-Viking",suffix="ogg",loud=false,subtitle="",duration=0.61,subduration=5},
GREYHOUND={file="PILOT-Greyhound",suffix="ogg",loud=false,subtitle="",duration=0.61,subduration=5},
BALL={file="PILOT-Ball",suffix="ogg",loud=false,subtitle="",duration=0.50,subduration=5},
BINGOFUEL={file="PILOT-BingoFuel",suffix="ogg",loud=false,subtitle="",duration=0.80},
GASATDIVERT={file="PILOT-GasAtDivert",suffix="ogg",loud=false,subtitle="",duration=1.80},
GASATTANKER={file="PILOT-GasAtTanker",suffix="ogg",loud=false,subtitle="",duration=1.95},
}
self.MarshalCall={
AFFIRMATIVE={file="MARSHAL-Affirmative",suffix="ogg",loud=false,subtitle="",duration=0.90},
ALTIMETER={file="MARSHAL-Altimeter",suffix="ogg",loud=false,subtitle="",duration=0.85},
BRC={file="MARSHAL-BRC",suffix="ogg",loud=false,subtitle="",duration=0.80},
CARRIERTURNTOHEADING={file="MARSHAL-CarrierTurnToHeading",suffix="ogg",loud=false,subtitle="",duration=2.48,subduration=5},
CASE={file="MARSHAL-Case",suffix="ogg",loud=false,subtitle="",duration=0.40},
CHARLIETIME={file="MARSHAL-CharlieTime",suffix="ogg",loud=false,subtitle="",duration=0.90},
CLEAREDFORRECOVERY={file="MARSHAL-ClearedForRecovery",suffix="ogg",loud=false,subtitle="",duration=1.25},
DECKCLOSED={file="MARSHAL-DeckClosed",suffix="ogg",loud=false,subtitle="",duration=1.10,subduration=5},
DEGREES={file="MARSHAL-Degrees",suffix="ogg",loud=false,subtitle="",duration=0.60},
EXPECTED={file="MARSHAL-Expected",suffix="ogg",loud=false,subtitle="",duration=0.55},
FLYNEEDLES={file="MARSHAL-FlyYourNeedles",suffix="ogg",loud=false,subtitle="Fly your needles",duration=0.9,subduration=5},
HOLDATANGELS={file="MARSHAL-HoldAtAngels",suffix="ogg",loud=false,subtitle="",duration=1.10},
HOURS={file="MARSHAL-Hours",suffix="ogg",loud=false,subtitle="",duration=0.60,subduration=5},
MARSHALRADIAL={file="MARSHAL-MarshalRadial",suffix="ogg",loud=false,subtitle="",duration=1.10},
N0={file="MARSHAL-N0",suffix="ogg",loud=false,subtitle="",duration=0.40},
N1={file="MARSHAL-N1",suffix="ogg",loud=false,subtitle="",duration=0.25},
N2={file="MARSHAL-N2",suffix="ogg",loud=false,subtitle="",duration=0.37},
N3={file="MARSHAL-N3",suffix="ogg",loud=false,subtitle="",duration=0.37},
N4={file="MARSHAL-N4",suffix="ogg",loud=false,subtitle="",duration=0.39},
N5={file="MARSHAL-N5",suffix="ogg",loud=false,subtitle="",duration=0.39},
N6={file="MARSHAL-N6",suffix="ogg",loud=false,subtitle="",duration=0.40},
N7={file="MARSHAL-N7",suffix="ogg",loud=false,subtitle="",duration=0.40},
N8={file="MARSHAL-N8",suffix="ogg",loud=false,subtitle="",duration=0.37},
N9={file="MARSHAL-N9",suffix="ogg",loud=false,subtitle="",duration=0.40},
NEGATIVE={file="MARSHAL-Negative",suffix="ogg",loud=false,subtitle="",duration=0.80,subduration=5},
NEWFB={file="MARSHAL-NewFB",suffix="ogg",loud=false,subtitle="",duration=1.35},
OPS={file="MARSHAL-Ops",suffix="ogg",loud=false,subtitle="",duration=0.48},
POINT={file="MARSHAL-Point",suffix="ogg",loud=false,subtitle="",duration=0.33},
RADIOCHECK={file="MARSHAL-RadioCheck",suffix="ogg",loud=false,subtitle="Radio check",duration=1.20,subduration=5},
RECOVERY={file="MARSHAL-Recovery",suffix="ogg",loud=false,subtitle="",duration=0.70,subduration=5},
RECOVERYOPSSTOPPED={file="MARSHAL-RecoveryOpsStopped",suffix="ogg",loud=false,subtitle="",duration=1.65,subduration=5},
RECOVERYPAUSEDNOTICE={file="MARSHAL-RecoveryPausedNotice",suffix="ogg",loud=false,subtitle="aircraft recovery paused until further notice",duration=2.90,subduration=5},
RECOVERYPAUSEDRESUMED={file="MARSHAL-RecoveryPausedResumed",suffix="ogg",loud=false,subtitle="",duration=3.40,subduration=5},
REPORTSEEME={file="MARSHAL-ReportSeeMe",suffix="ogg",loud=false,subtitle="",duration=0.95},
RESUMERECOVERY={file="MARSHAL-ResumeRecovery",suffix="ogg",loud=false,subtitle="resuming aircraft recovery",duration=1.75,subduraction=5},
ROGER={file="MARSHAL-Roger",suffix="ogg",loud=false,subtitle="",duration=0.53,subduration=5},
SAYNEEDLES={file="MARSHAL-SayNeedles",suffix="ogg",loud=false,subtitle="Say needles",duration=0.90,subduration=5},
STACKFULL={file="MARSHAL-StackFull",suffix="ogg",loud=false,subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instructions",duration=6.35,subduration=10},
STARTINGRECOVERY={file="MARSHAL-StartingRecovery",suffix="ogg",loud=false,subtitle="",duration=2.65,subduration=5},
CLICK={file="AIRBOSS-RadioClick",suffix="ogg",loud=false,subtitle="",duration=0.35},
NOISE={file="AIRBOSS-Noise",suffix="ogg",loud=false,subtitle="",duration=3.6},
}
self:SetVoiceOversLSOByRaynor()
self:SetVoiceOversMarshalByRaynor()
end
function AIRBOSS:SetVoiceOver(radiocall,duration,subtitle,subduration,filename,suffix)
radiocall.duration=duration
radiocall.subtitle=subtitle or radiocall.subtitle
radiocall.file=filename
radiocall.suffix=suffix or".ogg"
end
function AIRBOSS:_GetAircraftAoA(playerData)
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER
local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
local aoa={}
if hornet then
aoa.SLOW=9.8
aoa.Slow=9.3
aoa.OnSpeedMax=8.8
aoa.OnSpeed=8.1
aoa.OnSpeedMin=7.4
aoa.Fast=6.9
aoa.FAST=6.3
elseif tomcat then
aoa.SLOW=self:_AoAUnit2Deg(playerData,17.0)
aoa.Slow=self:_AoAUnit2Deg(playerData,16.0)
aoa.OnSpeedMax=self:_AoAUnit2Deg(playerData,15.5)
aoa.OnSpeed=self:_AoAUnit2Deg(playerData,15.0)
aoa.OnSpeedMin=self:_AoAUnit2Deg(playerData,14.5)
aoa.Fast=self:_AoAUnit2Deg(playerData,14.0)
aoa.FAST=self:_AoAUnit2Deg(playerData,13.0)
elseif goshawk then
aoa.SLOW=8.00
aoa.Slow=7.75
aoa.OnSpeedMax=7.25
aoa.OnSpeed=7.00
aoa.OnSpeedMin=6.75
aoa.Fast=6.25
aoa.FAST=6.00
elseif skyhawk then
aoa.SLOW=9.50
aoa.Slow=9.25
aoa.OnSpeedMax=9.00
aoa.OnSpeed=8.75
aoa.OnSpeedMin=8.50
aoa.Fast=8.25
aoa.FAST=8.00
elseif harrier then
aoa.SLOW=16.0
aoa.Slow=13.5
aoa.OnSpeedMax=12.5
aoa.OnSpeed=10.0
aoa.OnSpeedMin=9.5
aoa.Fast=8.0
aoa.FAST=7.5
end
return aoa
end
function AIRBOSS:_AoAUnit2Deg(playerData,aoaunits)
local degrees=aoaunits
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
degrees=-10+50/30*aoaunits
degrees=0.918*aoaunits-3.411
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
degrees=0.5*aoaunits
end
return degrees
end
function AIRBOSS:_AoADeg2Units(playerData,degrees)
local aoaunits=degrees
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
aoaunits=(degrees+10)*30/50
aoaunits=1.089*degrees+3.715
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
aoaunits=2*degrees
end
return aoaunits
end
function AIRBOSS:_GetAircraftParameters(playerData,step)
step=step or playerData.step
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
local alt
local aoa
local dist
local speed
local aoaac=self:_GetAircraftAoA(playerData)
if step==AIRBOSS.PatternStep.PLATFORM then
alt=UTILS.FeetToMeters(5000)
speed=UTILS.KnotsToMps(250)
elseif step==AIRBOSS.PatternStep.ARCIN then
if tomcat then
speed=UTILS.KnotsToMps(150)
else
speed=UTILS.KnotsToMps(250)
end
elseif step==AIRBOSS.PatternStep.ARCOUT then
if tomcat then
speed=UTILS.KnotsToMps(150)
else
speed=UTILS.KnotsToMps(250)
end
elseif step==AIRBOSS.PatternStep.DIRTYUP then
alt=UTILS.FeetToMeters(1200)
elseif step==AIRBOSS.PatternStep.BULLSEYE then
alt=UTILS.FeetToMeters(1200)
dist=-UTILS.NMToMeters(3)
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.INITIAL then
if hornet or tomcat or harrier then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(350)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
end
elseif step==AIRBOSS.PatternStep.BREAKENTRY then
if hornet or tomcat or harrier then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(350)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
speed=UTILS.KnotsToMps(250)
elseif goshawk then
alt=UTILS.FeetToMeters(800)
speed=UTILS.KnotsToMps(300)
end
elseif step==AIRBOSS.PatternStep.EARLYBREAK then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
end
elseif step==AIRBOSS.PatternStep.LATEBREAK then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(800)
elseif skyhawk then
alt=UTILS.FeetToMeters(600)
end
elseif step==AIRBOSS.PatternStep.ABEAM then
if hornet or tomcat or harrier or goshawk then
alt=UTILS.FeetToMeters(600)
elseif skyhawk then
alt=UTILS.FeetToMeters(500)
end
aoa=aoaac.OnSpeed
if goshawk then
dist=UTILS.NMToMeters(0.9)
elseif harrier then
dist=UTILS.NMToMeters(0.9)
else
dist=UTILS.NMToMeters(1.1)
end
elseif step==AIRBOSS.PatternStep.NINETY then
if hornet or tomcat then
alt=UTILS.FeetToMeters(500)
elseif goshawk then
alt=UTILS.FeetToMeters(450)
elseif skyhawk then
alt=UTILS.FeetToMeters(500)
elseif harrier then
alt=UTILS.FeetToMeters(425)
end
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.WAKE then
if hornet or goshawk then
alt=UTILS.FeetToMeters(370)
elseif tomcat then
alt=UTILS.FeetToMeters(430)
elseif skyhawk then
alt=UTILS.FeetToMeters(370)
end
aoa=aoaac.OnSpeed
elseif step==AIRBOSS.PatternStep.FINAL then
if hornet or goshawk then
alt=UTILS.FeetToMeters(300)
elseif tomcat then
alt=UTILS.FeetToMeters(360)
elseif skyhawk then
alt=UTILS.FeetToMeters(300)
elseif harrier then
alt=UTILS.FeetToMeters(312)
end
aoa=aoaac.OnSpeed
end
return alt,aoa,dist,speed
end
function AIRBOSS:_GetNextMarshalFight()
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
local stack=flight.flag
local Tmarshal=timer.getAbsTime()-flight.time
local TmarshalMin=2*60
if flight.ai then
TmarshalMin=3*60
end
if flight.holding~=nil and Tmarshal>=TmarshalMin then
if flight.case==1 and stack==1 or flight.case>1 then
if flight.ai then
return flight
else
if flight.step~=AIRBOSS.PatternStep.COMMENCING then
return flight
end
end
end
end
end
return nil
end
function AIRBOSS:_CheckQueue()
if self.Debug then
self:_PrintQueue(self.flights,"All Flights")
end
self:_PrintQueue(self.Qmarshal,"Marshal")
self:_PrintQueue(self.Qpattern,"Pattern")
self:_PrintQueue(self.Qwaiting,"Waiting")
self:_PrintQueue(self.Qspinning,"Spinning")
if self.case>1 then
for _,_flight in pairs(self.Qwaiting)do
local flight=_flight
local removed=self:_RemoveFlightFromQueue(self.Qwaiting,flight)
if removed then
local stack=self:_GetFreeStack(flight.ai)
self:T(self.lid..string.format("Moving flight %s onboard %s from Waiting queue to Case %d Marshal stack %d",flight.groupname,flight.onboard,self.case,stack))
if flight.ai then
self:_MarshalAI(flight,stack)
else
self:_MarshalPlayer(flight,stack)
end
break
end
end
end
if not self:IsRecovering()then
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if(flight.case==1 and self.case>1)or(flight.case>1 and self.case==1)then
local removed=self:_RemoveFlightFromQueue(self.Qmarshal,flight)
if removed then
local stack=self:_GetFreeStack(flight.ai)
self:T(self.lid..string.format("Moving flight %s onboard %s from Marshal Case %d ==> %d Marshal stack %d",flight.groupname,flight.onboard,flight.case,self.case,stack))
if flight.ai then
self:_MarshalAI(flight,stack)
else
self:_MarshalPlayer(flight,stack)
end
break
elseif flight.case~=self.case then
flight.case=self.case
end
end
end
return
end
local _,npattern=self:_GetQueueInfo(self.Qpattern)
local _,nspinning=self:_GetQueueInfo(self.Qspinning)
local marshalflight=self:_GetNextMarshalFight()
if marshalflight and npattern<self.Nmaxpattern and nspinning==0 then
local Tmarshal=timer.getAbsTime()-marshalflight.time
self:T(self.lid..string.format("Marshal time of next group %s = %d seconds",marshalflight.groupname,Tmarshal))
local Tpattern=9999
local npunits=1
local pcase=1
if npattern>0 then
local patternflight=self.Qpattern[#self.Qpattern]
pcase=patternflight.case
local npunits=self:_GetFlightUnits(patternflight,false)
Tpattern=timer.getAbsTime()-patternflight.time
self:T(self.lid..string.format("Pattern time of last group %s = %d seconds. # of units=%d.",patternflight.groupname,Tpattern,npunits))
end
local TpatternMin
if pcase==1 then
TpatternMin=2*60*npunits
else
TpatternMin=2*60*npunits
end
if Tpattern>TpatternMin then
self:T(self.lid..string.format("Sending marshal flight %s to pattern.",marshalflight.groupname))
self:_ClearForLanding(marshalflight)
end
end
end
function AIRBOSS:_ClearForLanding(flight)
if flight.ai then
self:_RemoveFlightFromMarshalQueue(flight,false)
self:_LandAI(flight)
self:_MarshalCallClearedForRecovery(flight.onboard,flight.case)
if self.xtVoiceOversAI then
local leader=flight.group:GetUnits()[1]
self:_CommencingCall(leader,flight.onboard)
end
else
if flight.step~=AIRBOSS.PatternStep.COMMENCING then
self:_MarshalCallClearedForRecovery(flight.onboard,flight.case)
flight.time=timer.getAbsTime()
end
self:_SetPlayerStep(flight,AIRBOSS.PatternStep.COMMENCING,3)
end
end
function AIRBOSS:_SetPlayerStep(playerData,step,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,self._SetPlayerStep,self,playerData,step)
else
if playerData then
playerData.step=step
playerData.warning=nil
self:_StepHint(playerData)
end
end
end
function AIRBOSS:_ScanCarrierZone()
local coord=self:GetCoordinate()
local RCCZ=self.zoneCCA:GetRadius()
self:T(self.lid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.",UTILS.MetersToNM(RCCZ)))
local _,_,_,unitscan=coord:ScanObjects(RCCZ,true,false,false)
local insideCCA={}
for _,_unit in pairs(unitscan)do
local unit=_unit
local airborne=unit:IsAir()
local inzone=unit:IsInZone(self.zoneCCA)
local friendly=self:GetCoalition()==unit:GetCoalition()
local carrierac=self:_IsCarrierAircraft(unit)
if airborne and inzone and friendly and carrierac then
local group=unit:GetGroup()
local groupname=group:GetName()
if insideCCA[groupname]==nil then
insideCCA[groupname]=group
end
end
end
for groupname,_group in pairs(insideCCA)do
local group=_group
local knownflight=self:_GetFlightFromGroupInQueue(group,self.flights)
local actype=group:GetTypeName()
if knownflight then
self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.",groupname,actype))
if knownflight.ai and knownflight.flag==-100 and self.handleai then
local putintomarshal=false
local flight=_DATABASE:GetOpsGroup(groupname)
if flight and flight:IsInbound()and flight.destbase:GetName()==self.carrier:GetName()then
if flight.ishelo then
else
putintomarshal=true
end
flight.airboss=self
end
if putintomarshal then
local stack=self:_GetFreeStack(knownflight.ai)
local respawn=self.respawnAI
if stack then
self:_MarshalAI(knownflight,stack,respawn)
else
if not self:_InQueue(self.Qwaiting,knownflight.group)then
self:_WaitAI(knownflight,respawn)
end
end
break
end
end
else
if not self:_IsHuman(group)then
self:_CreateFlightGroup(group)
end
end
end
local remove={}
for _,_flight in pairs(self.flights)do
local flight=_flight
if insideCCA[flight.groupname]==nil then
if flight.ai and not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then
table.insert(remove,flight)
end
end
end
for _,flight in pairs(remove)do
self:_RemoveFlightFromQueue(self.flights,flight)
end
end
function AIRBOSS:_WaitPlayer(playerData)
if playerData then
local nwaiting=#self.Qwaiting
self:_MarshalCallStackFull(playerData.onboard,nwaiting)
table.insert(self.Qwaiting,playerData)
playerData.time=timer.getAbsTime()
playerData.step=AIRBOSS.PatternStep.WAITING
playerData.warning=nil
for _,_flight in pairs(playerData.section)do
local flight=_flight
flight.step=AIRBOSS.PatternStep.WAITING
flight.time=timer.getAbsTime()
flight.warning=nil
end
end
end
function AIRBOSS:_MarshalPlayer(playerData,stack)
if playerData then
self:_AddMarshalGroup(playerData,stack)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.HOLDING)
playerData.holding=nil
for _,_flight in pairs(playerData.section)do
local flight=_flight
self:_SetPlayerStep(flight,AIRBOSS.PatternStep.HOLDING)
flight.holding=nil
flight.case=playerData.case
flight.flag=stack
self:Marshal(flight)
end
else
self:E(self.lid.."ERROR: Could not add player to Marshal stack! playerData=nil")
end
end
function AIRBOSS:_WaitAI(flight,respawn)
flight.flag=-99
table.insert(self.Qwaiting,flight)
local group=flight.group
local groupname=flight.groupname
local speedOrbitMps=UTILS.KnotsToMps(274)
local speedOrbitKmh=UTILS.KnotsToKmph(274)
local speedTransit=UTILS.KnotsToKmph(370)
local cv=self:GetCoordinate()
local fc=group:GetCoordinate()
local hdg=self:GetHeading(false)
local hdgto=cv:HeadingTo(fc)
local angels=math.random(6,10)
local altitude=UTILS.FeetToMeters(angels*1000)
local p0=cv:Translate(UTILS.NMToMeters(11),hdgto):Translate(UTILS.NMToMeters(5),hdg):SetAltitude(altitude)
local wp={}
wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position")
local taskorbit=group:TaskOrbit(p0,altitude,speedOrbitMps)
wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Waiting Orbit at Angels %d",angels))
if self.Debug then
p0:MarkToAll(string.format("Waiting Orbit of flight %s at Angels %s",groupname,angels))
end
if respawn then
local Template=group:GetTemplate()
Template.route.points=wp
group=group:Respawn(Template,true)
end
group:WayPointInitialize(wp)
group:Route(wp,1)
end
function AIRBOSS:_MarshalAI(flight,nstack,respawn)
self:F2({flight=flight,nstack=nstack,respawn=respawn})
if flight==nil or flight.group==nil then
self:E(self.lid.."ERROR: flight or flight.group is nil.")
return
end
if flight.group:GetCoordinate()==nil then
self:E(self.lid.."ERROR: cannot get coordinate of flight group.")
return
end
if not self:_InQueue(self.Qmarshal,flight.group)then
if self.xtVoiceOversAI then
local leader=flight.group:GetUnits()[1]
self:_MarshallInboundCall(leader,flight.onboard)
end
self:_AddMarshalGroup(flight,nstack)
end
local case=flight.case
local ostack=flight.flag
local group=flight.group
local groupname=flight.groupname
flight.flag=nstack
local Carrier=self:GetCoordinate()
local hdg=self:GetHeading()
local speedOrbitMps=UTILS.KnotsToMps(274)
local speedOrbitKmh=UTILS.KnotsToKmph(274)
local speedTransit=UTILS.KnotsToKmph(370)
local altitude
local p0
local p1
local p2
altitude,p1,p2=self:_GetMarshalAltitude(nstack,case)
local wp={}
if not flight.holding then
self:T(self.lid..string.format("Guiding AI flight %s to marshal stack %d-->%d.",groupname,ostack,nstack))
wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedTransit,{},"Current Position")
local TaskArrivedHolding=flight.group:TaskFunction("AIRBOSS._ReachedHoldingZone",self,flight)
if case==1 then
local pE=Carrier:Translate(UTILS.NMToMeters(7),hdg-30):SetAltitude(altitude)
p0=Carrier:Translate(UTILS.NMToMeters(5),hdg-135):SetAltitude(altitude)
wp[#wp+1]=pE:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case I Marshal Pattern")
else
local radial=self:GetRadial(case,false,true)
p0=p2:Translate(UTILS.NMToMeters(5),radial+90,true):Translate(UTILS.NMToMeters(5),radial,true)
wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedTransit,{TaskArrivedHolding},"Entering Case II/III Marshal Pattern")
end
else
self:T(self.lid..string.format("Updating AI flight %s at marshal stack %d-->%d.",groupname,ostack,nstack))
wp[1]=group:GetCoordinate():WaypointAirTurningPoint(nil,speedOrbitKmh,{},"Current Position")
p0=group:GetCoordinate():Translate(UTILS.NMToMeters(0.2),group:GetHeading(),true)
end
local taskorbit=group:TaskOrbit(p1,altitude,speedOrbitMps,p2)
wp[#wp+1]=p0:WaypointAirTurningPoint(nil,speedOrbitKmh,{taskorbit},string.format("Marshal Orbit Stack %d",nstack))
if self.Debug then
p0:MarkToAll("WP P0 "..groupname)
p1:MarkToAll("RT P1 "..groupname)
p2:MarkToAll("RT P2 "..groupname)
end
if respawn then
local Template=group:GetTemplate()
Template.route.points=wp
flight.group=group:Respawn(Template,true)
end
flight.group:WayPointInitialize(wp)
flight.group:Route(wp,1)
self:Marshal(flight)
end
function AIRBOSS:_RefuelAI(flight)
local wp={}
local CurrentSpeed=flight.group:GetVelocityKMH()
wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position")
local refuelac=false
local actype=flight.group:GetTypeName()
if actype==AIRBOSS.AircraftCarrier.AV8B or
actype==AIRBOSS.AircraftCarrier.F14A or
actype==AIRBOSS.AircraftCarrier.F14B or
actype==AIRBOSS.AircraftCarrier.F14A_AI or
actype==AIRBOSS.AircraftCarrier.HORNET or
actype==AIRBOSS.AircraftCarrier.RHINOE or
actype==AIRBOSS.AircraftCarrier.RHINOF or
actype==AIRBOSS.AircraftCarrier.GROWLER or
actype==AIRBOSS.AircraftCarrier.FA18C or
actype==AIRBOSS.AircraftCarrier.S3B or
actype==AIRBOSS.AircraftCarrier.S3BTANKER then
refuelac=true
end
local text=""
if self.tanker and refuelac then
local tankerpos=self.tanker.tanker:GetCoordinate()
local TaskRefuel=flight.group:TaskRefueling()
local TaskMarshal=flight.group:TaskFunction("AIRBOSS._TaskFunctionMarshalAI",self,flight)
wp[#wp+1]=tankerpos:WaypointAirTurningPoint(nil,CurrentSpeed,{TaskRefuel,TaskMarshal},"Refueling")
self:_MarshalCallGasAtTanker(flight.onboard)
else
local divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,self:GetCoalition())
if divertfield==nil then
divertfield=self:GetCoordinate():GetClosestAirbase(Airbase.Category.AIRDROME,0)
end
if divertfield then
local divertcoord=divertfield:GetCoordinate()
wp[#wp+1]=divertcoord:WaypointAirLanding(UTILS.KnotsToKmph(300),divertfield,{},"Divert Field")
self:_MarshalCallGasAtDivert(flight.onboard,divertfield:GetName())
local Template=flight.group:GetTemplate()
Template.route.points=wp
flight.group=flight.group:Respawn(Template,true)
else
self:E(self.lid..string.format("WARNING: No recovery tanker or divert field available for group %s.",flight.groupname))
flight.refueling=true
return
end
end
flight.group:WayPointInitialize(wp)
flight.group:Route(wp,1)
flight.refueling=true
end
function AIRBOSS:_LandAI(flight)
self:T(self.lid..string.format("Landing AI flight %s.",flight.groupname))
local Speed=UTILS.KnotsToKmph(200)
if flight.actype==AIRBOSS.AircraftCarrier.HORNET
or flight.actype==AIRBOSS.AircraftCarrier.FA18C
or flight.actype==AIRBOSS.AircraftCarrier.RHINOE
or flight.actype==AIRBOSS.AircraftCarrier.RHINOF
or flight.actype==AIRBOSS.AircraftCarrier.GROWLER then
Speed=UTILS.KnotsToKmph(200)
elseif flight.actype==AIRBOSS.AircraftCarrier.E2D or flight.actype==AIRBOSS.AircraftCarrier.C2A then
Speed=UTILS.KnotsToKmph(150)
elseif flight.actype==AIRBOSS.AircraftCarrier.F14A_AI or flight.actype==AIRBOSS.AircraftCarrier.F14A or flight.actype==AIRBOSS.AircraftCarrier.F14B then
Speed=UTILS.KnotsToKmph(175)
elseif flight.actype==AIRBOSS.AircraftCarrier.S3B or flight.actype==AIRBOSS.AircraftCarrier.S3BTANKER then
Speed=UTILS.KnotsToKmph(140)
end
local Carrier=self:GetCoordinate()
local hdg=self:GetHeading()
local wp={}
local CurrentSpeed=flight.group:GetVelocityKMH()
wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position")
local alt=UTILS.FeetToMeters(800)
wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4),hdg-160):SetAltitude(alt):WaypointAirLanding(Speed,self.airbase,nil,"Landing")
flight.group:WayPointInitialize(wp)
flight.group:Route(wp,0)
end
function AIRBOSS:_GetMarshalAltitude(stack,case)
if stack<=0 then
return 0,nil,nil
end
case=case or self.case
local Carrier=self:GetCoordinate()
local angels0
local Dist
local p1=nil
local p2=nil
local nstack=stack-1
if case==1 then
angels0=2
local hdg=self.carrier:GetHeading()
p1=Carrier
p2=Carrier:Translate(UTILS.NMToMeters(1.5),hdg)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
p1=Carrier:Translate(UTILS.NMToMeters(1.0),hdg+90)
p2=p1:Translate(2.5,hdg)
end
else
angels0=6
Dist=UTILS.NMToMeters(nstack+angels0+15)
local radial=self:GetRadial(case,false,true)
local l=UTILS.NMToMeters(10)
p1=Carrier:Translate(Dist+l,radial)
p2=Carrier:Translate(Dist,radial)
end
local altitude=UTILS.FeetToMeters((nstack+angels0)*1000)
p1:SetAltitude(altitude,true)
p2:SetAltitude(altitude,true)
return altitude,p1,p2
end
function AIRBOSS:_GetCharlieTime(flightgroup)
local stack=flightgroup.flag
if stack<=0 then
return nil
end
local Tnow=timer.getAbsTime()
local Tcharlie=0
local Trecovery=0
if self.recoverywindow then
Trecovery=math.max(self.recoverywindow.START-Tnow,0)
else
Trecovery=7*60
end
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
local mstack=flight.flag
local Tarrive=0
local Tholding=3*60
if stack>0 and mstack>0 and mstack<=stack then
if flight.holding==nil then
local holdingzone=self:_GetZoneHolding(flight.case,1):GetCoordinate()
local d0=holdingzone:Get2DDistance(flight.group:GetCoordinate())
local v0=flight.group:GetVelocityMPS()
Tarrive=d0/v0
self:T3(self.lid..string.format("Tarrive=%.1f seconds, Clock %s",Tarrive,UTILS.SecondsToClock(Tnow+Tarrive)))
else
if mstack==1 then
local tholding=timer.getAbsTime()-flight.time
Tholding=math.max(3*60-tholding,0)
end
end
local Tmin=math.max(Tarrive,Trecovery)
Tcharlie=math.max(Tmin,Tcharlie)+Tholding
end
end
Tcharlie=Tcharlie+Tnow
local text=string.format("Charlie time for flight %s (%s) %s",flightgroup.onboard,flightgroup.groupname,UTILS.SecondsToClock(Tcharlie))
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
return Tcharlie
end
function AIRBOSS:_AddMarshalGroup(flight,stack)
flight.flag=stack
flight.case=self.case
table.insert(self.Qmarshal,flight)
local P=UTILS.hPa2inHg(self:GetCoordinate():GetPressure())
local alt=self:_GetMarshalAltitude(stack,flight.case)
local brc=self:GetBRC()
if self.recoverywindow and self.recoverywindow.WIND then
brc=self:GetBRCintoWind(self.recoverywindow.SPEED)
end
flight.Tcharlie=self:_GetCharlieTime(flight)
local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie)
self:_MarshalCallArrived(flight.onboard,flight.case,brc,alt,Ccharlie,P)
if self.TACANon and(not flight.ai)and flight.difficulty==AIRBOSS.Difficulty.EASY then
local radial=self:GetRadial(flight.case,true,true,true)
if flight.case==1 then
radial=self:GetBRC()
end
local text=string.format("Select TACAN %03d°, channel %d%s (%s)",radial,self.TACANchannel,self.TACANmode,self.TACANmorse)
self:MessageToPlayer(flight,text,nil,"")
end
end
function AIRBOSS:_CollapseMarshalStack(flight,nopattern)
self:F2({flight=flight,nopattern=nopattern})
local case=flight.case
local stack=flight.flag
if stack<=0 then
self:E(self.lid..string.format("ERROR: Flight %s is has stack value %d<0. Cannot collapse stack!",flight.groupname,stack))
return
end
self.Tcollapse=timer.getTime()
for _,_flight in pairs(self.Qmarshal)do
local mflight=_flight
if(case==1 and mflight.case==1)then
local mstack=mflight.flag
if mstack>stack then
local newstack=self:_GetFreeStack(mflight.ai,mflight.case,true)
if newstack and newstack<mstack then
self:T(self.lid..string.format("Collapse Marshal: Flight %s (case %d) is changing marshal stack %d --> %d.",mflight.groupname,mflight.case,mstack,newstack))
if mflight.ai then
self:_MarshalAI(mflight,newstack)
else
mflight.flag=newstack
local angels=self:_GetAngels(self:_GetMarshalAltitude(newstack,case))
if mflight.difficulty~=AIRBOSS.Difficulty.HARD then
local text=string.format("descent to stack at Angels %d.",angels)
self:MessageToPlayer(mflight,text,"MARSHAL")
end
mflight.time=timer.getAbsTime()
for _,_sec in pairs(mflight.section)do
local sec=_sec
sec.flag=newstack
sec.time=timer.getAbsTime()
if sec.difficulty~=AIRBOSS.Difficulty.HARD then
local text=string.format("descent to stack at Angels %d.",angels)
self:MessageToPlayer(sec,text,"MARSHAL")
end
end
end
end
end
end
end
if nopattern then
self:T(self.lid..string.format("Flight %s is leaving stack but not going to pattern.",flight.groupname))
else
local Tmarshal=UTILS.SecondsToClock(timer.getAbsTime()-flight.time)
self:T(self.lid..string.format("Flight %s is leaving marshal after %s and going pattern.",flight.groupname,Tmarshal))
self:_AddFlightToPatternQueue(flight)
end
flight.flag=-1
flight.time=timer.getAbsTime()
end
function AIRBOSS:_GetFreeStack(ai,case,empty)
case=case or self.case
if case==1 then
return self:_GetFreeStack_Old(ai,case,empty)
end
local nmaxstacks=100
if case==1 then
nmaxstacks=self.Nmaxmarshal
end
local stack={}
for i=1,nmaxstacks do
stack[i]=self.NmaxStack
end
local nmax=1
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.case==case then
local n=flight.flag
if n>nmax then
nmax=n
end
if n>0 then
if flight.ai or flight.case>1 then
stack[n]=0
else
stack[n]=stack[n]-1
end
else
self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n))
end
end
end
local nfree=nil
if stack[nmax]==0 then
if case==1 then
if nmax>=nmaxstacks then
nfree=nil
else
nfree=nmax+1
end
else
nfree=nmax+1
end
elseif stack[nmax]==self.NmaxStack then
self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d",nmax,stack[nmax]))
nfree=nmax
else
if ai or empty or case>1 then
nfree=nmax+1
else
nfree=nmax
end
end
self:T(self.lid..string.format("Returning free stack %s",tostring(nfree)))
return nfree
end
function AIRBOSS:_GetFreeStack_Old(ai,case,empty)
case=case or self.case
local nmaxstacks=100
if case==1 then
nmaxstacks=self.Nmaxmarshal
end
local stack={}
for i=1,nmaxstacks do
stack[i]=self.NmaxStack
end
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.case==case then
local n=flight.flag
if n>0 then
if flight.ai or flight.case>1 then
stack[n]=0
else
stack[n]=stack[n]-1
end
else
self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n))
end
end
end
local nfree=nil
for i=1,nmaxstacks do
self:T2(self.lid..string.format("FF Stack[%d]=%d",i,stack[i]))
if ai or empty or case>1 then
if stack[i]==self.NmaxStack then
nfree=i
return i
end
else
if stack[i]>0 then
nfree=i
return i
end
end
end
return nfree
end
function AIRBOSS:_GetFlightUnits(flight,onground)
local inair=true
if onground==true then
inair=false
end
local function countunits(_group,inair)
local group=_group
local units=group:GetUnits()
local n=0
if units then
for _,_unit in pairs(units)do
local unit=_unit
if unit and unit:IsAlive()then
if inair then
if unit:InAir()then
self:T2(self.lid..string.format("Unit %s is in AIR",unit:GetName()))
n=n+1
end
else
n=n+1
end
end
end
end
return n
end
local nunits=countunits(flight.group,inair)
local nsection=0
for _,sec in pairs(flight.section)do
local secflight=sec
nsection=nsection+countunits(secflight.group,inair)
end
return nunits+nsection,nunits,nsection
end
function AIRBOSS:_GetQueueInfo(queue,case)
local ngroup=0
local Nunits=0
for _,_flight in pairs(queue)do
local flight=_flight
if case then
if(flight.case==case)or(case==23 and(flight.case==2 or flight.case==3))then
local ntot,nunits,nsection=self:_GetFlightUnits(flight)
Nunits=Nunits+ntot
if ntot>0 then
ngroup=ngroup+1
end
end
else
local ntot,nunits,nsection=self:_GetFlightUnits(flight)
Nunits=Nunits+ntot
if ntot>0 then
ngroup=ngroup+1
end
end
end
return ngroup,Nunits
end
function AIRBOSS:_PrintQueue(queue,name)
local Nqueue,nqueue=self:_GetQueueInfo(queue)
local text=string.format("%s Queue N=%d (#%d), n=%d:",name,Nqueue,#queue,nqueue)
if#queue==0 then
text=text.." empty."
else
for i,_flight in pairs(queue)do
local flight=_flight
local clock=UTILS.SecondsToClock(timer.getAbsTime()-flight.time)
local case=flight.case
local stack=flight.flag
local fuel=flight.group:GetFuelMin()*100
local ai=tostring(flight.ai)
local lead=flight.seclead
local Nsec=#flight.section
local actype=self:_GetACNickname(flight.actype)
local onboard=flight.onboard
local holding=tostring(flight.holding)
local _,nunits,nsec=self:_GetFlightUnits(flight,false)
text=text..string.format("\n[%d] %s*%d (%s): lead=%s (%d/%d), onboard=%s, flag=%d, case=%d, time=%s, fuel=%d, ai=%s, holding=%s",i,flight.groupname,nunits,actype,lead,nsec,Nsec,onboard,stack,case,clock,fuel,ai,holding)
if stack>0 then
local alt=UTILS.MetersToFeet(self:_GetMarshalAltitude(stack,case))
text=text..string.format(" stackalt=%d ft",alt)
end
for j,_element in pairs(flight.elements)do
local element=_element
text=text..string.format("\n  (%d) %s (%s): ai=%s, ballcall=%s, recovered=%s",j,element.onboard,element.unitname,tostring(element.ai),tostring(element.ballcall),tostring(element.recovered))
end
end
end
self:T(self.lid..text)
end
function AIRBOSS:_CreateFlightGroup(group)
self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.",group:GetName(),group:GetTypeName()))
local flight={}
if not self:_InQueue(self.flights,group)then
local groupname=group:GetName()
local human,playername=self:_IsHuman(group)
flight.group=group
flight.groupname=group:GetName()
flight.nunits=#group:GetUnits()
flight.time=timer.getAbsTime()
flight.dist0=group:GetCoordinate():Get2DDistance(self:GetCoordinate())
flight.flag=-100
flight.ai=not human
flight.actype=group:GetTypeName()
flight.onboardnumbers=self:_GetOnboardNumbers(group)
flight.seclead=flight.group:GetUnit(1):GetName()
flight.section={}
flight.ballcall=false
flight.refueling=false
flight.holding=nil
flight.name=flight.group:GetUnit(1):GetName()
flight.case=self.case
local text=string.format("Flight elements of group %s:",flight.groupname)
flight.elements={}
local units=group:GetUnits()
for i,_unit in pairs(units)do
local unit=_unit
local element={}
element.unit=unit
element.unitname=unit:GetName()
element.onboard=flight.onboardnumbers[element.unitname]
element.ballcall=false
element.ai=not self:_IsHumanUnit(unit)
element.recovered=nil
text=text..string.format("\n[%d] %s onboard #%s, AI=%s",i,element.unitname,tostring(element.onboard),tostring(element.ai))
table.insert(flight.elements,element)
end
self:T(self.lid..text)
if flight.ai then
local onboard=flight.onboardnumbers[flight.seclead]
flight.onboard=onboard
else
flight.onboard=self:_GetOnboardNumberPlayer(group)
end
table.insert(self.flights,flight)
else
self:E(self.lid..string.format("ERROR: Flight group %s already exists in self.flights!",group:GetName()))
return nil
end
return flight
end
function AIRBOSS:_NewPlayer(unitname)
local playerunit,playername=self:_GetPlayerUnitAndName(unitname)
if playerunit and playername then
local group=playerunit:GetGroup()
local playerData
playerData=self:_CreateFlightGroup(group)
if playerData then
playerData.unit=playerunit
playerData.unitname=unitname
playerData.name=playername
playerData.callsign=playerData.unit:GetCallsign()
playerData.client=CLIENT:FindByName(unitname,nil,true)
playerData.seclead=playername
playerData.passes=0
playerData.messages={}
playerData.lastdebrief=playerData.lastdebrief or{}
playerData.attitudemonitor=false
if playerData.trapon==nil then
playerData.trapon=self.trapsheet
end
playerData.difficulty=playerData.difficulty or self.defaultskill
if playerData.subtitles==nil then
playerData.subtitles=true
end
if playerData.showhints==nil then
if playerData.difficulty==AIRBOSS.Difficulty.HARD then
playerData.showhints=false
else
playerData.showhints=true
end
end
playerData.points={}
playerData=self:_InitPlayer(playerData)
self.players[playername]=playerData
self.playerscores[playername]=self.playerscores[playername]or{}
if self.welcome then
self:MessageToPlayer(playerData,string.format("Welcome, %s %s!",playerData.difficulty,playerData.name),string.format("AIRBOSS %s",self.alias),"",5)
end
end
return playerData
end
return nil
end
function AIRBOSS:_InitPlayer(playerData,step)
self:T(self.lid..string.format("Initializing player data for %s callsign %s.",playerData.name,playerData.callsign))
playerData.step=step or AIRBOSS.PatternStep.UNDEFINED
playerData.groove={}
playerData.debrief={}
playerData.trapsheet={}
playerData.warning=nil
playerData.holding=nil
playerData.refueling=false
playerData.valid=false
playerData.lig=false
playerData.wop=false
playerData.waveoff=false
playerData.wofd=false
playerData.owo=false
playerData.boltered=false
playerData.hover=false
playerData.stable=false
playerData.landed=false
playerData.Tlso=timer.getTime()
playerData.Tgroove=nil
playerData.TIG0=nil
playerData.wire=nil
playerData.flag=-100
playerData.debriefschedulerID=nil
if playerData.group:GetName():match("Groove")and playerData.passes==0 then
self:MessageToPlayer(playerData,"Group name contains \"Groove\". Happy groove testing.")
playerData.attitudemonitor=true
playerData.step=AIRBOSS.PatternStep.FINAL
self:_AddFlightToPatternQueue(playerData)
self.dTstatus=0.1
end
return playerData
end
function AIRBOSS:_GetFlightFromGroupInQueue(group,queue)
if group then
local name=group:GetName()
for i,_flight in pairs(queue)do
local flight=_flight
if flight.groupname==name then
return flight,i
end
end
self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.",name))
end
self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!"))
return nil,nil
end
function AIRBOSS:_GetFlightElement(unitname)
local unit=UNIT:FindByName(unitname)
if unit then
local flight=self:_GetFlightFromGroupInQueue(unit:GetGroup(),self.flights)
if flight then
for i,_element in pairs(flight.elements)do
local element=_element
if element.unit:GetName()==unitname then
return element,i,flight
end
end
self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.",unitname,flight.groupname))
end
end
return nil,nil,nil
end
function AIRBOSS:_RemoveFlightElement(unitname)
local element,idx,flight=self:_GetFlightElement(unitname)
if idx then
table.remove(flight.elements,idx)
return true
else
self:T("WARNING: Flight element could not be removed from flight group. Index=nil!")
return nil
end
end
function AIRBOSS:_InQueue(queue,group)
local name=group:GetName()
for _,_flight in pairs(queue)do
local flight=_flight
if name==flight.groupname then
return true
end
end
return false
end
function AIRBOSS:_RemoveDeadFlightGroups()
for i=#self.flight,1,-1 do
local flight=self.flights[i]
if not flight.group:IsAlive()then
self:T(string.format("Removing dead flight group %s from ALL flights table.",flight.groupname))
table.remove(self.flights,i)
end
end
for i=#self.Qmarshal,1,-1 do
local flight=self.Qmarshal[i]
if not flight.group:IsAlive()then
self:T(string.format("Removing dead flight group %s from Marshal Queue table.",flight.groupname))
table.remove(self.Qmarshal,i)
end
end
for i=#self.Qpattern,1,-1 do
local flight=self.Qpattern[i]
if not flight.group:IsAlive()then
self:T(string.format("Removing dead flight group %s from Pattern Queue table.",flight.groupname))
table.remove(self.Qpattern,i)
end
end
end
function AIRBOSS:_GetLeadFlight(flight)
local lead=flight
if flight.name~=flight.seclead then
lead=self.players[flight.seclead]
end
return lead
end
function AIRBOSS:_CheckSectionRecovered(flight)
if flight==nil then
return true
end
local lead=self:_GetLeadFlight(flight)
for _,_element in pairs(lead.elements)do
local element=_element
if not element.recovered then
return false
end
end
for _,_section in pairs(lead.section)do
local sectionmember=_section
for _,_element in pairs(sectionmember.elements)do
local element=_element
if not element.recovered then
return false
end
end
end
self:_RemoveFlightFromQueue(self.Qpattern,lead)
if self:_InQueue(self.Qmarshal,lead.group)then
self:E(self.lid..string.format("ERROR: lead flight group %s should not be in marshal queue",lead.groupname))
self:_RemoveFlightFromMarshalQueue(lead,true)
end
if self:_InQueue(self.Qwaiting,lead.group)then
self:E(self.lid..string.format("ERROR: lead flight group %s should not be in pattern queue",lead.groupname))
self:_RemoveFlightFromQueue(self.Qwaiting,lead)
end
return true
end
function AIRBOSS:_AddFlightToPatternQueue(flight)
table.insert(self.Qpattern,flight)
flight.flag=-1
flight.time=timer.getAbsTime()
flight.recovered=false
for _,elem in pairs(flight.elements)do
elem.recoverd=false
end
for _,sec in pairs(flight.section)do
sec.flag=-1
sec.time=timer.getAbsTime()
for _,elem in pairs(sec.elements)do
elem.recoverd=false
end
end
end
function AIRBOSS:_RecoveredElement(unit)
local element,idx,flight=self:_GetFlightElement(unit:GetName())
if element then
element.recovered=true
end
return flight
end
function AIRBOSS:_RemoveFlightFromMarshalQueue(flight,nopattern)
local removed,idx=self:_RemoveFlightFromQueue(self.Qmarshal,flight)
if removed then
flight.holding=nil
self:_CollapseMarshalStack(flight,nopattern)
if flight.case==1 and#self.Qwaiting>0 then
local nextflight=self.Qwaiting[1]
local freestack=self:_GetFreeStack(nextflight.ai)
if nextflight.ai then
self:_MarshalAI(nextflight,freestack)
else
self:_MarshalPlayer(nextflight,freestack)
end
self:_RemoveFlightFromQueue(self.Qwaiting,nextflight)
end
end
return removed,idx
end
function AIRBOSS:_RemoveFlightFromQueue(queue,flight)
for i,_flight in pairs(queue)do
local qflight=_flight
if qflight.groupname==flight.groupname then
self:T(self.lid..string.format("Removing flight group %s from queue.",flight.groupname))
table.remove(queue,i)
return true,i
end
end
return false,nil
end
function AIRBOSS:_RemoveUnitFromFlight(unit)
if unit and unit:IsInstanceOf("UNIT")then
local group=unit:GetGroup()
if group then
local flight=self:_GetFlightFromGroupInQueue(group,self.flights)
if flight then
local removed=self:_RemoveFlightElement(unit:GetName())
if removed then
local _,nunits=self:_GetFlightUnits(flight,not flight.ai)
local nelements=#flight.elements
self:T(self.lid..string.format("Removed unit %s: nunits=%d, nelements=%d",unit:GetName(),nunits,nelements))
if nunits==0 or nelements==0 then
self:_RemoveFlight(flight)
end
end
end
end
end
end
function AIRBOSS:_RemoveFlightFromSection(flight)
if flight.name~=flight.seclead then
local lead=self.players[flight.seclead]
if lead then
for i,sec in pairs(lead.section)do
local sectionmember=sec
if sectionmember.name==flight.name then
table.remove(lead.section,i)
break
end
end
end
end
end
function AIRBOSS:_UpdateFlightSection(flight)
if flight.seclead==flight.name then
if#flight.section>=1 then
local newlead=flight.section[1]
newlead.seclead=newlead.name
for i=2,#flight.section do
local member=flight.section[i]
table.insert(newlead.section,member)
member.seclead=newlead.name
end
end
flight.section={}
else
self:_RemoveFlightFromSection(flight)
end
end
function AIRBOSS:_RemoveFlight(flight,completely)
self:F(self.lid..string.format("Removing flight %s, ai=%s completely=%s.",tostring(flight.groupname),tostring(flight.ai),tostring(completely)))
self:_RemoveFlightFromMarshalQueue(flight,true)
self:_RemoveFlightFromQueue(self.Qpattern,flight)
self:_RemoveFlightFromQueue(self.Qwaiting,flight)
self:_RemoveFlightFromQueue(self.Qspinning,flight)
if flight.ai then
self:_RemoveFlightFromQueue(self.flights,flight)
else
local grades=self.playerscores[flight.name]
if grades and#grades>0 then
while#grades>0 and grades[#grades].finalscore==nil do
table.remove(grades,#grades)
end
end
if completely then
self:_UpdateFlightSection(flight)
self:_RemoveFlightFromQueue(self.flights,flight)
local playerdata=self.players[flight.name]
if playerdata then
self:T(self.lid..string.format("Removing player %s completely.",flight.name))
self.players[flight.name]=nil
end
flight=nil
else
self:_SetPlayerStep(flight,AIRBOSS.PatternStep.UNDEFINED)
for _,sectionmember in pairs(flight.section)do
self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.UNDEFINED)
self:_RemoveFlightFromQueue(self.Qspinning,sectionmember)
end
self:_RemoveFlightFromSection(flight)
end
end
end
function AIRBOSS:_CheckPlayerStatus()
for _playerName,_playerData in pairs(self.players)do
local playerData=_playerData
if playerData then
local unit=playerData.unit
if unit and unit:IsAlive()then
if unit:IsInZone(self.zoneCCA)then
if playerData.attitudemonitor then
self:_AttitudeMonitor(playerData)
end
self:_CheckPlayerPatternDistance(playerData)
self:_CheckFoulDeck(playerData)
if playerData.step==AIRBOSS.PatternStep.UNDEFINED then
elseif playerData.step==AIRBOSS.PatternStep.REFUELING then
elseif playerData.step==AIRBOSS.PatternStep.SPINNING then
self:_Spinning(playerData)
elseif playerData.step==AIRBOSS.PatternStep.HOLDING then
self:_Holding(playerData)
elseif playerData.step==AIRBOSS.PatternStep.WAITING then
self:_Waiting(playerData)
elseif playerData.step==AIRBOSS.PatternStep.COMMENCING then
self:_Commencing(playerData,true)
elseif playerData.step==AIRBOSS.PatternStep.BOLTER then
self:_BolterPattern(playerData)
elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then
self:_Platform(playerData)
elseif playerData.step==AIRBOSS.PatternStep.ARCIN then
self:_ArcInTurn(playerData)
elseif playerData.step==AIRBOSS.PatternStep.ARCOUT then
self:_ArcOutTurn(playerData)
elseif playerData.step==AIRBOSS.PatternStep.DIRTYUP then
self:_DirtyUp(playerData)
elseif playerData.step==AIRBOSS.PatternStep.BULLSEYE then
self:_Bullseye(playerData)
elseif playerData.step==AIRBOSS.PatternStep.INITIAL then
self:_Initial(playerData)
elseif playerData.step==AIRBOSS.PatternStep.BREAKENTRY then
self:_BreakEntry(playerData)
elseif playerData.step==AIRBOSS.PatternStep.EARLYBREAK then
self:_Break(playerData,AIRBOSS.PatternStep.EARLYBREAK)
elseif playerData.step==AIRBOSS.PatternStep.LATEBREAK then
self:_Break(playerData,AIRBOSS.PatternStep.LATEBREAK)
elseif playerData.step==AIRBOSS.PatternStep.ABEAM then
self:_Abeam(playerData)
elseif playerData.step==AIRBOSS.PatternStep.NINETY then
self:_CheckForLongDownwind(playerData)
self:_Ninety(playerData)
elseif playerData.step==AIRBOSS.PatternStep.WAKE then
self:_Wake(playerData)
elseif playerData.step==AIRBOSS.PatternStep.EMERGENCY then
self:_Final(playerData,true)
elseif playerData.step==AIRBOSS.PatternStep.FINAL then
self:_Final(playerData)
elseif playerData.step==AIRBOSS.PatternStep.GROOVE_XX or
playerData.step==AIRBOSS.PatternStep.GROOVE_IM or
playerData.step==AIRBOSS.PatternStep.GROOVE_IC or
playerData.step==AIRBOSS.PatternStep.GROOVE_AR or
playerData.step==AIRBOSS.PatternStep.GROOVE_AL or
playerData.step==AIRBOSS.PatternStep.GROOVE_LC or
playerData.step==AIRBOSS.PatternStep.GROOVE_IW then
self:_Groove(playerData)
elseif playerData.step==AIRBOSS.PatternStep.DEBRIEF then
playerData.debriefschedulerID=self:ScheduleOnce(5,self._Debrief,self,playerData)
playerData.step=AIRBOSS.PatternStep.UNDEFINED
else
self:E(self.lid..string.format("ERROR: Unknown player step %s. Please report!",tostring(playerData.step)))
end
self:_CheckMissedStepOnEntry(playerData)
else
self:T2(self.lid.."WARNING: Player unit not inside the CCA!")
end
else
self:T(self.lid.."WARNING: Player unit is not alive!")
end
end
end
end
function AIRBOSS:_CheckMissedStepOnEntry(playerData)
local rightcase=playerData.case>1
local rightqueue=self:_InQueue(self.Qpattern,playerData.group)
local rightflag=playerData.flag~=-42
local step=playerData.step
local missedstep=step==AIRBOSS.PatternStep.PLATFORM or step==AIRBOSS.PatternStep.ARCIN or step==AIRBOSS.PatternStep.ARCOUT or step==AIRBOSS.PatternStep.DIRTYUP
if rightcase and rightqueue and rightflag then
local zone=nil
if playerData.case==2 and missedstep then
zone=self:_GetZoneInitial(playerData.case)
elseif playerData.case==3 and missedstep then
zone=self:_GetZoneBullseye(playerData.case)
end
if zone then
local inzone=playerData.unit:IsInZone(zone)
local relheading=self:_GetRelativeHeading(playerData.unit,false)
if inzone and math.abs(relheading)<60 then
local text=string.format("you missed an important step in the pattern!\nYour next step would have been %s.",playerData.step)
self:MessageToPlayer(playerData,text,"AIRBOSS",nil,5)
if playerData.case==2 then
playerData.step=AIRBOSS.PatternStep.INITIAL
elseif playerData.case==3 then
playerData.step=AIRBOSS.PatternStep.BULLSEYE
end
playerData.flag=-42
end
end
end
end
function AIRBOSS:_SetTimeInGroove(playerData)
if playerData.TIG0 then
playerData.Tgroove=timer.getTime()-playerData.TIG0
else
playerData.Tgroove=999
end
end
function AIRBOSS:_GetTimeInGroove(playerData)
local Tgroove=999
if playerData.TIG0 then
Tgroove=timer.getTime()-playerData.TIG0
end
return Tgroove
end
function AIRBOSS:OnEventBirth(EventData)
self:F3({eventbirth=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil and(not EventData.IniObjectCategory==Object.Category.STATIC)then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
self:E(EventData)
return
end
if EventData.IniObjectCategory~=Object.Category.UNIT then return end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T(self.lid.."BIRTH: unit   = "..tostring(EventData.IniUnitName))
self:T(self.lid.."BIRTH: group  = "..tostring(EventData.IniGroupName))
self:T(self.lid.."BIRTH: player = "..tostring(_playername))
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _callsign=_unit:GetCallsign()
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.",_playername,_callsign,_unitName,_group:GetName())
self:T(self.lid..text)
MESSAGE:New(text,5):ToAllIf(self.Debug)
local rightaircraft=self:_IsCarrierAircraft(_unit)
if rightaircraft==false then
local text=string.format("Player aircraft type %s not supported by AIRBOSS class.",_unit:GetTypeName())
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:T2(self.lid..text)
return
end
if self:GetCoalition()~=_unit:GetCoalition()then
local text=string.format("Player entered aircraft of other coalition.")
MESSAGE:New(text,30):ToAllIf(self.Debug)
self:T(self.lid..text)
return
end
self:_AddF10Commands(_unitName)
self:ScheduleOnce(1,self._NewPlayer,self,_unitName)
end
end
function AIRBOSS:OnEventRunwayTouch(EventData)
self:F3({eventland=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event LAND!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event LAND!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T(self.lid.."LAND: unit   = "..tostring(EventData.IniUnitName))
self:T(self.lid.."LAND: group  = "..tostring(EventData.IniGroupName))
self:T(self.lid.."LAND: player = "..tostring(_playername))
local airbase=EventData.Place
if airbase==nil then
return
end
local airbasename=tostring(airbase:GetName())
if airbasename==self.airbase:GetName()then
local stern=self:_GetSternCoord()
local zoneCarrier=self:_GetZoneCarrierBox()
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _callsign=_unit:GetCallsign()
local text=string.format("Player %s, callsign %s unit %s (ID=%d) of group %s landed at airbase %s",_playername,_callsign,_unitName,_uid,_group:GetName(),airbasename)
self:T(self.lid..text)
MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.Debug)
local playerData=self.players[_playername]
if playerData==nil then
self:E(self.lid..string.format("ERROR: playerData nil in landing event. unit=%s player=%s",tostring(_unitName),tostring(_playername)))
return
end
if _unit:IsInZone(zoneCarrier)then
if not playerData.valid then
local text=string.format("you missed at least one important step in the pattern!\nYour next step would have been %s.\nThis pass is INVALID.",playerData.step)
self:MessageToPlayer(playerData,text,"AIRBOSS",nil,30,true,5)
self:_RemoveFlightFromMarshalQueue(playerData,true)
self:_RemoveFlightFromQueue(self.Qpattern,playerData)
self:_RemoveFlightFromQueue(self.Qwaiting,playerData)
self:_RemoveFlightFromQueue(self.Qspinning,playerData)
self:_InitPlayer(playerData)
return
end
if playerData.landed then
self:E(self.lid..string.format("Player %s just landed a second time.",_playername))
else
playerData.landed=true
playerData.attitudemonitor=false
local coord=playerData.unit:GetCoordinate()
local X,Z,rho,phi=self:_GetDistances(_unit)
local dist=coord:Get2DDistance(stern)
if self.Debug and false then
local lp=coord:MarkToAll("Landing coord.")
coord:SmokeGreen()
end
self:_SetTimeInGroove(playerData)
local text=string.format("Player %s AC type %s landed at dist=%.1f m. Tgroove=%.1f sec.",playerData.name,playerData.actype,dist,self:_GetTimeInGroove(playerData))
text=text..string.format(" X=%.1f m, Z=%.1f m, rho=%.1f m.",X,Z,rho)
self:T(self.lid..text)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:RadioTransmission(self.LSORadio,self.LSOCall.IDLE,false,1,nil,true)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.UNDEFINED)
self:ScheduleOnce(1,self._Trapped,self,playerData)
end
end
else
if playerData then
self:E(self.lid..string.format("Player %s did not land in carrier box zone. Maybe in the water near the carrier?",playerData.name))
end
end
else
if self.carriertype~=AIRBOSS.CarrierType.INVINCIBLE or self.carriertype~=AIRBOSS.CarrierType.HERMES or self.carriertype~=AIRBOSS.CarrierType.TARAWA or self.carriertype~=AIRBOSS.CarrierType.AMERICA or self.carriertype~=AIRBOSS.CarrierType.JCARLOS or self.carriertype~=AIRBOSS.CarrierType.CANBERRA then
local coord=EventData.IniUnit:GetCoordinate()
local dist=coord:Get2DDistance(self:GetCoordinate())
local wire=self:_GetWire(coord,0)
local _type=EventData.IniUnit:GetTypeName()
local text=string.format("AI unit %s of type %s landed at dist=%.1f m. Trapped wire=%d.",_unitName,_type,dist,wire)
self:T(self.lid..text)
end
local flight=self:_RecoveredElement(EventData.IniUnit)
self:_CheckSectionRecovered(flight)
end
end
end
function AIRBOSS:OnEventEngineShutdown(EventData)
self:F3({eventengineshutdown=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event ENGINESHUTDOWN!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event ENGINESHUTDOWN!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."ENGINESHUTDOWN: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."ENGINESHUTDOWN: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."ENGINESHUTDOWN: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s shut down its engines!",_playername))
else
self:T(self.lid..string.format("AI unit %s shut down its engines!",_unitName))
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
if flight and flight.ai then
local recovered=self:_CheckSectionRecovered(flight)
if recovered then
self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.",tostring(EventData.IniGroupName)))
self:_RemoveFlight(flight)
local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName
local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName
if self.despawnshutdown and not(istanker or isawacs)then
EventData.IniGroup:Destroy(nil,5)
end
end
end
end
end
function AIRBOSS:OnEventTakeoff(EventData)
self:F3({eventtakeoff=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event TAKEOFF!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event TAKEOFF!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."TAKEOFF: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."TAKEOFF: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."TAKEOFF: player = "..tostring(_playername))
local airbase=EventData.Place
local airbasename="unknown"
if airbase then
airbasename=airbase:GetName()
end
if airbasename==self.airbase:GetName()then
if _unit and _playername then
self:T(self.lid..string.format("Player %s took off at %s!",_playername,airbasename))
else
self:T2(self.lid..string.format("AI unit %s took off at %s!",_unitName,airbasename))
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
if flight then
for _,elem in pairs(flight.elements)do
local element=elem
element.ballcall=false
element.recovered=nil
end
end
end
end
end
function AIRBOSS:OnEventCrash(EventData)
self:F3({eventcrash=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event CRASH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event CRASH!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."CRASH: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."CRASH: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."CARSH: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s crashed!",_playername))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
else
self:T2(self.lid..string.format("AI unit %s crashed!",EventData.IniUnitName))
self:_RemoveUnitFromFlight(EventData.IniUnit)
end
end
function AIRBOSS:OnEventEjection(EventData)
self:F3({eventland=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event EJECTION!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event EJECTION!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."EJECT: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."EJECT: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."EJECT: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s ejected!",_playername))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
else
self:T(self.lid..string.format("AI unit %s ejected!",EventData.IniUnitName))
self:_RemoveUnitFromFlight(EventData.IniUnit)
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
self:_CheckSectionRecovered(flight)
end
end
function AIRBOSS:OnEventRemoveUnit(EventData)
self:F3({eventland=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."EJECT: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."EJECT: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."EJECT: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s removed!",_playername))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
else
self:T(self.lid..string.format("AI unit %s removed!",EventData.IniUnitName))
self:_RemoveUnitFromFlight(EventData.IniUnit)
local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights)
self:_CheckSectionRecovered(flight)
end
end
function AIRBOSS:_PlayerLeft(EventData)
self:F3({eventleave=EventData})
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event PLAYERLEFTUNIT!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event PLAYERLEFTUNIT!")
self:E(EventData)
return
end
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
self:T3(self.lid.."PLAYERLEAVEUNIT: unit   = "..tostring(EventData.IniUnitName))
self:T3(self.lid.."PLAYERLEAVEUNIT: group  = "..tostring(EventData.IniGroupName))
self:T3(self.lid.."PLAYERLEAVEUNIT: player = "..tostring(_playername))
if _unit and _playername then
self:T(self.lid..string.format("Player %s left unit %s!",_playername,_unitName))
local flight=self.players[_playername]
if flight then
self:_RemoveFlight(flight,true)
end
end
end
function AIRBOSS:OnEventMissionEnd(EventData)
self:T3(self.lid.."Mission Ended")
end
function AIRBOSS:_Spinning(playerData)
local SpinIt={}
SpinIt.name="Spinning"
SpinIt.Xmin=-UTILS.NMToMeters(6)
SpinIt.Xmax=UTILS.NMToMeters(5)
SpinIt.Zmin=-UTILS.NMToMeters(6)
SpinIt.Zmax=UTILS.NMToMeters(2)
SpinIt.LimitXmin=-100
SpinIt.LimitXmax=nil
SpinIt.LimitZmin=-UTILS.NMToMeters(1)
SpinIt.LimitZmax=nil
local X,Z,rho,phi=self:_GetDistances(playerData.unit)
if self:_CheckLimits(X,Z,SpinIt)then
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.INITIAL)
self:_RemoveFlightFromQueue(self.Qspinning,playerData)
end
end
function AIRBOSS:_Waiting(playerData)
local radius=UTILS.NMToMeters(10)
local zone=ZONE_RADIUS:New("Carrier 10 NM Zone",self.carrier:GetVec2(),radius)
local inzone=playerData.unit:IsInZone(zone)
local Twaiting=timer.getAbsTime()-playerData.time
if inzone and Twaiting>3*60 and not playerData.warning then
local text=string.format("You are supposed to wait outside the 10 NM zone.")
self:MessageToPlayer(playerData,text,"AIRBOSS")
playerData.warning=true
end
if inzone==false and playerData.warning==true then
playerData.warning=nil
end
end
function AIRBOSS:_Holding(playerData)
local unit=playerData.unit
local stack=playerData.flag
if stack<=0 then
local text=string.format("ERROR: player %s in step %s is holding but has stack=%s (<=0)",playerData.name,playerData.step,tostring(stack))
self:E(self.lid..text)
end
local patternalt=self:_GetMarshalAltitude(stack,playerData.case)
local playeralt=unit:GetAltitude()
local zoneHolding=self:_GetZoneHolding(playerData.case,stack)
if zoneHolding==nil then
self:E(self.lid.."ERROR: zoneHolding is nil!")
self:E({playerData=playerData})
return
end
local inholdingzone=unit:IsInZone(zoneHolding)
local altdiff=playeralt-patternalt
local altgood=UTILS.FeetToMeters(500)
if playerData.difficulty==AIRBOSS.Difficulty.HARD then
altgood=UTILS.FeetToMeters(200)
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
altgood=UTILS.FeetToMeters(350)
elseif playerData.difficulty==AIRBOSS.Difficulty.EASY then
altgood=UTILS.FeetToMeters(500)
end
local altback=altgood*0.5
local justcollapsed=false
if self.Tcollapse then
local dT=timer.getTime()-self.Tcollapse
if dT<=90 then
justcollapsed=true
end
end
local goodalt=math.abs(altdiff)<altgood
local angels=self:_GetAngels(patternalt)
local text=""
if playerData.holding==true then
if inholdingzone then
self:T3("Player is still in the holding zone. Good job.")
else
text=text..string.format("You just left the holding zone. Watch your numbers!")
playerData.holding=false
end
if not justcollapsed then
if altdiff>altgood then
if not playerData.warning then
text=text..string.format("You left your assigned altitude. Descent to angels %d.",angels)
playerData.warning=true
end
elseif altdiff<-altgood then
if not playerData.warning then
text=text..string.format("You left your assigned altitude. Climb to angels %d.",angels)
playerData.warning=true
end
end
end
if playerData.warning and math.abs(altdiff)<=altback then
text=text..string.format("Altitude is looking good again.")
playerData.warning=nil
end
elseif playerData.holding==false then
if inholdingzone then
text=text..string.format("You are back in the holding zone. Now stay there!")
playerData.holding=true
else
self:T3("Player still outside the holding zone. What are you doing man?!")
end
elseif playerData.holding==nil then
if inholdingzone then
playerData.holding=true
text=text..string.format("You arrived at the holding zone.")
if goodalt then
text=text..string.format(" Altitude is good.")
else
if altdiff<0 then
text=text..string.format(" But you're too low.")
else
text=text..string.format(" But you're too high.")
end
text=text..string.format("\nCurrently assigned altitude is %d ft.",UTILS.MetersToFeet(patternalt))
playerData.warning=true
end
else
self:T3("Waiting for player to arrive in the holding zone.")
end
end
if playerData.showhints then
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
function AIRBOSS:_Commencing(playerData,zonecheck)
if zonecheck then
local zoneCommence=self:_GetZoneCommence(playerData.case,playerData.flag)
local inzone=playerData.unit:IsInZone(zoneCommence)
if not inzone then
if timer.getAbsTime()-playerData.time>180 then
self:_MarshalCallClearedForRecovery(playerData.onboard,playerData.case)
playerData.time=timer.getAbsTime()
end
return
end
end
self:_RemoveFlightFromMarshalQueue(playerData)
self:_InitPlayer(playerData)
if playerData.difficulty~=AIRBOSS.Difficulty.HARD then
local text=""
if playerData.case==1 then
text=text.."Proceed to initial."
else
text=text.."Descent to platform."
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then
text=text.." VSI 4000 ft/min until you reach 5000 ft."
end
end
self:MessageToPlayer(playerData,text,"MARSHAL")
end
local nextstep
if playerData.case==1 then
nextstep=AIRBOSS.PatternStep.INITIAL
else
nextstep=AIRBOSS.PatternStep.PLATFORM
end
self:_SetPlayerStep(playerData,nextstep)
for i,_flight in pairs(playerData.section)do
local flight=_flight
self:_Commencing(flight,false)
end
end
function AIRBOSS:_Initial(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneInitial(playerData.case))
local relheading=self:_GetRelativeHeading(playerData.unit,false)
local altitude=playerData.unit:GetAltitude()
if inzone and math.abs(relheading)<60 and altitude<=self.initialmaxalt then
if playerData.showhints then
local hint=string.format("Initial")
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
hint=hint.." - Hook down, SAS on, Wing Sweep 68°!"
else
hint=hint.." - Hook down!"
end
end
self:MessageToPlayer(playerData,hint,"MARSHAL")
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BREAKENTRY)
return true
end
return false
end
function AIRBOSS:_CheckCorridor(playerData)
local validzone=self:_GetZoneCorridor(playerData.case)
local invalid=playerData.unit:IsNotInZone(validzone)
if invalid and(not playerData.warning)then
self:MessageToPlayer(playerData,"you left the approach corridor!","AIRBOSS")
playerData.warning=true
end
if(not invalid)and playerData.warning then
self:MessageToPlayer(playerData,"you're back in the approach corridor.","AIRBOSS")
playerData.warning=false
end
end
function AIRBOSS:_Platform(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZonePlatform(playerData.case))
if inzone then
self:_PlayerHint(playerData)
local nextstep
if math.abs(self.holdingoffset)>0 and playerData.case>1 then
nextstep=AIRBOSS.PatternStep.ARCIN
else
if playerData.case==2 then
nextstep=AIRBOSS.PatternStep.INITIAL
elseif playerData.case==3 then
nextstep=AIRBOSS.PatternStep.DIRTYUP
end
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_ArcInTurn(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneArcIn(playerData.case))
if inzone then
self:_PlayerHint(playerData)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.ARCOUT)
end
end
function AIRBOSS:_ArcOutTurn(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case))
if inzone then
self:_PlayerHint(playerData)
local nextstep
if playerData.case==3 then
nextstep=AIRBOSS.PatternStep.DIRTYUP
else
nextstep=AIRBOSS.PatternStep.INITIAL
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_DirtyUp(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case))
if inzone then
self:_PlayerHint(playerData)
if playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.F14A
or playerData.actype==AIRBOSS.AircraftCarrier.F14B
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER
then
local callsay=self:_NewRadioCall(self.MarshalCall.SAYNEEDLES,nil,nil,5,playerData.onboard)
local callfly=self:_NewRadioCall(self.MarshalCall.FLYNEEDLES,nil,nil,5,playerData.onboard)
self:RadioTransmission(self.MarshalRadio,callsay,false,55,nil,true)
self:RadioTransmission(self.MarshalRadio,callfly,false,60,nil,true)
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BULLSEYE)
end
end
function AIRBOSS:_Bullseye(playerData)
self:_CheckCorridor(playerData)
local inzone=playerData.unit:IsInZone(self:_GetZoneBullseye(playerData.case))
local relheading=self:_GetRelativeHeading(playerData.unit,true)
if inzone and math.abs(relheading)<60 then
self:_PlayerHint(playerData)
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,nil,nil,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT75,nil,nil,nil,true)
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_XX)
end
end
function AIRBOSS:_BolterPattern(playerData)
local X,Z,rho,phi=self:_GetDistances(playerData.unit)
local Bolter={}
Bolter.name="Bolter Pattern"
Bolter.Xmin=-UTILS.NMToMeters(5)
Bolter.Xmax=UTILS.NMToMeters(3)
Bolter.Zmin=-UTILS.NMToMeters(5)
Bolter.Zmax=UTILS.NMToMeters(1)
Bolter.LimitXmin=100
Bolter.LimitXmax=nil
Bolter.LimitZmin=nil
Bolter.LimitZmax=nil
if self:_CheckLimits(X,Z,Bolter)then
local nextstep
if playerData.case<3 then
nextstep=AIRBOSS.PatternStep.ABEAM
else
nextstep=AIRBOSS.PatternStep.BULLSEYE
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_BreakEntry(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.BreakEntry)then
self:_AbortPattern(playerData,X,Z,self.BreakEntry,true)
return
end
if self:_CheckLimits(X,Z,self.BreakEntry)then
self:_PlayerHint(playerData)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK)
end
end
function AIRBOSS:_Break(playerData,part)
local X,Z=self:_GetDistances(playerData.unit)
local breakpoint=self.BreakEarly
if part==AIRBOSS.PatternStep.LATEBREAK then
breakpoint=self.BreakLate
end
if self:_CheckAbort(X,Z,breakpoint)then
self:_AbortPattern(playerData,X,Z,breakpoint,true)
return
end
local tooclose=false
if part==AIRBOSS.PatternStep.LATEBREAK then
local close=0.8
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
close=0.5
end
if X<0 and Z<UTILS.NMToMeters(close)then
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then
self:MessageToPlayer(playerData,"your turn was too tight! Allow for more distance to the boat next time.","LSO")
end
tooclose=true
end
end
if self:_CheckLimits(X,Z,breakpoint)or tooclose then
self:_PlayerHint(playerData)
local nextstep
if part==AIRBOSS.PatternStep.EARLYBREAK then
nextstep=AIRBOSS.PatternStep.LATEBREAK
else
nextstep=AIRBOSS.PatternStep.ABEAM
end
self:_SetPlayerStep(playerData,nextstep)
end
end
function AIRBOSS:_CheckForLongDownwind(playerData)
local X,Z=self:_GetDistances(playerData.unit)
local limit=UTILS.NMToMeters(-1.6)
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
limit=UTILS.NMToMeters(-2.0)
end
if X<limit then
self:RadioTransmission(self.LSORadio,self.LSOCall.LONGINGROOVE)
self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,nil,nil,nil,true)
self:_AddToDebrief(playerData,"Long in the groove - Pattern Waveoff!")
playerData.lig=true
playerData.wop=true
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
end
end
function AIRBOSS:_Abeam(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.Abeam)then
self:_AbortPattern(playerData,X,Z,self.Abeam,true)
return
end
if self:_CheckLimits(X,Z,self.Abeam)then
self:RadioTransmission(self.LSORadio,self.LSOCall.PADDLESCONTACT,nil,nil,nil,true)
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.JCARLOS then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,false,5,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT5,false,5,nil,true)
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT75,false,5,nil,true)
end
self:_PlayerHint(playerData,3)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.NINETY)
end
end
function AIRBOSS:_Ninety(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.Ninety)then
self:_AbortPattern(playerData,X,Z,self.Ninety,true)
return
end
local relheading=self:_GetRelativeHeading(playerData.unit,false)
if relheading<=90 then
self:_PlayerHint(playerData)
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:_SetPlayerStep(playerData,AIRBOSS.PatternStep.FINAL)
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.WAKE)
end
elseif relheading>90 and self:_CheckLimits(X,Z,self.Wake)then
self:MessageToPlayer(playerData,"you are already at the wake and have not passed the 90. Turn faster next time!","LSO")
self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,nil,nil,nil,true)
playerData.wop=true
self:_AddToDebrief(playerData,"Overshoot at wake - Pattern Waveoff!")
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
end
end
function AIRBOSS:_Wake(playerData)
local X,Z=self:_GetDistances(playerData.unit)
if self:_CheckAbort(X,Z,self.Wake)then
self:_AbortPattern(playerData,X,Z,self.Wake,true)
return
end
if self:_CheckLimits(X,Z,self.Wake)then
self:_PlayerHint(playerData)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.FINAL)
end
end
function AIRBOSS:_GetGrooveData(playerData)
local X,Z=self:_GetDistances(playerData.unit)
local stern=self:_GetSternCoord()
local rho=stern:Get2DDistance(playerData.unit:GetCoordinate())
local astern=X<self.carrierparam.sterndist
if astern==false then
rho=-rho
end
local vel=playerData.unit:GetVelocityVec3()
local Gg,Gp,Gd=self:_LSOgrade(playerData)
local groovedata={}
groovedata.Step=playerData.step
groovedata.Time=timer.getTime()
groovedata.Rho=rho
groovedata.X=X
groovedata.Z=Z
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.Roll=playerData.unit:GetRoll()
groovedata.Pitch=playerData.unit:GetPitch()
groovedata.Yaw=playerData.unit:GetYaw()
groovedata.Vel=UTILS.VecNorm(vel)
groovedata.Vy=vel.y
groovedata.Gamma=self:_GetRelativeHeading(playerData.unit,true)
groovedata.Grade=Gg
groovedata.GradePoints=Gp
groovedata.GradeDetail=Gd
return groovedata
end
function AIRBOSS:_Final(playerData,nocheck)
local X,Z,rho,phi=self:_GetDistances(playerData.unit)
if not nocheck then
if self:_CheckAbort(X,Z,self.Final)then
self:_AbortPattern(playerData,X,Z,self.Final,true)
return
end
end
local groovedata=self:_GetGrooveData(playerData)
table.insert(playerData.trapsheet,groovedata)
local zone=self:_GetZoneGroove()
local inzone=playerData.unit:IsInZone(zone)
if inzone then
self:_PlayerHint(playerData,nil,true)
groovedata.FlyThrough=nil
playerData.groove.X0=UTILS.DeepCopy(groovedata)
playerData.Tlso=timer.getTime()
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_XX)
end
groovedata.Step=playerData.step
end
function AIRBOSS:_Groove(playerData)
local RX0=UTILS.NMToMeters(1.000)
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 groovedata=self:_GetGrooveData(playerData)
table.insert(playerData.trapsheet,groovedata)
local X=groovedata.X
local Z=groovedata.Z
if self:_CheckAbort(groovedata.X,groovedata.Z,self.Groove)then
self:_AbortPattern(playerData,groovedata.X,groovedata.Z,self.Groove,true)
return
end
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
playerData.TIG0=timer.getTime()
self:RadioTransmission(self.LSORadio,self.LSOCall.CALLTHEBALL,nil,nil,nil,true)
playerData.Tlso=timer.getTime()
self:RadioTransmission(self.LSORadio,self.LSOCall.ROGERBALL,false,nil,2,true)
playerData.groove.XX=UTILS.DeepCopy(groovedata)
playerData.valid=true
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IM)
elseif rho<=RIM and playerData.step==AIRBOSS.PatternStep.GROOVE_IM then
playerData.groove.IM=UTILS.DeepCopy(groovedata)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IC)
elseif rho<=RIC and playerData.step==AIRBOSS.PatternStep.GROOVE_IC then
playerData.groove.IC=UTILS.DeepCopy(groovedata)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_AR)
elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AR then
playerData.groove.AR=UTILS.DeepCopy(groovedata)
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_AL)
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_IW)
end
elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_AL then
playerData.groove.AL=UTILS.DeepCopy(groovedata)
local ZoneALS=self:_GetZoneAbeamLandingSpot()
local vplayer=playerData.unit:GetVelocityKMH()
local vcarrier=self.carrier:GetVelocityKMH()
local dv=math.abs(vplayer-vcarrier)
local stable=dv<30
if playerData.unit:IsInZone(ZoneALS)and stable then
self:RadioTransmission(self.LSORadio,self.LSOCall.CLEAREDTOLAND,nil,nil,nil,true)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_LC)
playerData.stable=true
playerData.hover=true
end
elseif rho<=RAR and playerData.step==AIRBOSS.PatternStep.GROOVE_LC then
playerData.groove.LC=UTILS.DeepCopy(groovedata)
local ZoneLS=self:_GetZoneLandingSpot()
local vplayer=playerData.unit:GetVelocityKMH()
local vcarrier=self.carrier:GetVelocityKMH()
local dv=math.abs(vplayer-vcarrier)
local stable=dv<15
if playerData.unit:IsInZone(ZoneLS)and stable and playerData.stable==true then
self:RadioTransmission(self.LSORadio,self.LSOCall.STABILIZED,nil,nil,nil,false)
playerData.stable=false
playerData.warning=true
end
end
if rho>=RAR and rho<=RIC and not playerData.waveoff then
local waveoff=self:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData)
if waveoff then
self:T3(self.lid..string.format("Waveoff distance rho=%.1f m",rho))
self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,nil,nil,nil,true)
playerData.Tlso=timer.getTime()
playerData.waveoff=true
return
end
end
groovedata.Step=playerData.step
if rho>=RAR and rho<RX0 and playerData.waveoff==false then
local gs=self:_GS(playerData.step,-1)
local gd=playerData.groove[gs]
if gd then
self:T3(gd)
local d=UTILS.MetersToNM(rho)
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"
self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
elseif gd.LUE<-0.22 and lineupError>0.22 then
env.info" Drift Left ==> DL-"
gd.Drift=" DL"
self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
elseif gd.LUE>0.13 and lineupError<-0.14 then
env.info" Little Drift Right across centre ==> (DR-)"
gd.Drift=" (DR)"
self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
elseif gd.LUE<-0.13 and lineupError>0.14 then
env.info" Little Drift Left across centre ==> (DL-)"
gd.Drift=" (DL)"
self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError))
end
end
if math.abs(lineupError)>math.abs(gd.LUE)then
self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f",gs,d,lineupError,gd.LUE))
gd.LUE=lineupError
end
if gd.GSE>0.4 and glideslopeError<-0.3 then
gd.FlyThrough="\\"
self:T(self.lid..string.format("Got Fly through DOWN at step %s, d=%.3f: Max GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError))
elseif gd.GSE<-0.3 and glideslopeError>0.4 then
gd.FlyThrough="/"
self:E(self.lid..string.format("Got Fly through UP at step %s, d=%.3f: Min GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError))
end
if math.abs(glideslopeError)>math.abs(gd.GSE)then
self:T(self.lid..string.format("Got bigger GSE at step %s, d=%.3f: GSE |%.3f|>|%.3f|",gs,d,glideslopeError,gd.GSE))
gd.GSE=glideslopeError
end
local aircraftaoa=self:_GetAircraftAoA(playerData)
local aoaopt=aircraftaoa.OnSpeed
if math.abs(AoA-aoaopt)>math.abs(gd.AoA-aoaopt)then
self:T(self.lid..string.format("Got bigger AoA error at step %s, d=%.3f: AoA %.3f>%.3f.",gs,d,AoA,gd.AoA))
gd.AoA=AoA
end
end
local deltaT=timer.getTime()-playerData.Tlso
local _advice=true
if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then
_advice=false
end
if deltaT>=self.LSOdT and _advice then
self:_LSOadvice(playerData,glideslopeError,lineupError)
end
end
if X>self.carrierparam.totlength+self.carrierparam.sterndist then
if playerData.waveoff then
if playerData.landed then
self:_AddToDebrief(playerData,"You were waved off but landed anyway. Airboss wants to talk to you!")
else
self:_AddToDebrief(playerData,"You were waved off.")
end
elseif playerData.boltered then
self:_AddToDebrief(playerData,"You boltered.")
else
self:T("Player was not waved off but flew past the carrier without landing ==> Own wave off!")
self:_AddToDebrief(playerData,"Own waveoff.")
playerData.owo=true
end
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF)
end
end
function AIRBOSS:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData)
local waveoff=false
local glMax=1.8
local glMin=-1.2
local luAbs=3.0
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
glMax=2.6
glMin=-2.2
luAbs=4.1
end
if glideslopeError>glMax then
local text=string.format("\n- Waveoff due to glideslope error %.2f > %.1f degrees!",glideslopeError,glMax)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
elseif glideslopeError<glMin then
local text=string.format("\n- Waveoff due to glideslope error %.2f < %.1f degrees!",glideslopeError,glMin)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
end
if math.abs(lineupError)>luAbs then
local text=string.format("\n- Waveoff due to line up error |%.1f| > %.1f degrees!",lineupError,luAbs)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
end
if playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then
local aoaac=self:_GetAircraftAoA(playerData)
if AoA<aoaac.FAST then
local text=string.format("\n- Waveoff due to AoA %.1f < %.1f!",AoA,aoaac.FAST)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
elseif AoA>aoaac.SLOW then
local text=string.format("\n- Waveoff due to AoA %.1f > %.1f!",AoA,aoaac.SLOW)
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
waveoff=true
end
end
return waveoff
end
function AIRBOSS:_CheckFoulDeck(playerData)
local check=false
if playerData.step==AIRBOSS.PatternStep.GROOVE_IM or playerData.step==AIRBOSS.PatternStep.GROOVE_IC then
check=true
end
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
if playerData.step==AIRBOSS.PatternStep.GROOVE_AR or playerData.step==AIRBOSS.PatternStep.GROOVE_AL then
check=true
end
end
if playerData.wofd==true or check==false then
return
end
local runway=self:_GetZoneRunwayBox()
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
runway=self:_GetZoneLandingSpot()
end
local R=250
self:T(self.lid..string.format("Foul deck check: Scanning Carrier Runway Area. Radius=%.1f m.",R))
local _,_,_,unitscan=self:GetCoordinate():ScanObjects(R,true,false,false)
local fouldeck=false
local foulunit=nil
for _,_unit in pairs(unitscan)do
local unit=_unit
local inzone=unit:IsInZone(runway)
local isaircraft=unit:IsAir()
local isairborn=unit:InAir()
if inzone and isaircraft and not isairborn then
local text=string.format("Unit %s on landing runway ==> Foul deck!",unit:GetName())
self:T(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
if self.Debug then
runway:FlareZone(FLARECOLOR.Red,30)
end
fouldeck=true
foulunit=unit
end
end
if playerData and fouldeck then
local text=string.format("Foul deck waveoff due to aircraft %s!",foulunit:GetName())
self:T(self.lid..string.format("%s: %s",playerData.name,text))
self:_AddToDebrief(playerData,text)
self:RadioTransmission(self.LSORadio,self.LSOCall.FOULDECK,false,1)
self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,false,1.2,nil,true)
if playerData.showhints then
local text=string.format("overfly landing area and enter bolter pattern.")
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3)
end
playerData.wofd=true
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
playerData.valid=false
if foulunit then
local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(),self.flights)
if foulflight and not foulflight.ai then
self:MessageToPlayer(foulflight,"move your ass from my runway. NOW!","AIRBOSS")
end
end
end
return fouldeck
end
function AIRBOSS:_GetSternCoord()
local hdg=self.carrier:GetHeading()
local FB=self:GetFinalBearing()
local case=self.case
self.sterncoord:UpdateFromCoordinate(self:GetCoordinate())
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if case==3 then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true)
elseif case==2 or case==1 then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true)
end
elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7,FB+90,true,true)
elseif self.carriertype==AIRBOSS.CarrierType.FORRESTAL then
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7.5,FB+90,true,true)
else
self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(9.5,FB+90,true,true)
end
self.sterncoord:SetAltitude(self.carrierparam.deckheight)
return self.sterncoord
end
function AIRBOSS:_GetWireFromDrawArg()
local wireArgs={}
wireArgs[1]=141
wireArgs[2]=142
wireArgs[3]=143
wireArgs[4]=144
for wire,drawArg in pairs(wireArgs)do
local value=self.carrier:GetDrawArgumentValue(drawArg)
if math.abs(value)>0.001 then
return wire
end
end
return 99
end
function AIRBOSS:_GetWire(Lcoord,dc)
local FB=self:GetFinalBearing()
local Scoord=self:_GetSternCoord()
local Ldist=Lcoord:Get2DDistance(Scoord)
dc=dc or 65
local d=Ldist-dc
if self.mpWireCorrection then
d=d-self.mpWireCorrection
end
local w1=self.carrierparam.wire1
local w2=self.carrierparam.wire2
local w3=self.carrierparam.wire3
local w4=self.carrierparam.wire4
local wire
if d<w1 then
wire=1
elseif d<w2 then
wire=2
elseif d<w3 then
wire=3
elseif d<w4 then
wire=4
else
wire=99
end
if self.Debug and false then
local wp1=Scoord:Translate(w1,FB)
local wp2=Scoord:Translate(w2,FB)
local wp3=Scoord:Translate(w3,FB)
local wp4=Scoord:Translate(w4,FB)
wp1:MarkToAll("Wire 1")
wp2:MarkToAll("Wire 2")
wp3:MarkToAll("Wire 3")
wp4:MarkToAll("Wire 4")
Scoord:MarkToAll("Stern")
Lcoord:MarkToAll(string.format("Landing Point wire=%s",wire))
Lcoord:SmokeGreen()
local Dcoord=Lcoord:Translate(-dc,FB)
Dcoord:SmokeRed()
end
self:T(string.format("GetWire: L=%.1f, L-dc=%.1f ==> wire=%d (dc=%.1f)",Ldist,Ldist-dc,wire,dc))
return wire
end
function AIRBOSS:_Trapped(playerData)
if playerData.unit:InAir()==false then
local unit=playerData.unit
local coord=unit:GetCoordinate()
local v=unit:GetVelocityKMH()-self.carrier:GetVelocityKMH()
local stern=self:_GetSternCoord()
local s=stern:Get2DDistance(coord)
local dcorr=100
if playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then
dcorr=100
elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
dcorr=100
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
dcorr=56
elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then
dcorr=56
end
local wire=self:_GetWire(coord,dcorr)
local text=string.format("Player %s _Trapped: v=%.1f km/h, s-dcorr=%.1f m ==> wire=%d (dcorr=%d)",playerData.name,v,s-dcorr,wire,dcorr)
self:T(self.lid..text)
if v>5 then
if wire>4 and v>10 and not playerData.warning then
self:RadioTransmission(self.LSORadio,self.LSOCall.BOLTER,nil,nil,nil,true)
playerData.warning=true
end
self:ScheduleOnce(0.1,self._Trapped,self,playerData)
return
end
if self.Debug then
coord:SmokeBlue()
coord:MarkToAll(text)
stern:MarkToAll("Stern")
end
playerData.wire=wire
local text=string.format("Trapped %d-wire.",wire)
if wire==3 then
text=text.." Well done!"
elseif wire==2 then
text=text.." Not bad, maybe you even get the 3rd next time."
elseif wire==4 then
text=text.." That was scary. You can do better than this!"
elseif wire==1 then
text=text.." Try harder next time!"
end
self:MessageToPlayer(playerData,text,"LSO","")
local hint=string.format("Trapped %d-wire.",wire)
self:_AddToDebrief(playerData,hint,"Groove: IW")
else
local text=string.format("Player %s boltered in trapped function.",playerData.name)
self:T(self.lid..text)
MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.debug)
playerData.boltered=true
end
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
function AIRBOSS:_GetZoneInitial(case)
self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial")
local radial=self:GetRadial(2,false,false)
local cv=self:GetCoordinate()
local vec2={}
if case==1 then
local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90)
local c2=cv:Translate(UTILS.NMToMeters(1.3),radial-90):Translate(UTILS.NMToMeters(3),radial)
local c3=cv:Translate(UTILS.NMToMeters(0.4),radial+90):Translate(UTILS.NMToMeters(3),radial)
local c4=cv:Translate(UTILS.NMToMeters(1.0),radial)
local c5=cv
vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()}
else
local c1=cv:Translate(UTILS.NMToMeters(0.5),radial-90)
local c2=c1:Translate(UTILS.NMToMeters(0.5),radial)
local c3=cv:Translate(UTILS.NMToMeters(1.2),radial-90):Translate(UTILS.NMToMeters(3),radial)
local c4=cv:Translate(UTILS.NMToMeters(1.2),radial+90):Translate(UTILS.NMToMeters(3),radial)
local c5=cv:Translate(UTILS.NMToMeters(0.5),radial)
local c6=cv
vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()}
end
self.zoneInitial:UpdateFromVec2(vec2)
return self.zoneInitial
end
function AIRBOSS:_GetZoneLineup()
self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup")
local fbi=self:GetRadial(1,false,false)
local st=self:_GetOptLandingCoordinate()
local c1=st
local c2=st:Translate(UTILS.NMToMeters(0.50),fbi+15)
local c3=st:Translate(UTILS.NMToMeters(0.50),fbi+self.lue._max-0.05)
local c4=st:Translate(UTILS.NMToMeters(0.77),fbi+self.lue._max-0.05)
local c5=c4:Translate(UTILS.NMToMeters(0.25),fbi-90)
local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2()}
self.zoneLineup:UpdateFromVec2(vec2)
return self.zoneLineup
end
function AIRBOSS:_GetZoneGroove(l,w,b)
self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove")
l=l or 1.50
w=w or 0.25
b=b or 0.10
local fbi=self:GetRadial(1,false,false)
local st=self:_GetSternCoord()
local c1=st:Translate(self.carrierparam.totwidthstarboard,fbi-90)
local c2=st:Translate(UTILS.NMToMeters(0.10),fbi-90):Translate(UTILS.NMToMeters(0.3),fbi)
local c3=st:Translate(UTILS.NMToMeters(0.25),fbi-90):Translate(UTILS.NMToMeters(l),fbi)
local c4=st:Translate(UTILS.NMToMeters(w/2),fbi+90):Translate(UTILS.NMToMeters(l),fbi)
local c5=st:Translate(UTILS.NMToMeters(b),fbi+90):Translate(UTILS.NMToMeters(0.3),fbi)
local c6=st:Translate(self.carrierparam.totwidthport,fbi+90)
local vec2={c1:GetVec2(),c2:GetVec2(),c3:GetVec2(),c4:GetVec2(),c5:GetVec2(),c6:GetVec2()}
self.zoneGroove:UpdateFromVec2(vec2)
return self.zoneGroove
end
function AIRBOSS:_GetZoneBullseye(case)
local radius=UTILS.NMToMeters(1)
local distance=UTILS.NMToMeters(3)
local radial=self:GetRadial(case,false,false)
local coord=self:GetCoordinate():Translate(distance,radial)
local vec2=coord:GetVec2()
local zone=ZONE_RADIUS:New("Zone Bullseye",vec2,radius)
return zone
end
function AIRBOSS:_GetZoneDirtyUp(case)
local radius=UTILS.NMToMeters(1)
local distance=UTILS.NMToMeters(9)
local radial=self:GetRadial(case,false,false)
local coord=self:GetCoordinate():Translate(distance,radial)
local vec2=coord:GetVec2()
local zone=ZONE_RADIUS:New("Zone Dirty Up",vec2,radius)
return zone
end
function AIRBOSS:_GetZoneArcOut(case)
local radius=UTILS.NMToMeters(1.25)
local distance=UTILS.NMToMeters(11.75)
local radial=self:GetRadial(case,false,false)
local coord=self:GetCoordinate():Translate(distance,radial)
local zone=ZONE_RADIUS:New("Zone Arc Out",coord:GetVec2(),radius)
return zone
end
function AIRBOSS:_GetZoneArcIn(case)
local radius=UTILS.NMToMeters(1.25)
local radial=self:GetRadial(case,false,true)
local alpha=math.rad(self.holdingoffset)
local x=14
local distance=UTILS.NMToMeters(x)
local coord=self:GetCoordinate():Translate(distance,radial)
local zone=ZONE_RADIUS:New("Zone Arc In",coord:GetVec2(),radius)
return zone
end
function AIRBOSS:_GetZonePlatform(case)
local radius=UTILS.NMToMeters(1)
local radial=self:GetRadial(case,false,true)
local alpha=math.rad(self.holdingoffset)
local distance=UTILS.NMToMeters(19)
local coord=self:GetCoordinate():Translate(distance,radial)
local zone=ZONE_RADIUS:New("Zone Platform",coord:GetVec2(),radius)
return zone
end
function AIRBOSS:_GetZoneCorridor(case,l)
l=l or 31
local radial=self:GetRadial(case,false,false)
local offset=self:GetRadial(case,false,true)
local dx=5
local w=2
local w2=w/2
local d=12
local cv=self:GetCoordinate()
local c={}
c[1]=cv:Translate(-UTILS.NMToMeters(dx),radial)
if math.abs(self.holdingoffset)>=5 then
c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90)
c[3]=c[2]:Translate(UTILS.NMToMeters(d+dx+w2),radial)
c[4]=cv:Translate(UTILS.NMToMeters(15),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[5]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[6]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90)
c[7]=cv:Translate(UTILS.NMToMeters(13),offset):Translate(UTILS.NMToMeters(1),offset+90)
c[8]=cv:Translate(UTILS.NMToMeters(11),radial):Translate(UTILS.NMToMeters(1),radial+90)
c[9]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90)
else
c[2]=c[1]:Translate(UTILS.NMToMeters(w2),radial-90)
c[3]=c[2]:Translate(UTILS.NMToMeters(dx+l),radial)
c[4]=c[3]:Translate(UTILS.NMToMeters(w),radial+90)
c[5]=c[1]:Translate(UTILS.NMToMeters(w2),radial+90)
end
local p={}
for _i,_c in ipairs(c)do
if self.Debug then
end
p[_i]=_c:GetVec2()
end
local zone=ZONE_POLYGON_BASE:New("CASE II/III Approach Corridor",p)
return zone
end
function AIRBOSS:_GetZoneCarrierBox()
self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone")
local S=self:_GetSternCoord()
local hdg=self:GetHeading(false)
local p={}
p[1]=S:Translate(self.carrierparam.totwidthstarboard,hdg+90)
p[2]=p[1]:Translate(self.carrierparam.totlength,hdg)
p[3]=p[2]:Translate(self.carrierparam.totwidthstarboard+self.carrierparam.totwidthport,hdg-90)
p[4]=p[3]:Translate(self.carrierparam.totlength,hdg-180)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
self.zoneCarrierbox:UpdateFromVec2(vec2)
return self.zoneCarrierbox
end
function AIRBOSS:_GetZoneRunwayBox()
self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone")
local S=self:_GetSternCoord()
local FB=self:GetFinalBearing(false)
local p={}
p[1]=S:Translate(self.carrierparam.rwywidth*0.5,FB+90)
p[2]=p[1]:Translate(self.carrierparam.rwylength,FB)
p[3]=p[2]:Translate(self.carrierparam.rwywidth,FB-90)
p[4]=p[3]:Translate(self.carrierparam.rwylength,FB-180)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
self.zoneRunwaybox:UpdateFromVec2(vec2)
return self.zoneRunwaybox
end
function AIRBOSS:_GetZoneAbeamLandingSpot()
local S=self:_GetOptLandingCoordinate()
local FB=self:GetFinalBearing(false)
local p={}
p[1]=S:Translate(15,FB):Translate(15,FB+90)
p[2]=S:Translate(-45,FB):Translate(15,FB+90)
p[3]=S:Translate(-45,FB):Translate(15,FB-90)
p[4]=S:Translate(15,FB):Translate(15,FB-90)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
local zone=ZONE_POLYGON_BASE:New("Abeam Landing Spot Zone",vec2)
return zone
end
function AIRBOSS:_GetZoneLandingSpot()
local S=self:_GetLandingSpotCoordinate()
local FB=self:GetFinalBearing(false)
local p={}
p[1]=S:Translate(10,FB):Translate(10,FB+90)
p[2]=S:Translate(-10,FB):Translate(10,FB+90)
p[3]=S:Translate(-10,FB):Translate(10,FB-90)
p[4]=S:Translate(10,FB):Translate(10,FB-90)
local vec2={}
for _,coord in ipairs(p)do
table.insert(vec2,coord:GetVec2())
end
local zone=ZONE_POLYGON_BASE:New("Landing Spot Zone",vec2)
return zone
end
function AIRBOSS:_GetZoneHolding(case,stack)
local zoneHolding=nil
if stack<=0 then
self:E(self.lid.."ERROR: Stack <= 0 in _GetZoneHolding!")
self:E({case=case,stack=stack})
return nil
end
local patternalt,c1,c2=self:_GetMarshalAltitude(stack,case)
if case==1 then
local hdg=self:GetHeading()
local D=UTILS.NMToMeters(2.5)
local Post=self:GetCoordinate():Translate(D,hdg+270)
self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",Post:GetVec2(),self.marshalradius)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone",self.carrier:GetVec2(),UTILS.NMToMeters(5))
end
else
local radial=self:GetRadial(case,false,true)
local p={}
p[1]=c2:Translate(UTILS.NMToMeters(1),radial-90):GetVec2()
p[2]=c1:Translate(UTILS.NMToMeters(1),radial-90):GetVec2()
p[3]=c1:Translate(UTILS.NMToMeters(7),radial+90):GetVec2()
p[4]=c2:Translate(UTILS.NMToMeters(7),radial+90):GetVec2()
self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone")
self.zoneHolding:UpdateFromVec2(p)
end
return self.zoneHolding
end
function AIRBOSS:_GetZoneCommence(case,stack)
local zone
if case==1 then
local hdg=self:GetHeading()
local D=UTILS.NMToMeters(4.75)
local R=UTILS.NMToMeters(1)
local Three=self:GetCoordinate():Translate(D,hdg+275)
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
local Dx=UTILS.NMToMeters(2.25)
local Dz=UTILS.NMToMeters(2.25)
R=UTILS.NMToMeters(1)
Three=self:GetCoordinate():Translate(Dz,hdg-90):Translate(Dx,hdg-180)
end
self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone")
self.zoneCommence:UpdateFromVec2(Three:GetVec2(),R)
else
stack=stack or 1
local l=20+stack
local offset=self:GetRadial(case,false,true)
local cv=self:GetCoordinate()
local c={}
c[1]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[2]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset-90)
c[3]=cv:Translate(UTILS.NMToMeters(l+2.5),offset):Translate(UTILS.NMToMeters(1),offset+90)
c[4]=cv:Translate(UTILS.NMToMeters(l),offset):Translate(UTILS.NMToMeters(1),offset+90)
local p={}
for _i,_c in ipairs(c)do
p[_i]=_c:GetVec2()
end
self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone")
self.zoneCommence:UpdateFromVec2(p)
end
return self.zoneCommence
end
function AIRBOSS:_AttitudeMonitor(playerData)
local unit=playerData.unit
local aoa=unit:GetAoA()
local yaw=unit:GetYaw()
local roll=unit:GetRoll()
local pitch=unit:GetPitch()
local dist=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate())
local dx,dz,rho,phi=self:_GetDistances(unit)
local wind=unit:GetCoordinate():GetWindWithTurbulenceVec3()
local velo=unit:GetVelocityVec3()
local vabs=UTILS.VecNorm(velo)
local rwy=false
local step=playerData.step
if playerData.step==AIRBOSS.PatternStep.FINAL or
playerData.step==AIRBOSS.PatternStep.GROOVE_XX or
playerData.step==AIRBOSS.PatternStep.GROOVE_IM or
playerData.step==AIRBOSS.PatternStep.GROOVE_IC or
playerData.step==AIRBOSS.PatternStep.GROOVE_AR or
playerData.step==AIRBOSS.PatternStep.GROOVE_AL or
playerData.step==AIRBOSS.PatternStep.GROOVE_LC or
playerData.step==AIRBOSS.PatternStep.GROOVE_IW then
step=self:_GS(step,-1)
rwy=true
end
local relhead=self:_GetRelativeHeading(playerData.unit,rwy)
local text=string.format("Pattern step: %s",step)
text=text..string.format("\nAoA=%.1f° = %.1f Units | |V|=%.1f knots",aoa,self:_AoADeg2Units(playerData,aoa),UTILS.MpsToKnots(vabs))
if self.Debug then
text=text..string.format("\nVx=%.1f Vy=%.1f Vz=%.1f m/s",velo.x,velo.y,velo.z)
text=text..string.format("\nWind Vx=%.1f Vy=%.1f Vz=%.1f m/s",wind.x,wind.y,wind.z)
end
text=text..string.format("\nPitch=%.1f° | Roll=%.1f° | Yaw=%.1f°",pitch,roll,yaw)
text=text..string.format("\nClimb Angle=%.1f° | Rate=%d ft/min",unit:GetClimbAngle(),velo.y*196.85)
local dist=self:_GetOptLandingCoordinate():Get3DDistance(playerData.unit:GetVec3())
local vplayer=playerData.unit:GetVelocityKMH()
local vcarrier=self.carrier:GetVelocityKMH()
local dv=math.abs(vplayer-vcarrier)
local alt=self:_GetAltCarrier(playerData.unit)
text=text..string.format("\nDist=%.1f m Alt=%.1f m delta|V|=%.1f km/h",dist,alt,dv)
if playerData.step==AIRBOSS.PatternStep.FINAL or
playerData.step==AIRBOSS.PatternStep.GROOVE_XX or
playerData.step==AIRBOSS.PatternStep.GROOVE_IM or
playerData.step==AIRBOSS.PatternStep.GROOVE_IC or
playerData.step==AIRBOSS.PatternStep.GROOVE_AR or
playerData.step==AIRBOSS.PatternStep.GROOVE_AL or
playerData.step==AIRBOSS.PatternStep.GROOVE_LC or
playerData.step==AIRBOSS.PatternStep.GROOVE_IW then
local lue=self:_Lineup(playerData.unit,true)
local gle=self:_Glideslope(playerData.unit)
text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi)
text=text..string.format("\nLineUp=%.2f° | GlideSlope=%.2f° | AoA=%.1f Units",lue,gle,self:_AoADeg2Units(playerData,aoa))
local grade,points,analysis=self:_LSOgrade(playerData)
text=text..string.format("\nTgroove=%.1f sec",self:_GetTimeInGroove(playerData))
text=text..string.format("\nGrade: %s %.1f PT - %s",grade,points,analysis)
else
text=text..string.format("\nR=%.2f NM | X=%d Z=%d m",UTILS.MetersToNM(rho),dx,dz)
text=text..string.format("\nGamma=%.1f° | Rho=%.1f°",relhead,phi)
end
MESSAGE:New(text,1,nil,true):ToClient(playerData.client)
end
function AIRBOSS:_Glideslope(unit,optangle)
if optangle==nil then
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
optangle=3.0
else
optangle=3.5
end
end
local landingcoord=self:_GetOptLandingCoordinate()
local x=unit:GetCoordinate():Get2DDistance(landingcoord)
local h=self:_GetAltCarrier(unit)
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2)
end
local glideslope=math.atan(h/x)
local gs=math.deg(glideslope)-optangle
return gs
end
function AIRBOSS:_Glideslope2(unit,optangle)
if optangle==nil then
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
optangle=3.0
else
optangle=3.5
end
end
local landingcoord=self:_GetOptLandingCoordinate()
local x=unit:GetCoordinate():Get3DDistance(landingcoord)
local h=self:_GetAltCarrier(unit)
if unit:GetTypeName()==AIRBOSS.AircraftCarrier.AV8B then
h=unit:GetAltitude()-(UTILS.FeetToMeters(50)+self.carrierparam.deckheight+2)
end
local glideslope=math.asin(h/x)
local gs=math.deg(glideslope)-optangle
self:T3(self.lid..string.format("Glide slope error = %.1f, x=%.1f h=%.1f",gs,x,h))
return gs
end
function AIRBOSS:_Lineup(unit,runway)
local landingcoord=self:_GetOptLandingCoordinate()
local A=landingcoord:GetVec3()
local B=unit:GetVec3()
local C=UTILS.VecSubstract(A,B)
C.y=0.0
local X=self.carrier:GetOrientationX()
X.y=0.0
if runway then
X=UTILS.Rotate2D(X,-self.carrierparam.rwyangle)
end
local x=UTILS.VecDot(X,C)
local Z=self.carrier:GetOrientationZ()
Z.y=0.0
if runway then
Z=UTILS.Rotate2D(Z,-self.carrierparam.rwyangle)
end
local z=UTILS.VecDot(Z,C)
local lineup=math.deg(math.atan2(z,x))
return lineup
end
function AIRBOSS:_GetAltCarrier(unit)
local h=unit:GetAltitude()-self.carrierparam.deckheight-2
return h
end
function AIRBOSS:_GetOptLandingCoordinate()
self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord())
local FB=self:GetFinalBearing(false)
local case=self.case
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if case==3 then
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate())
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
elseif case==2 or case==1 then
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35,FB-90,true,true)
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
end
else
if self.carrierparam.wire3 then
self.landingcoord:Translate(self.carrierparam.wire3,FB,true,true)
end
self.landingcoord.y=self.landingcoord.y+2
end
return self.landingcoord
end
function AIRBOSS:_GetLandingSpotCoordinate()
self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord())
local hdg=self:GetHeading()
self.landingspotcoord:Translate(self.carrierparam.landingspot,hdg,true,true):SetAltitude(self.carrierparam.deckheight)
return self.landingspotcoord
end
function AIRBOSS:GetHeading(magnetic)
self:F3({magnetic=magnetic})
local hdg=self.carrier:GetHeading()
if magnetic then
hdg=hdg-self.magvar
end
if hdg<0 then
hdg=hdg+360
end
return hdg
end
function AIRBOSS:GetBRC()
return self:GetHeading(true)
end
function AIRBOSS:GetWind(alt,magnetic,coord)
local cv=coord or self:GetCoordinate()
local Wdir,Wspeed=cv:GetWind(alt or 18)
if magnetic then
Wdir=Wdir-self.magvar
if Wdir<0 then
Wdir=Wdir+360
end
end
return Wdir,Wspeed
end
function AIRBOSS:GetWindOnDeck(alt)
local cv=self:GetCoordinate()
local vc=self.carrier:GetVelocityVec3()
local xc=self.carrier:GetOrientationX()
local zc=self.carrier:GetOrientationZ()
xc=UTILS.Rotate2D(xc,-self.carrierparam.rwyangle)
zc=UTILS.Rotate2D(zc,-self.carrierparam.rwyangle)
local vw=cv:GetWindWithTurbulenceVec3(alt or 18)
local vT=UTILS.VecSubstract(vw,vc)
local vpa=UTILS.VecDot(vT,xc)
local vpp=UTILS.VecDot(vT,zc)
local vabs=UTILS.VecNorm(vT)
return-vpa,vpp,vabs
end
function AIRBOSS:GetHeadingIntoWind(vdeck,magnetic,coord)
if self.intowindold then
return self:GetHeadingIntoWind_old(vdeck,magnetic,coord)
else
return self:GetHeadingIntoWind_new(vdeck,magnetic,coord)
end
end
function AIRBOSS:GetHeadingIntoWind_old(vdeck,magnetic,coord)
local function adjustDegreesForWindSpeed(windSpeed)
local degreesAdjustment=0
if windSpeed>0 and windSpeed<3 then
degreesAdjustment=30
elseif windSpeed>=3 and windSpeed<5 then
degreesAdjustment=20
elseif windSpeed>=5 and windSpeed<8 then
degreesAdjustment=8
elseif windSpeed>=8 and windSpeed<13 then
degreesAdjustment=4
elseif windSpeed>=13 then
degreesAdjustment=0
end
return degreesAdjustment
end
local windfrom,vwind=self:GetWind(nil,nil,coord)
local intowind=windfrom-self.carrierparam.rwyangle+adjustDegreesForWindSpeed(vwind)
if vwind<0.1 then
intowind=self:GetHeading()
end
if magnetic then
intowind=intowind-self.magvar
end
if intowind<0 then
intowind=intowind+360
end
local vtot=math.max(vdeck-UTILS.MpsToKnots(vwind),4)
return intowind,vtot
end
function AIRBOSS:GetHeadingIntoWind_new(vdeck,magnetic,coord)
local Offset=self.carrierparam.rwyangle or 0
local windfrom,vwind=self:GetWind(18,nil,coord)
local Vmin=4
local Vmax=UTILS.KmphToKnots(self.carrier:GetSpeedMax())
if vwind<0.1 then
local h=self:GetHeading(magnetic)
return h,math.min(vdeck,Vmax)
end
vwind=UTILS.MpsToKnots(vwind)
local windto=(windfrom+180)%360
local alpha=math.rad(-Offset)
local C=math.sqrt(math.cos(alpha)^2/math.sin(alpha)^2+1)
local vdeckMax=vwind+math.cos(alpha)*Vmax
local vdeckMin=vwind+math.cos(alpha)*Vmin
local v=0
local theta=0
if vdeck>vdeckMax then
v=Vmax
theta=math.asin(v/(vwind*C))-math.asin(-1/C)
elseif vdeck<vdeckMin then
v=Vmin
theta=math.asin(v/(vwind*C))-math.asin(-1/C)
elseif vdeck*math.sin(alpha)>vwind then
theta=math.pi/2
v=math.sqrt(vdeck^2-vwind^2)
else
theta=math.asin(vdeck*math.sin(alpha)/vwind)
v=vdeck*math.cos(alpha)-vwind*math.cos(theta)
end
local magvar=magnetic and self.magvar or 0
local intowind=(540+(windto-magvar+math.deg(theta)))%360
return intowind,v
end
function AIRBOSS:GetBRCintoWind(vdeck)
return self:GetHeadingIntoWind(vdeck,true)
end
function AIRBOSS:GetFinalBearing(magnetic)
local fb=self:GetHeading(magnetic)
fb=fb+self.carrierparam.rwyangle
if fb<0 then
fb=fb+360
end
return fb
end
function AIRBOSS:GetRadial(case,magnetic,offset,inverse)
case=case or self.case
local radial
if case==1 then
radial=self:GetFinalBearing(magnetic)-180
elseif case==2 then
radial=self:GetHeading(magnetic)-180
if offset then
radial=radial+self.holdingoffset
end
elseif case==3 then
radial=self:GetFinalBearing(magnetic)-180
if offset then
radial=radial+self.holdingoffset
end
end
if radial<0 then
radial=radial+360
end
if inverse then
radial=radial-180
if radial<0 then
radial=radial+360
end
end
return radial
end
function AIRBOSS:_GetDeltaHeading(hdg1,hdg2)
local V={}
V.x=math.cos(math.rad(hdg1))
V.y=0
V.z=math.sin(math.rad(hdg1))
local W={}
W.x=math.cos(math.rad(hdg2))
W.y=0
W.z=math.sin(math.rad(hdg2))
local alpha=UTILS.VecAngle(V,W)
return alpha
end
function AIRBOSS:_GetRelativeHeading(unit,runway)
local vC=self.carrier:GetOrientationX()
if runway then
vC=UTILS.Rotate2D(vC,-self.carrierparam.rwyangle)
end
local vP=unit:GetOrientationX()
vC.y=0;
vP.y=0
local rhdg=UTILS.VecAngle(vC,vP)
return rhdg
end
function AIRBOSS:_GetRelativeVelocity(unit)
local vC=self.carrier:GetVelocityVec3()
local vP=unit:GetVelocityVec3()
vC.y=0;
vP.y=0
local v=UTILS.VecSubstract(vP,vC)
return UTILS.VecNorm(v),v
end
function AIRBOSS:_GetDistances(unit)
local a=self.carrier:GetVec3()
local b=unit:GetVec3()
local c={x=b.x-a.x,y=0,z=b.z-a.z}
local x=self.carrier:GetOrientationX()
local dx=UTILS.VecDot(x,c)
local z=self.carrier:GetOrientationZ()
local dz=UTILS.VecDot(z,c)
local rho=math.sqrt(dx*dx+dz*dz)
local phi=math.deg(math.atan2(dz,dx))
if phi<0 then
phi=phi+360
end
return dx,dz,rho,phi
end
function AIRBOSS:_CheckLimits(X,Z,check)
local nextXmin=check.LimitXmin==nil or(check.LimitXmin and(check.LimitXmin<0 and X<=check.LimitXmin or check.LimitXmin>=0 and X>=check.LimitXmin))
local nextXmax=check.LimitXmax==nil or(check.LimitXmax and(check.LimitXmax<0 and X>=check.LimitXmax or check.LimitXmax>=0 and X<=check.LimitXmax))
local nextZmin=check.LimitZmin==nil or(check.LimitZmin and(check.LimitZmin<0 and Z<=check.LimitZmin or check.LimitZmin>=0 and Z>=check.LimitZmin))
local nextZmax=check.LimitZmax==nil or(check.LimitZmax and(check.LimitZmax<0 and Z>=check.LimitZmax or check.LimitZmax>=0 and Z<=check.LimitZmax))
local next=nextXmin and nextXmax and nextZmin and nextZmax
local text=string.format("step=%s: next=%s: X=%d Xmin=%s Xmax=%s | Z=%d Zmin=%s Zmax=%s",check.name,tostring(next),X,tostring(check.LimitXmin),tostring(check.LimitXmax),Z,tostring(check.LimitZmin),tostring(check.LimitZmax))
self:T3(self.lid..text)
return next
end
function AIRBOSS:_LSOadvice(playerData,glideslopeError,lineupError)
local advice=0
if glideslopeError>self.gle.HIGH then
self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,true,nil,nil,true)
advice=advice+self.LSOCall.HIGH.duration
elseif glideslopeError>self.gle.High then
self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,false,nil,nil,true)
advice=advice+self.LSOCall.HIGH.duration
elseif glideslopeError<self.gle.LOW then
self:RadioTransmission(self.LSORadio,self.LSOCall.POWER,true,nil,nil,true)
advice=advice+self.LSOCall.POWER.duration
elseif glideslopeError<self.gle.Low then
self:RadioTransmission(self.LSORadio,self.LSOCall.POWER,false,nil,nil,true)
advice=advice+self.LSOCall.POWER.duration
else
end
if lineupError<self.lue.LEFT then
self:RadioTransmission(self.LSORadio,self.LSOCall.COMELEFT,true,nil,nil,true)
advice=advice+self.LSOCall.COMELEFT.duration
elseif lineupError<self.lue.Left then
self:RadioTransmission(self.LSORadio,self.LSOCall.COMELEFT,false,nil,nil,true)
advice=advice+self.LSOCall.COMELEFT.duration
elseif lineupError>self.lue.RIGHT then
self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,true,nil,nil,true)
advice=advice+self.LSOCall.RIGHTFORLINEUP.duration
elseif lineupError>self.lue.Right then
self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,false,nil,nil,true)
advice=advice+self.LSOCall.RIGHTFORLINEUP.duration
else
end
local AOA=playerData.unit:GetAoA()
local acaoa=self:_GetAircraftAoA(playerData)
if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then
if AOA>acaoa.SLOW then
self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,true,nil,nil,true)
advice=advice+self.LSOCall.SLOW.duration
elseif AOA>acaoa.Slow then
self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,false,nil,nil,true)
advice=advice+self.LSOCall.SLOW.duration
elseif AOA>acaoa.OnSpeedMax then
elseif AOA<acaoa.FAST then
self:RadioTransmission(self.LSORadio,self.LSOCall.FAST,true,nil,nil,true)
advice=advice+self.LSOCall.FAST.duration
elseif AOA<acaoa.Fast then
self:RadioTransmission(self.LSORadio,self.LSOCall.FAST,false,nil,nil,true)
advice=advice+self.LSOCall.FAST.duration
elseif AOA<acaoa.OnSpeedMin then
end
end
playerData.Tlso=timer.getTime()
end
function AIRBOSS:_EvalGrooveTime(playerData)
local t=playerData.Tgroove
local grade=""
if t<9 then
grade="_NESA_"
elseif t<15 then
grade="NESA"
elseif t<19 then
grade="OK Groove"
elseif t<=24 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
grade="OK V/STOL Groove"
elseif playerData.actype==AIRBOSS.AircraftCarrier.AV8B and t>=76 then
grade="SLOW V/STOL Groove"
else
grade="LIG"
end
if t>=16.4 and t<=16.6 then
grade="_OK_"
end
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B and(t>=60.0 and t<=65.0)then
grade="_OK_ V/STOL"
end
return grade
end
function AIRBOSS:_LSOgrade(playerData)
local function count(base,pattern)
return select(2,string.gsub(base,pattern,""))
end
local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX)
local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM)
local GIC,nIC=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IC)
local GAR,nAR=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.AR)
local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
local G=GXX.." "..GIM.." ".." "..GIC.." "..GAR
local N=nXX+nIM+nIC+nAR
local nL=count(G,'_')/2
local nS=count(G,'%(')
local nN=N-nS-nL
local Tgroove=playerData.Tgroove
local TgrooveUnicorn=Tgroove and(Tgroove>=15.0 and Tgroove<=18.99)or false
local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false
local grade
local points
if N==0 and(TgrooveUnicorn or TgrooveVstolUnicorn or playerData.case==3)then
grade="_OK_"
points=5.0
G="Unicorn"
else
if vtol then
local Gb=GXX.." "..GIM
local N=nXX+nIM
local nL=count(Gb,'_')/2
local nS=count(Gb,'%(')
local nN=N-nS-nL
local Gv=GIC.." "..GAR
local Nv=nIC+nAR
local nLv=count(Gv,'_')/2
local nSv=count(Gv,'%(')
local nNv=Nv-nSv-nLv
if nL>0 or nLv>1 then
grade="--"
points=2.0
elseif nN>0 or nNv>1 or nLv==1 then
grade="(OK)"
points=3.0
else
grade="OK"
points=4.0
end
else
if nL>0 then
grade="--"
points=2.0
elseif nN>0 then
grade="(OK)"
points=3.0
else
grade="OK"
points=4.0
end
end
end
G=G:gsub("%)%(","")
G=G:gsub("__","")
local text="LSO grade:\n"
text=text..G.."\n"
text=text.."Grade = "..grade.." points = "..points.."\n"
text=text.."# of total deviations   = "..N.."\n"
text=text.."# of large deviations _ = "..nL.."\n"
text=text.."# of normal deviations  = "..nN.."\n"
text=text.."# of small deviations ( = "..nS.."\n"
self:T2(self.lid..text)
if playerData.wop then
if playerData.lig then
grade="WO"
points=1.0
G="LIG"
else
grade="WOP"
points=2.0
G="n/a"
end
elseif playerData.wofd then
if playerData.landed then
grade="CUT"
points=0.0
else
grade="WOFD"
points=-1.0
end
G="n/a"
elseif playerData.owo then
grade="OWO"
points=2.0
if N==0 then
G="n/a"
end
elseif playerData.waveoff then
if playerData.landed then
grade="CUT"
points=0.0
else
grade="WO"
points=1.0
end
elseif playerData.boltered then
grade="-- (BOLTER)"
points=2.5
elseif not playerData.hover and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
if playerData.landed then
grade="CUT"
points=0.0
end
end
return grade,points,G
end
function AIRBOSS:_Flightdata2Text(playerData,groovestep)
local function little(text)
return string.format("(%s)",text)
end
local function underline(text)
return string.format("_%s_",text)
end
local fdata=playerData.groove[groovestep]
if fdata==nil then
self:T3(self.lid.."Flight data is nil.")
return"",0
end
local step=fdata.Step
local AOA=fdata.AoA
local GSE=fdata.GSE
local LUE=fdata.LUE
local ROL=fdata.Roll
local acaoa=self:_GetAircraftAoA(playerData)
local P=nil
if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then
if LUE>self.lue.RIGHT then
P=underline("AA")
elseif LUE>self.lue.RightMed then
P="AA "
elseif LUE>self.lue.Right then
P=little("AA")
end
end
local O=nil
if step==AIRBOSS.PatternStep.GROOVE_XX then
if LUE<self.lue.LEFT then
O=underline("OS")
elseif LUE<self.lue.Left then
O="OS"
elseif LUE<self.lue._min then
O=little("OS")
end
end
local S=nil
if AOA>acaoa.SLOW then
S=underline("SLO")
elseif AOA>acaoa.Slow then
S="SLO"
elseif AOA>acaoa.OnSpeedMax then
S=little("SLO")
elseif AOA<acaoa.FAST then
S=underline("F")
elseif AOA<acaoa.Fast then
S="F"
elseif AOA<acaoa.OnSpeedMin then
S=little("F")
end
local A=nil
if GSE>self.gle.HIGH then
A=underline("H")
elseif GSE>self.gle.High then
A="H"
elseif GSE>self.gle._max then
A=little("H")
elseif GSE<self.gle.LOW then
A=underline("LO")
elseif GSE<self.gle.Low then
A="LO"
elseif GSE<self.gle._min then
A=little("LO")
end
local D=nil
if LUE>self.lue.RIGHT then
D=underline("LUL")
elseif LUE>self.lue.Right then
D="LUL"
elseif LUE>self.lue._max then
D=little("LUL")
elseif playerData.case<3 then
if LUE<self.lue.LEFT and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=underline("LUR")
elseif LUE<self.lue.Left and step~=AIRBOSS.PatternStep.GROOVE_XX then
D="LUR"
elseif LUE<self.lue._min and step~=AIRBOSS.PatternStep.GROOVE_XX then
D=little("LUR")
end
elseif playerData.case==3 then
if LUE<self.lue.LEFT then
D=underline("LUR")
elseif LUE<self.lue.Left then
D="LUR"
elseif LUE<self.lue._min then
D=little("LUR")
end
end
local G=""
local n=0
if fdata.FlyThrough then
G=G..fdata.FlyThrough
end
if P then
G=G..P
n=n
end
if S then
G=G..S
n=n+1
end
if A then
G=G..A
n=n+1
end
if D then
G=G..D
n=n+1
end
if fdata.Drift then
G=G..fdata.Drift
n=n
end
if O then
G=G..O
n=n+1
end
local step=self:_GS(step)
step=step:gsub("XX","X")
if G~=""then
G=G..step
end
local text=string.format("LSO Grade at %s:\n",step)
text=text..string.format("AOA=%.1f\n",AOA)
text=text..string.format("GSE=%.1f\n",GSE)
text=text..string.format("LUE=%.1f\n",LUE)
text=text..string.format("ROL=%.1f\n",ROL)
text=text..G
self:T3(self.lid..text)
return G,n
end
function AIRBOSS:_GS(step,n)
local gp
n=n or 0
if step==AIRBOSS.PatternStep.FINAL then
gp=AIRBOSS.GroovePos.X0
if n==-1 then
gp=AIRBOSS.GroovePos.X0
elseif n==1 then
gp=AIRBOSS.GroovePos.XX
end
elseif step==AIRBOSS.PatternStep.GROOVE_XX then
gp=AIRBOSS.GroovePos.XX
if n==-1 then
gp=AIRBOSS.GroovePos.X0
elseif n==1 then
gp=AIRBOSS.GroovePos.IM
end
elseif step==AIRBOSS.PatternStep.GROOVE_IM then
gp=AIRBOSS.GroovePos.IM
if n==-1 then
gp=AIRBOSS.GroovePos.XX
elseif n==1 then
gp=AIRBOSS.GroovePos.IC
end
elseif step==AIRBOSS.PatternStep.GROOVE_IC then
gp=AIRBOSS.GroovePos.IC
if n==-1 then
gp=AIRBOSS.GroovePos.IM
elseif n==1 then
gp=AIRBOSS.GroovePos.AR
end
elseif step==AIRBOSS.PatternStep.GROOVE_AR then
gp=AIRBOSS.GroovePos.AR
if n==-1 then
gp=AIRBOSS.GroovePos.IC
elseif n==1 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
gp=AIRBOSS.GroovePos.AL
else
gp=AIRBOSS.GroovePos.IW
end
end
elseif step==AIRBOSS.PatternStep.GROOVE_AL then
gp=AIRBOSS.GroovePos.AL
if n==-1 then
gp=AIRBOSS.GroovePos.AR
elseif n==1 then
gp=AIRBOSS.GroovePos.LC
end
elseif step==AIRBOSS.PatternStep.GROOVE_LC then
gp=AIRBOSS.GroovePos.LC
if n==-1 then
gp=AIRBOSS.GroovePos.AL
elseif n==1 then
gp=AIRBOSS.GroovePos.LC
end
elseif step==AIRBOSS.PatternStep.GROOVE_IW then
gp=AIRBOSS.GroovePos.IW
if n==-1 then
gp=AIRBOSS.GroovePos.AR
elseif n==1 then
gp=AIRBOSS.GroovePos.IW
end
end
return gp
end
function AIRBOSS:_CheckAbort(X,Z,pos)
local abort=false
if pos.Xmin and X<pos.Xmin then
self:T(string.format("Xmin: X=%d < %d=Xmin",X,pos.Xmin))
abort=true
elseif pos.Xmax and X>pos.Xmax then
self:T(string.format("Xmax: X=%d > %d=Xmax",X,pos.Xmax))
abort=true
elseif pos.Zmin and Z<pos.Zmin then
self:T(string.format("Zmin: Z=%d < %d=Zmin",Z,pos.Zmin))
abort=true
elseif pos.Zmax and Z>pos.Zmax then
self:T(string.format("Zmax: Z=%d > %d=Zmax",Z,pos.Zmax))
abort=true
end
return abort
end
function AIRBOSS:_TooFarOutText(X,Z,posData)
local text="you are too "
local xtext=nil
if posData.Xmin and X<posData.Xmin then
if posData.Xmin<=0 then
xtext="far behind "
else
xtext="close to "
end
elseif posData.Xmax and X>posData.Xmax then
if posData.Xmax>=0 then
xtext="far ahead of "
else
xtext="close to "
end
end
local ztext=nil
if posData.Zmin and Z<posData.Zmin then
if posData.Zmin<=0 then
ztext="far port of "
else
ztext="close to "
end
elseif posData.Zmax and Z>posData.Zmax then
if posData.Zmax>=0 then
ztext="far starboard of "
else
ztext="too close to "
end
end
if xtext and ztext then
text=text..xtext.." and "..ztext
elseif xtext then
text=text..xtext
elseif ztext then
text=text..ztext
end
text=text.."the carrier."
if xtext==nil and ztext==nil then
text="you are too far from where you should be!"
end
return text
end
function AIRBOSS:_AbortPattern(playerData,X,Z,posData,patternwo)
local text=self:_TooFarOutText(X,Z,posData)
local dtext=string.format("Abort: X=%d Xmin=%s, Xmax=%s | Z=%d Zmin=%s Zmax=%s",X,tostring(posData.Xmin),tostring(posData.Xmax),Z,tostring(posData.Zmin),tostring(posData.Zmax))
self:T(self.lid..dtext)
self:MessageToPlayer(playerData,text,"LSO")
if patternwo then
playerData.wop=true
self:_AddToDebrief(playerData,string.format("Pattern wave off: %s",text))
self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,false,3,nil,nil,true)
playerData.step=AIRBOSS.PatternStep.DEBRIEF
playerData.warning=nil
end
end
function AIRBOSS:_PlayerHint(playerData,delay,soundoff)
if not playerData.showhints then
return
end
local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData)
local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData,alt)
local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData,speed)
local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData,aoa)
local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData,dist)
local hint=""
if hintAlt and hintAlt~=""then
hint=hint.."\n"..hintAlt
end
if hintSpeed and hintSpeed~=""then
hint=hint.."\n"..hintSpeed
end
if hintAoA and hintAoA~=""then
hint=hint.."\n"..hintAoA
end
if hintDist and hintDist~=""then
hint=hint.."\n"..hintDist
end
local debrief=""
if debriefAlt and debriefAlt~=""then
debrief=debrief.."\n- "..debriefAlt
end
if debriefSpeed and debriefSpeed~=""then
debrief=debrief.."\n- "..debriefSpeed
end
if debriefAoA and debriefAoA~=""then
debrief=debrief.."\n- "..debriefAoA
end
if debriefDist and debriefDist~=""then
debrief=debrief.."\n- "..debriefDist
end
if debrief~=""then
self:_AddToDebrief(playerData,debrief)
end
delay=delay or 0
if not soundoff then
if callAlt then
self:Sound2Player(playerData,self.LSORadio,callAlt,false,delay)
delay=delay+callAlt.duration+0.5
end
if callSpeed then
self:Sound2Player(playerData,self.LSORadio,callSpeed,false,delay)
delay=delay+callSpeed.duration+0.5
end
if callAoA then
self:Sound2Player(playerData,self.LSORadio,callAoA,false,delay)
delay=delay+callAoA.duration+0.5
end
if callDist then
self:Sound2Player(playerData,self.LSORadio,callDist,false,delay)
delay=delay+callDist.duration+0.5
end
end
if playerData.step==AIRBOSS.PatternStep.ARCIN then
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
local radial=self:GetRadial(playerData.case,true,false,true)
local turn="right"
if self.holdingoffset<0 then
turn="left"
end
hint=hint..string.format("\nTurn %s and select TACAN %03d°.",turn,radial)
end
end
if playerData.step==AIRBOSS.PatternStep.DIRTYUP then
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
hint=hint.."\nFAF! Checks completed. Nozzles 50°."
else
hint=hint.."\nDirty up! Hook, gear and flaps down."
end
end
end
if playerData.step==AIRBOSS.PatternStep.BULLSEYE then
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
if playerData.actype==AIRBOSS.AircraftCarrier.HORNET
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype==AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype==AIRBOSS.AircraftCarrier.GROWLER then
hint=hint..string.format("\nIntercept glideslope and follow the needles.")
else
hint=hint..string.format("\nIntercept glideslope.")
end
end
end
if hint~=""then
local text=string.format("%s%s",playerData.step,hint)
self:MessageToPlayer(playerData,hint,"AIRBOSS","")
end
end
function AIRBOSS:_StepHint(playerData,step)
step=step or playerData.step
if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then
local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData,step)
local hint=""
if alt then
hint=hint..string.format("\nAltitude %d ft",UTILS.MetersToFeet(alt))
end
if aoa then
hint=hint..string.format("\nAoA %.1f",self:_AoADeg2Units(playerData,aoa))
end
if speed then
hint=hint..string.format("\nSpeed %d knots",UTILS.MpsToKnots(speed))
end
if dist then
hint=hint..string.format("\nDistance to the boat %.1f NM",UTILS.MetersToNM(dist))
end
if step==AIRBOSS.PatternStep.LATEBREAK then
if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
hint=hint.."\nWing Sweep 20°, Gear DOWN < 280 KIAS."
end
end
if step==AIRBOSS.PatternStep.ABEAM then
if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
hint=hint.."\nNozzles 50°-60°. Antiskid OFF. Lights OFF."
elseif playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then
hint=hint.."\nSlats/Flaps EXTENDED < 225 KIAS. DLC SELECTED. Auto Throttle IF DESIRED."
else
hint=hint.."\nDirty up! Gear DOWN, flaps DOWN. Check hook down."
end
end
if hint~=""then
local text=string.format("Optimal setup at next step %s:%s",step,hint)
self:MessageToPlayer(playerData,text,"AIRBOSS","",nil,false,1)
end
end
end
function AIRBOSS:_AltitudeCheck(playerData,altopt)
if altopt==nil then
return nil,nil
end
local altitude=playerData.unit:GetAltitude()
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local _error=(altitude-altopt)/altopt*100
local radiocall=nil
local hint=""
if _error>badscore then
radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","")
elseif _error>lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.HIGH,"Paddles","")
elseif _error<-badscore then
radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","")
elseif _error<-lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.LOW,"Paddles","")
else
hint=string.format("Good altitude. ")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format("Optimal altitude is %d ft.",UTILS.MetersToFeet(altopt))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("Altitude %d ft = %d%% deviation from %d ft.",UTILS.MetersToFeet(altitude),_error,UTILS.MetersToFeet(altopt))
return hint,debrief,radiocall
end
function AIRBOSS:_AoACheck(playerData,optaoa)
if optaoa==nil then
return nil,nil
end
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local aoa=playerData.unit:GetAoA()
local _error=(aoa-optaoa)/optaoa*100
local aircraftaoa=self:_GetAircraftAoA(playerData)
local radiocall=nil
local hint=""
if aoa>=aircraftaoa.SLOW then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","")
elseif aoa>=aircraftaoa.Slow then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"Paddles","")
elseif aoa>=aircraftaoa.OnSpeedMax then
hint="Your're a little slow. "
elseif aoa>=aircraftaoa.OnSpeedMin then
hint="You're on speed. "
elseif aoa>=aircraftaoa.Fast then
hint="You're a little fast. "
elseif aoa>=aircraftaoa.FAST then
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","")
else
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"Paddles","")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format("Optimal AoA is %.1f.",self:_AoADeg2Units(playerData,optaoa))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("AoA %.1f = %d%% deviation from %.1f.",self:_AoADeg2Units(playerData,aoa),_error,self:_AoADeg2Units(playerData,optaoa))
return hint,debrief,radiocall
end
function AIRBOSS:_SpeedCheck(playerData,speedopt)
if speedopt==nil then
return nil,nil
end
local speed=playerData.unit:GetVelocityMPS()
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local _error=(speed-speedopt)/speedopt*100
local radiocall=nil
local hint=""
if _error>badscore then
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","")
elseif _error>lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.FAST,"AIRBOSS","")
elseif _error<-badscore then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","")
elseif _error<-lowscore then
radiocall=self:_NewRadioCall(self.LSOCall.SLOW,"AIRBOSS","")
else
hint=string.format("Good speed. ")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format("Optimal speed is %d knots.",UTILS.MpsToKnots(speedopt))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("Speed %d knots = %d%% deviation from %d knots.",UTILS.MpsToKnots(speed),_error,UTILS.MpsToKnots(speedopt))
return hint,debrief,radiocall
end
function AIRBOSS:_DistanceCheck(playerData,optdist)
if optdist==nil then
return nil,nil
end
local distance=playerData.unit:GetCoordinate():Get2DDistance(self:GetCoordinate())
local lowscore,badscore=self:_GetGoodBadScore(playerData)
local _error=(distance-optdist)/optdist*100
local hint
if _error>badscore then
hint=string.format("You're too far from the boat!")
elseif _error>lowscore then
hint=string.format("You're slightly too far from the boat.")
elseif _error<-badscore then
hint=string.format("You're too close to the boat!")
elseif _error<-lowscore then
hint=string.format("You're slightly too far from the boat.")
else
hint=string.format("Good distance to the boat.")
end
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
hint=hint..string.format(" Optimal distance is %.1f NM.",UTILS.MetersToNM(optdist))
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
hint=""
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
hint=""
end
local debrief=string.format("Distance %.1f NM = %d%% deviation from %.1f NM.",UTILS.MetersToNM(distance),_error,UTILS.MetersToNM(optdist))
return hint,debrief,nil
end
function AIRBOSS:_AddToDebrief(playerData,hint,step)
step=step or playerData.step
table.insert(playerData.debrief,{step=step,hint=hint})
end
function AIRBOSS:_Debrief(playerData)
self:F(self.lid..string.format("Debriefing of player %s.",playerData.name))
playerData.debriefschedulerID=nil
playerData.attitudemonitor=false
local grade,points,analysis=self:_LSOgrade(playerData)
if points and points>=0 then
table.insert(playerData.points,points)
end
local Points=0
if playerData.landed and not playerData.unit:InAir()then
for _,_points in pairs(playerData.points)do
Points=Points+_points
end
Points=Points/#playerData.points
playerData.points={}
else
Points=points
end
local mygrade={}
mygrade.grade=grade
mygrade.points=points
mygrade.details=analysis
mygrade.wire=playerData.wire
mygrade.Tgroove=playerData.Tgroove
if playerData.landed and not playerData.unit:InAir()then
mygrade.finalscore=Points
end
mygrade.case=playerData.case
local windondeck=self:GetWindOnDeck()
mygrade.wind=UTILS.Round(UTILS.MpsToKnots(windondeck),1)
mygrade.modex=playerData.onboard
mygrade.airframe=playerData.actype
mygrade.carriertype=self.carriertype
mygrade.carriername=self.alias
mygrade.carrierrwy=self.carrierparam.rwyangle
mygrade.theatre=self.theatre
mygrade.mitime=UTILS.SecondsToClock(timer.getAbsTime(),true)
mygrade.midate=UTILS.GetDCSMissionDate()
mygrade.osdate="n/a"
if os then
mygrade.osdate=os.date()
end
playerData.grade=mygrade
if playerData.trapon and self.trapsheet then
self:_SaveTrapSheet(playerData,mygrade)
end
table.insert(self.playerscores[playerData.name],mygrade)
self:LSOGrade(playerData,mygrade)
local text=string.format("%s %.1f PT - %s",grade,Points,analysis)
if Points==-1 then
text=string.format("%s n/a PT - Foul deck",grade,Points,analysis)
end
if not(playerData.wop or playerData.wofd)then
if playerData.wire and playerData.wire<=4 then
text=text..string.format(" %d-wire",playerData.wire)
end
if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then
text=text..string.format("\nTime in the groove %.1f seconds: %s",playerData.Tgroove,self:_EvalGrooveTime(playerData))
end
end
playerData.lastdebrief=UTILS.DeepCopy(playerData.debrief)
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
text=text..string.format("\nYour detailed debriefing can be found via the F10 radio menu.")
end
self:MessageToPlayer(playerData,text,"LSO","",30,true)
playerData.step=AIRBOSS.PatternStep.UNDEFINED
if playerData.wop then
if playerData.unit:IsAlive()then
local heading,distance
if playerData.case==1 or playerData.case==2 then
playerData.step=AIRBOSS.PatternStep.INITIAL
local initial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false))
heading=playerData.unit:GetCoordinate():HeadingTo(initial)
distance=playerData.unit:GetCoordinate():Get2DDistance(initial)
elseif playerData.case==3 then
playerData.step=AIRBOSS.PatternStep.BULLSEYE
local zone=self:_GetZoneBullseye(playerData.case)
heading=playerData.unit:GetCoordinate():HeadingTo(zone:GetCoordinate())
distance=playerData.unit:GetCoordinate():Get2DDistance(zone:GetCoordinate())
end
local text=string.format("fly heading %03d° for %d NM to re-enter the pattern.",heading,UTILS.MetersToNM(distance))
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,5)
else
self:E(self.lid..string.format("ERROR: Player unit not alive!"))
end
elseif playerData.wofd then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
else
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
local text=string.format("deck was fouled but you landed anyway. Airboss wants to talk to you!")
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3)
end
elseif playerData.owo then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
else
self:E(self.lid.."ERROR: player landed when OWO was issues. This should not happen. Please report!")
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
end
elseif playerData.waveoff then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
else
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
local text=string.format("you were waved off but landed anyway. Airboss wants to talk to you!")
self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3)
end
elseif playerData.boltered then
if playerData.unit:InAir()then
playerData.step=AIRBOSS.PatternStep.BOLTER
end
elseif playerData.landed then
if not playerData.unit:InAir()then
self:Sound2Player(playerData,self.LSORadio,self.LSOCall.WELCOMEABOARD)
end
else
self:MessageToPlayer(playerData,"Undefined state after landing! Please report.","ERROR",nil,20)
playerData.step=AIRBOSS.PatternStep.UNDEFINED
end
if playerData.landed and not playerData.unit:InAir()then
self:_RecoveredElement(playerData.unit)
self:_CheckSectionRecovered(playerData)
end
playerData.passes=playerData.passes+1
self:_StepHint(playerData)
self:_InitPlayer(playerData,playerData.step)
MESSAGE:New(string.format("Player step %s.",playerData.step),5,"DEBUG"):ToAllIf(self.Debug)
if self.autosave and mygrade.finalscore then
self:Save(self.autosavepath,self.autosavefile)
end
end
function AIRBOSS:_CheckCollisionCoord(coordto,coordfrom)
local dx=100
local d=0
if coordfrom then
d=0
else
d=250
coordfrom=self:GetCoordinate():Translate(d,self:GetHeading())
end
local dmax=coordfrom:Get2DDistance(coordto)
local direction=coordfrom:HeadingTo(coordto)
local clear=true
while d<=dmax do
local cp=coordfrom:Translate(d,direction)
if not cp:IsSurfaceTypeWater()then
if self.Debug then
local st=cp:GetSurfaceType()
cp:MarkToAll(string.format("Collision check surface type %d",st))
end
clear=false
break
end
d=d+dx
end
local text=""
if clear then
text=string.format("Path into direction %03d° is clear for the next %.1f NM.",direction,UTILS.MetersToNM(d))
else
text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.",UTILS.MetersToNM(d),direction)
end
self:T2(self.lid..text)
return not clear,d
end
function AIRBOSS:_CheckFreePathToNextWP(fromcoord)
fromcoord=fromcoord or self:GetCoordinate():Translate(250,self:GetHeading())
local Nnextwp=math.min(self.currentwp+1,#self.waypoints)
local nextwp=self.waypoints[Nnextwp]
local collision=self:_CheckCollisionCoord(nextwp,fromcoord)
return collision
end
function AIRBOSS:_Pathfinder()
local hdg=self:GetHeading()
local cv=self:GetCoordinate()
local directions={-20,20,-30,30,-40,40,-50,50,-60,60,-70,70,-80,80,-90,90,-100,100}
for _,_direction in pairs(directions)do
local direction=hdg+_direction
local _,dfree=self:_CheckCollisionCoord(cv:Translate(UTILS.NMToMeters(20),direction),cv)
local distance=500
while distance<=dfree do
local fromcoord=cv:Translate(distance,direction)
local collision=self:_CheckFreePathToNextWP(fromcoord)
self:T2(self.lid..string.format("Pathfinder d=%.1f m, direction=%03d°, collision=%s",distance,direction,tostring(collision)))
if not collision then
self:CarrierDetour(fromcoord)
return
end
distance=distance+500
end
end
end
function AIRBOSS:CarrierResumeRoute(gotocoord)
AIRBOSS._ResumeRoute(self.carrier:GetGroup(),self,gotocoord)
return self
end
function AIRBOSS:CarrierDetour(coord,speed,uturn,uspeed,tcoord)
local pos0=self:GetCoordinate()
local vel0=self.carrier:GetVelocityKNOTS()
speed=speed or math.max(vel0,5)
local speedkmh=math.max(UTILS.KnotsToKmph(speed),UTILS.KnotsToKmph(2))
local cspeedkmh=math.max(self.carrier:GetVelocityKMH(),UTILS.KnotsToKmph(10))
local uspeedkmh=UTILS.KnotsToKmph(uspeed or speed)
local wp={}
table.insert(wp,pos0:WaypointGround(cspeedkmh))
if tcoord then
table.insert(wp,tcoord:WaypointGround(cspeedkmh))
end
table.insert(wp,coord:WaypointGround(speedkmh))
if uturn then
table.insert(wp,pos0:WaypointGround(uspeedkmh))
end
local group=self.carrier:GetGroup()
local TaskResumeRoute=group:TaskFunction("AIRBOSS._ResumeRoute",self)
group:SetTaskWaypoint(wp[#wp],TaskResumeRoute)
if self.Debug then
if tcoord then
tcoord:MarkToAll(string.format("Detour Turn Help WP. Speed %.1f knots",UTILS.KmphToKnots(cspeedkmh)))
end
coord:MarkToAll(string.format("Detour Waypoint. Speed %.1f knots",UTILS.KmphToKnots(speedkmh)))
if uturn then
pos0:MarkToAll(string.format("Detour U-turn WP. Speed %.1f knots",UTILS.KmphToKnots(uspeedkmh)))
end
end
self.detour=true
self.carrier:Route(wp)
end
function AIRBOSS:CarrierTurnIntoWind(time,vdeck,uturn)
local _,vwind=self:GetWind()
local vdeck=UTILS.MpsToKnots(vdeck)
local hiw,speedknots=self:GetHeadingIntoWind(vdeck)
local vtot=UTILS.KnotsToMps(speedknots)
local dist=vtot*time
local distNM=UTILS.MetersToNM(dist)
local hdg=self:GetHeading()
local deltaH=self:_GetDeltaHeading(hdg,hiw)
self:I(self.lid..string.format("Carrier steaming into the wind (%.1f kts). Heading=%03d-->%03d (Delta=%.1f), Speed=%.1f knots, Distance=%.1f NM, Time=%d sec",
UTILS.MpsToKnots(vwind),hdg,hiw,deltaH,speedknots,distNM,speedknots,time))
local Cv=self:GetCoordinate()
local Ctiw=nil
local Csoo=nil
if deltaH<45 then
Csoo=Cv:Translate(750,hdg):Translate(750,hiw)
local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
elseif deltaH<90 then
Csoo=Cv:Translate(900,hdg):Translate(900,hiw)
local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
elseif deltaH<135 then
Csoo=Cv:Translate(1100,hdg-90):Translate(1000,hiw)
local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
else
Csoo=Cv:Translate(1200,hdg-90):Translate(1000,hiw)
local hsw=self:GetHeadingIntoWind(vdeck,false,Csoo)
Ctiw=Csoo:Translate(dist,hsw)
end
self.Creturnto=self:GetCoordinate()
local nextwp=self:_GetNextWaypoint()
local vdownwind=UTILS.MpsToKnots(nextwp:GetVelocity())
if vdownwind<1 then
vdownwind=10
end
self:CarrierDetour(Ctiw,speedknots,uturn,vdownwind,Csoo)
self.turnintowind=true
return self
end
function AIRBOSS:_GetNextWaypoint()
local Nextwp=nil
if self.currentwp==#self.waypoints then
Nextwp=1
else
Nextwp=self.currentwp+1
end
local text=string.format("Current WP=%d/%d, next WP=%d",self.currentwp,#self.waypoints,Nextwp)
self:T2(self.lid..text)
local nextwp=self.waypoints[Nextwp]
return nextwp,Nextwp
end
function AIRBOSS:_InitWaypoints()
local Waypoints=self.carrier:GetGroup():GetTemplateRoutePoints()
self.waypoints={}
for i,point in ipairs(Waypoints)do
local coord=COORDINATE:New(point.x,point.alt,point.y)
coord:SetVelocity(point.speed)
table.insert(self.waypoints,coord)
if self.Debug then
coord:MarkToAll(string.format("Carrier Waypoint %d, Speed=%.1f knots",i,UTILS.MpsToKnots(point.speed)))
end
end
return self
end
function AIRBOSS:_PatrolRoute(n)
local nextWP,N=self:_GetNextWaypoint()
n=n or N
local CarrierGroup=self.carrier:GetGroup()
local Waypoints={}
local wp=self:GetCoordinate():WaypointGround(CarrierGroup:GetVelocityKMH())
table.insert(Waypoints,wp)
for i=n,#self.waypoints do
local coord=self.waypoints[i]
local wp=coord:WaypointGround(UTILS.MpsToKmph(coord.Velocity))
local TaskPassingWP=CarrierGroup:TaskFunction("AIRBOSS._PassingWaypoint",self,i,#self.waypoints)
CarrierGroup:SetTaskWaypoint(wp,TaskPassingWP)
table.insert(Waypoints,wp)
end
CarrierGroup:Route(Waypoints)
return self
end
function AIRBOSS:_GetETAatNextWP()
local cwp=self.currentwp
local tnow=timer.getAbsTime()
local p=self:GetCoordinate()
local v=self.carrier:GetVelocityMPS()
local nextWP=self:_GetNextWaypoint()
local s=p:Get2DDistance(nextWP)
local t=s/v
local eta=t+tnow
return eta
end
function AIRBOSS:_CheckCarrierTurning()
local vNew=self.carrier:GetOrientationX()
local vLast=self.Corientlast
vNew.y=0;
vLast.y=0
local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast)))
self.Corientlast=vNew
local turning=math.abs(deltaLast)>=1
if self.turning and not turning then
local FB=self:GetFinalBearing(true)
self:_MarshalCallNewFinalBearing(FB)
end
if turning and not self.turning then
local hdg
if self.turnintowind then
local vdeck=self.recoverywindow and self.recoverywindow.SPEED or 20
hdg=self:GetHeadingIntoWind(vdeck,false)
else
hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint())
end
hdg=hdg-self.magvar
if hdg<0 then
hdg=360+hdg
end
self:_MarshalCallCarrierTurnTo(hdg)
end
self.turning=turning
end
function AIRBOSS:_CheckPatternUpdate()
local dTPupdate=10*60
local Dupdate=UTILS.NMToMeters(2.5)
local Hupdate=5
local dt=timer.getTime()-self.Tpupdate
if dt<dTPupdate or self.turning then
return
end
local vNew=self.carrier:GetOrientationX()
local vOld=self.Corientation
vNew.y=0;
vOld.y=0
local deltaHeading=math.deg(math.acos(UTILS.VecDot(vNew,vOld)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vOld)))
local Hchange=false
if math.abs(deltaHeading)>=Hupdate then
self:T(self.lid..string.format("Carrier heading changed by %d°.",deltaHeading))
Hchange=true
end
local pos=self:GetCoordinate()
local dist=pos:Get2DDistance(self.Cposition)
local Dchange=false
if dist>=Dupdate then
self:T(self.lid..string.format("Carrier position changed by %.1f NM.",UTILS.MetersToNM(dist)))
Dchange=true
end
if Hchange or Dchange then
for _,_flight in pairs(self.Qmarshal)do
local flight=_flight
if flight.ai then
self:_MarshalAI(flight,flight.flag)
end
end
self.Corientation=vNew
self.Cposition=pos
self.Tpupdate=timer.getTime()
end
end
function AIRBOSS._PassingWaypoint(group,airboss,i,final)
local text=string.format("Group %s passing waypoint %d of %d.",group:GetName(),i,final)
if airboss.Debug and false then
local pos=group:GetCoordinate()
pos:SmokeRed()
local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d",group:GetName(),i))
end
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:T(airboss.lid..text)
airboss.currentwp=i
airboss:PassingWaypoint(i)
if i==final and final>1 and airboss.adinfinitum then
airboss:_PatrolRoute()
end
end
function AIRBOSS._ResumeRoute(group,airboss,gotocoord)
local nextwp,Nextwp=airboss:_GetNextWaypoint()
local speedkmh=nextwp.Velocity*3.6
if speedkmh<1 then
speedkmh=UTILS.KnotsToKmph(10)
end
local waypoints={}
local c0=group:GetCoordinate()
local wp0=c0:WaypointGround(speedkmh)
table.insert(waypoints,wp0)
if gotocoord then
local headingto=c0:HeadingTo(gotocoord)
local hdg1=airboss:GetHeading()
local hdg2=c0:HeadingTo(gotocoord)
local delta=airboss:_GetDeltaHeading(hdg1,hdg2)
if delta>90 then
local turnradius=UTILS.NMToMeters(3)
local gotocoordh=c0:Translate(turnradius,hdg1+45)
local wp=gotocoordh:WaypointGround(speedkmh)
table.insert(waypoints,wp)
gotocoordh=c0:Translate(turnradius,hdg1+90)
wp=gotocoordh:WaypointGround(speedkmh)
table.insert(waypoints,wp)
end
local wp1=gotocoord:WaypointGround(speedkmh)
table.insert(waypoints,wp1)
end
local text=string.format("Carrier is resuming route. Next waypoint %d, Speed=%.1f knots.",Nextwp,UTILS.KmphToKnots(speedkmh))
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:I(airboss.lid..text)
for i=Nextwp,#airboss.waypoints do
local coord=airboss.waypoints[i]
local speed=coord.Velocity*3.6
if speed<1 then
speed=UTILS.KnotsToKmph(10)
end
local wp=coord:WaypointGround(speed)
local TaskPassingWP=group:TaskFunction("AIRBOSS._PassingWaypoint",airboss,i,#airboss.waypoints)
group:SetTaskWaypoint(wp,TaskPassingWP)
table.insert(waypoints,wp)
end
airboss.turnintowind=false
airboss.detour=false
group:Route(waypoints)
end
function AIRBOSS._ReachedHoldingZone(group,airboss,flight)
local text=string.format("Flight %s reached holding zone.",group:GetName())
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:T(airboss.lid..text)
if airboss.Debug then
group:GetCoordinate():MarkToAll(text)
end
if flight then
flight.holding=true
flight.time=timer.getAbsTime()
end
end
function AIRBOSS._TaskFunctionMarshalAI(group,airboss,flight)
local text=string.format("Flight %s is send to marshal.",group:GetName())
MESSAGE:New(text,10):ToAllIf(airboss.Debug)
airboss:T(airboss.lid..text)
local stack=airboss:_GetFreeStack(flight.ai)
if stack then
airboss:_MarshalAI(flight,stack)
else
if not airboss:_InQueue(airboss.Qwaiting,flight.group)then
airboss:_WaitAI(flight)
end
end
if flight.refueling==true then
airboss:I(airboss.lid..string.format("Flight group %s finished refueling task.",flight.groupname))
end
flight.refueling=false
end
function AIRBOSS:_GetACNickname(actype)
local nickname="unknown"
if actype==AIRBOSS.AircraftCarrier.A4EC then
nickname="Skyhawk"
elseif actype==AIRBOSS.AircraftCarrier.T45C then
nickname="Goshawk"
elseif actype==AIRBOSS.AircraftCarrier.AV8B then
nickname="Harrier"
elseif actype==AIRBOSS.AircraftCarrier.E2D then
nickname="Hawkeye"
elseif actype==AIRBOSS.AircraftCarrier.C2A then
nickname="Greyhound"
elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B then
nickname="Tomcat"
elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then
nickname="Hornet"
elseif actype==AIRBOSS.AircraftCarrier.RHINOE or actype==AIRBOSS.AircraftCarrier.RHINOF then
nickname="Rhino"
elseif actype==AIRBOSS.AircraftCarrier.GROWLER then
nickname="Growler"
elseif actype==AIRBOSS.AircraftCarrier.S3B or actype==AIRBOSS.AircraftCarrier.S3BTANKER then
nickname="Viking"
end
return nickname
end
function AIRBOSS:_GetOnboardNumberPlayer(group)
return self:_GetOnboardNumbers(group,true)
end
function AIRBOSS:_GetOnboardNumbers(group,playeronly)
local groupname=group:GetName()
local text=string.format("Onboard numbers of group %s:",groupname)
local 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
local skill=unit.skill or"Unknown"
text=text..string.format("\n- unit %s: onboard #=%s  skill=%s",name,n,tostring(skill))
if playeronly and skill=="Client"or skill=="Player"then
return n
end
numbers[name]=n
end
self:T2(self.lid..text)
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()
self.TowerFreq=0
local striketemplate=self.carrier:GetGroup():GetTemplate()
for _,unit in pairs(striketemplate.units)do
if self.carrier:GetName()==unit.name then
self.TowerFreq=unit.frequency/1000000
return
end
end
end
function AIRBOSS:_GetGoodBadScore(playerData)
local lowscore
local badscore
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
lowscore=10
badscore=20
elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then
lowscore=5
badscore=10
elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then
lowscore=2.5
badscore=5
end
return lowscore,badscore
end
function AIRBOSS:_IsCarrierAircraft(unit)
local aircrafttype=unit:GetTypeName()
if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
return true
else
return false
end
end
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then
return false
end
end
for _,actype in pairs(AIRBOSS.AircraftCarrier)do
if actype==aircrafttype then
return true
end
end
return false
end
function AIRBOSS:_IsHumanUnit(unit)
local playerunit=self:_GetPlayerUnitAndName(unit:GetName())
if playerunit then
return true
else
return false
end
end
function AIRBOSS:_IsHuman(group)
local units=group:GetUnits()
for _,_unit in pairs(units)do
local human=self:_IsHumanUnit(_unit)
if human then
return true
end
end
return false
end
function AIRBOSS:_GetFuelState(unit)
local fuel=unit:GetFuel()
local maxfuel=self:_GetUnitMasses(unit)
local fuelstate=fuel*maxfuel
self:T2(self.lid..string.format("Unit %s fuel state = %.1f kg = %.1f lbs",unit:GetName(),fuelstate,UTILS.kg2lbs(fuelstate)))
return UTILS.kg2lbs(fuelstate)
end
function AIRBOSS:_GetAngels(alt)
if alt then
local angels=UTILS.Round(UTILS.MetersToFeet(alt)/1000,0)
return angels
else
return 0
end
end
function AIRBOSS:_GetUnitMasses(unit)
local Desc=unit:GetDesc()
local massfuel=Desc.fuelMassMax or 0
local massempty=Desc.massEmpty or 0
local massmax=Desc.massMax or 0
local masscargo=massmax-massfuel-massempty
self:T2(self.lid..string.format("Unit %s mass fuel=%.1f kg, empty=%.1f kg, max=%.1f kg, cargo=%.1f kg",unit:GetName(),massfuel,massempty,massmax,masscargo))
return massfuel,massempty,massmax,masscargo
end
function AIRBOSS:_GetPlayerDataUnit(unit)
if unit:IsAlive()then
local unitname=unit:GetName()
local playerunit,playername=self:_GetPlayerUnitAndName(unitname)
if playerunit and playername then
return self.players[playername]
end
end
return nil
end
function AIRBOSS:_GetPlayerDataGroup(group)
local units=group:GetUnits()
for _,unit in pairs(units)do
local playerdata=self:_GetPlayerDataUnit(unit)
if playerdata then
return playerdata
end
end
return nil
end
function AIRBOSS:_GetPlayerUnit(_unitName)
for _,_player in pairs(self.players)do
local player=_player
if player.unit and player.unit:GetName()==_unitName then
self:T(self.lid..string.format("Found player=%s unit=%s in players table.",tostring(player.name),tostring(_unitName)))
return player.unit,player.name
end
end
return nil,nil
end
function AIRBOSS:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName~=nil then
local u,pn=self:_GetPlayerUnit(_unitName)
if u and pn then
return u,pn
end
local DCSunit=Unit.getByName(_unitName)
if DCSunit and DCSunit.getPlayerName then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit,unit=unit,playername=playername})
if DCSunit and unit and playername then
self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername)))
return unit,playername
end
end
end
return nil,nil
end
function AIRBOSS:GetCoalition()
return self.carrier:GetCoalition()
end
function AIRBOSS:GetCoordinate()
return self.carrier:GetCoord()
end
function AIRBOSS:GetCoord()
return self.carrier:GetCoord()
end
function AIRBOSS:_GetStaticWeather()
local weather=env.mission.weather
local clouds=weather.clouds
local visibility=weather.visibility.distance
local dust=nil
if weather.enable_dust==true then
dust=weather.dust_density
end
local fog=nil
if weather.enable_fog==true then
fog=weather.fog
end
return clouds,visibility,fog,dust
end
function AIRBOSS._CheckRadioQueueT(param,time)
AIRBOSS._CheckRadioQueue(param.airboss,param.radioqueue,param.name)
return time+0.05
end
function AIRBOSS:_CheckRadioQueue(radioqueue,name)
if#radioqueue==0 then
if name=="LSO"then
self:T(self.lid..string.format("Stopping LSO radio queue."))
self.radiotimer:Stop(self.RQLid)
self.RQLid=nil
elseif name=="MARSHAL"then
self:T(self.lid..string.format("Stopping Marshal radio queue."))
self.radiotimer:Stop(self.RQMid)
self.RQMid=nil
end
return
end
local _time=timer.getAbsTime()
local playing=false
local next=nil
local _remove=nil
for i,_transmission in ipairs(radioqueue)do
local transmission=_transmission
if _time>=transmission.Tplay then
if transmission.isplaying then
if _time>=transmission.Tstarted+transmission.call.duration then
transmission.isplaying=false
_remove=i
if transmission.radio.alias=="LSO"then
self.TQLSO=_time
elseif transmission.radio.alias=="MARSHAL"then
self.TQMarshal=_time
end
else
playing=true
end
else
local Tlast=nil
if transmission.interval then
if transmission.radio.alias=="LSO"then
Tlast=self.TQLSO
elseif transmission.radio.alias=="MARSHAL"then
Tlast=self.TQMarshal
end
end
if transmission.interval==nil then
if next==nil then
next=transmission
end
else
if _time-Tlast>=transmission.interval then
next=transmission
else
end
end
if next or Tlast then
break
end
end
else
end
end
if next~=nil and not playing then
self:Broadcast(next.radio,next.call,next.loud)
next.isplaying=true
next.Tstarted=_time
end
if _remove then
table.remove(radioqueue,_remove)
end
return
end
function AIRBOSS:RadioTransmission(radio,call,loud,delay,interval,click,pilotcall)
self:F2({radio=radio,call=call,loud=loud,delay=delay,interval=interval,click=click})
if radio==nil or call==nil then
return
end
if not self.SRS then
local transmission={}
transmission.radio=radio
transmission.call=call
transmission.Tplay=timer.getAbsTime()+(delay or 0)
transmission.interval=interval
transmission.isplaying=false
transmission.Tstarted=nil
transmission.loud=loud and call.loud
if self:_IsOnboard(call.modexsender)then
self:_Number2Radio(radio,call.modexsender,delay,0.3,pilotcall)
end
if self:_IsOnboard(call.modexreceiver)then
self:_Number2Radio(radio,call.modexreceiver,delay,0.3,pilotcall)
end
local caller=""
if radio.alias=="LSO"then
table.insert(self.RQLSO,transmission)
caller="LSOCall"
if not self.RQLid then
self:T(self.lid..string.format("Starting LSO radio queue."))
self.RQLid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQLSO,"LSO"},0.02,0.05)
end
elseif radio.alias=="MARSHAL"then
table.insert(self.RQMarshal,transmission)
caller="MarshalCall"
if not self.RQMid then
self:T(self.lid..string.format("Starting Marhal radio queue."))
self.RQMid=self.radiotimer:Schedule(nil,AIRBOSS._CheckRadioQueue,{self,self.RQMarshal,"MARSHAL"},0.02,0.05)
end
end
if click then
self:RadioTransmission(radio,self[caller].CLICK,false,delay)
end
else
if call.subtitle~=nil and string.len(call.subtitle)>1 then
local frequency=self.MarshalRadio.frequency
local modulation=self.MarshalRadio.modulation
local voice=nil
local gender=nil
local culture=nil
if radio.alias=="AIRBOSS"then
frequency=self.AirbossRadio.frequency
modulation=self.AirbossRadio.modulation
voice=self.AirbossRadio.voice
gender=self.AirbossRadio.gender
culture=self.AirbossRadio.culture
end
if radio.alias=="MARSHAL"then
voice=self.MarshalRadio.voice
gender=self.MarshalRadio.gender
culture=self.MarshalRadio.culture
end
if radio.alias=="LSO"then
frequency=self.LSORadio.frequency
modulation=self.LSORadio.modulation
voice=self.LSORadio.voice
gender=self.LSORadio.gender
culture=self.LSORadio.culture
end
if pilotcall then
voice=self.PilotRadio.voice
gender=self.PilotRadio.gender
culture=self.PilotRadio.culture
radio.alias="PILOT"
end
if not radio.alias then
frequency=self.AirbossRadio.frequency
modulation=self.AirbossRadio.modulation
radio.alias="AIRBOSS"
end
local volume=nil
if loud then
volume=1.0
end
local text=call.subtitle
self:T(self.lid..text)
local srstext=self:_GetNiceSRSText(text)
self.SRSQ:NewTransmission(srstext,call.duration,self.SRS,nil,0.1,nil,call.subtitle,call.subduration,frequency,modulation,gender,culture,voice,volume,radio.alias)
end
end
end
function AIRBOSS:SetSRSPilotVoice(Voice,Gender,Culture)
self.PilotRadio={}
self.PilotRadio.alias="PILOT"
self.PilotRadio.voice=Voice or MSRS.Voices.Microsoft.David
self.PilotRadio.gender=Gender or"male"
self.PilotRadio.culture=Culture or"en-US"
if(not Voice)and self.SRS and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then
self.PilotRadio.voice=MSRS.Voices.Google.Standard.en_US_Standard_J
end
return self
end
function AIRBOSS:_NeedsSubtitle(call)
if call.file==self.MarshalCall.NOISE.file or call.file==self.LSOCall.NOISE.file then
return true
else
return false
end
end
function AIRBOSS:Broadcast(radio,call,loud)
self:F(call)
if not self.usersoundradio then
local sender=self:_GetRadioSender(radio)
local filename=self:_RadioFilename(call,loud,radio.alias)
local subtitle=self:_RadioSubtitle(radio,call,loud)
self:T({filename=filename,subtitle=subtitle})
if sender then
self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName()))
local commandFrequency={
id="SetFrequency",
params={
frequency=radio.frequency*1000000,
modulation=radio.modulation,
},
}
local commandTransmit={
id="TransmitMessage",
params={
file=filename,
duration=call.subduration or 5,
subtitle=subtitle,
loop=false,
},
}
sender:SetCommand(commandFrequency)
sender:SetCommand(commandTransmit)
else
self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission()."))
local vec3=self.carrier:GetPositionVec3()
trigger.action.radioTransmission(filename,vec3,radio.modulation,false,radio.frequency*1000000,100)
for _,_player in pairs(self.players)do
local playerData=_player
if playerData.unit:IsInZone(self.zoneCCA)and playerData.actype~=AIRBOSS.AircraftCarrier.A4EC then
if playerData.subtitles or self:_NeedsSubtitle(call)then
if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then
self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration or 5)
end
end
end
end
end
end
for _,_player in pairs(self.players)do
local playerData=_player
if self.usersoundradio or playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
if radio.alias=="MARSHAL"or(radio.alias=="LSO"and self:_InQueue(self.Qpattern,playerData.group))then
self:Sound2Player(playerData,radio,call,loud)
end
end
end
end
function AIRBOSS:Sound2Player(playerData,radio,call,loud,delay)
if playerData.unit:IsInZone(self.zoneCCA)and call then
local filename=self:_RadioFilename(call,loud,radio.alias)
local subtitle=self:_RadioSubtitle(radio,call,loud)
USERSOUND:New(filename):ToGroup(playerData.group,delay)
if playerData.subtitles or self:_NeedsSubtitle(call)then
self:MessageToPlayer(playerData,subtitle,nil,"",call.subduration,false,delay)
end
end
end
function AIRBOSS:_RadioSubtitle(radio,call,loud)
if call==nil or call.subtitle==nil or call.subtitle==""then
return""
end
local sender=call.sender or radio.alias
if call.modexsender then
sender=call.modexsender
end
local receiver=call.modexreceiver or""
local subtitle=string.format("%s: %s",sender,call.subtitle)
if receiver and receiver~=""then
subtitle=string.format("%s: %s, %s",sender,receiver,call.subtitle)
end
local lastchar=string.sub(subtitle,-1)
if loud then
if lastchar=="."or lastchar=="!"then
subtitle=string.sub(subtitle,1,-1)
end
subtitle=subtitle.."!"
else
if lastchar=="!"then
elseif lastchar=="."then
else
subtitle=subtitle.."."
end
end
return subtitle
end
function AIRBOSS:_RadioFilename(call,loud,channel)
local prefix=call.file or""
local suffix=call.suffix or"ogg"
local path=self.soundfolder or"l10n/DEFAULT/"
if string.find(call.file,"LSO-")and channel and(channel=="LSO"or channel=="LSOCall")then
path=self.soundfolderLSO or path
end
if string.find(call.file,"MARSHAL-")and channel and(channel=="MARSHAL"or channel=="MarshalCall")then
path=self.soundfolderMSH or path
end
if loud then
prefix=prefix.."_Loud"
end
local filename=string.format("%s%s.%s",path,prefix,suffix)
return filename
end
function AIRBOSS:_GetNiceSRSText(text)
text=string.gsub(text,"================================\n","")
text=string.gsub(text,"||","parallel")
text=string.gsub(text,"==","perpendicular")
text=string.gsub(text,"BRC","Base recovery")
text=string.gsub(text,"%((%a+)%)","Morse %1")
text=string.gsub(text,"°C","° Celsius")
text=string.gsub(text,"°"," degrees")
text=string.gsub(text," FB "," Final bearing ")
text=string.gsub(text," ops"," operations ")
text=string.gsub(text," kts"," knots")
text=string.gsub(text,"TACAN","Tackan")
text=string.gsub(text,"ICLS","I.C.L.S.")
text=string.gsub(text,"LSO","L.S.O.")
text=string.gsub(text,"inHg","inches of Mercury")
text=string.gsub(text,"QFE","Q.F.E.")
text=string.gsub(text,"hPa","hecto pascal")
text=string.gsub(text," NM"," nautical miles")
text=string.gsub(text," ft"," feet")
text=string.gsub(text,"A/C","aircraft")
text=string.gsub(text,"(#[%a%d%p%s]+)\n","")
text=string.gsub(text,"%.000"," dot zero")
text=string.gsub(text,"00"," double zero")
text=string.gsub(text," 0 "," zero ")
text=string.gsub(text,"\n","; ")
return text
end
function AIRBOSS:MessageToPlayer(playerData,message,sender,receiver,duration,clear,delay)
self:T({sender,receiver,message})
if playerData and message and message~=""then
duration=duration or self.Tmessage
local text
if receiver and receiver==""then
text=string.format("%s",message)
else
receiver=receiver or playerData.onboard
text=string.format("%s, %s",receiver,message)
end
self:T(self.lid..text)
if delay and delay>0 then
self:ScheduleOnce(delay,self.MessageToPlayer,self,playerData,message,sender,receiver,duration,clear)
else
if not self.SRS then
local wait=0
if receiver==playerData.onboard then
if sender and(sender=="LSO"or sender=="MARSHAL"or sender=="AIRBOSS")then
wait=wait+self:_Number2Sound(playerData,sender,receiver)
end
end
if string.find(text:lower(),"negative")then
local filename=self:_RadioFilename(self.MarshalCall.NEGATIVE,false,"MARSHAL")
USERSOUND:New(filename):ToGroup(playerData.group,wait)
wait=wait+self.MarshalCall.NEGATIVE.duration
end
if string.find(text:lower(),"affirm")then
local filename=self:_RadioFilename(self.MarshalCall.AFFIRMATIVE,false,"MARSHAL")
USERSOUND:New(filename):ToGroup(playerData.group,wait)
wait=wait+self.MarshalCall.AFFIRMATIVE.duration
end
if string.find(text:lower(),"roger")then
local filename=self:_RadioFilename(self.MarshalCall.ROGER,false,"MARSHAL")
USERSOUND:New(filename):ToGroup(playerData.group,wait)
wait=wait+self.MarshalCall.ROGER.duration
end
if wait>0 then
local filename=self:_RadioFilename(self.MarshalCall.CLICK)
USERSOUND:New(filename):ToGroup(playerData.group,wait)
end
else
local frequency=self.MarshalRadio.frequency
local modulation=self.MarshalRadio.modulation
local voice=self.MarshalRadio.voice
local gender=self.MarshalRadio.gender
local culture=self.MarshalRadio.culture
if not sender then sender="AIRBOSS"end
if string.find(sender,"AIRBOSS")then
frequency=self.AirbossRadio.frequency
modulation=self.AirbossRadio.modulation
voice=self.AirbossRadio.voice
gender=self.AirbossRadio.gender
culture=self.AirbossRadio.culture
end
if sender=="LSO"then
frequency=self.LSORadio.frequency
modulation=self.LSORadio.modulation
voice=self.LSORadio.voice
gender=self.LSORadio.gender
culture=self.LSORadio.culture
end
self:T(self.lid..text)
self:T({sender,frequency,modulation,voice})
local srstext=self:_GetNiceSRSText(text)
self.SRSQ:NewTransmission(srstext,duration,self.SRS,nil,0.1,nil,nil,nil,frequency,modulation,gender,culture,voice,nil,sender)
end
if playerData.client then
MESSAGE:New(text,duration,sender,clear):ToClient(playerData.client)
end
end
end
end
function AIRBOSS:MessageToPattern(message,sender,receiver,duration,clear,delay)
local call=self:_NewRadioCall(self.LSOCall.NOISE,sender or"LSO",message,duration,receiver,sender)
self:RadioTransmission(self.LSORadio,call,false,delay,nil,true)
end
function AIRBOSS:MessageToMarshal(message,sender,receiver,duration,clear,delay)
local call=self:_NewRadioCall(self.MarshalCall.NOISE,sender or"MARSHAL",message,duration,receiver,sender)
self:RadioTransmission(self.MarshalRadio,call,false,delay,nil,true)
end
function AIRBOSS:_NewRadioCall(call,sender,subtitle,subduration,modexreceiver,modexsender)
local newcall=UTILS.DeepCopy(call)
newcall.sender=sender
newcall.subtitle=subtitle or call.subtitle
newcall.subduration=subduration or self.Tmessage
if self:_IsOnboard(modexreceiver)then
newcall.modexreceiver=modexreceiver
end
if self:_IsOnboard(modexsender)then
newcall.modexsender=modexsender
end
return newcall
end
function AIRBOSS:_GetRadioSender(radio)
local sender=nil
if self.senderac then
sender=UNIT:FindByName(self.senderac)
end
if radio.alias=="MARSHAL"then
if self.radiorelayMSH then
sender=UNIT:FindByName(self.radiorelayMSH)
end
end
if radio.alias=="LSO"then
if self.radiorelayLSO then
sender=UNIT:FindByName(self.radiorelayLSO)
end
end
if sender and sender:IsAlive()and sender:IsAir()then
return sender
end
return nil
end
function AIRBOSS:_IsOnboard(text)
if text==nil then
return false
end
if text=="99"then
return true
end
for _,_flight in pairs(self.flights)do
local flight=_flight
for _,onboard in pairs(flight.onboardnumbers)do
if text==onboard then
return true
end
end
end
return false
end
function AIRBOSS:_Number2Sound(playerData,sender,number,delay)
delay=delay or 0
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars,c)
end
return chars
end
local Sender
if sender=="LSO"then
Sender="LSOCall"
elseif sender=="MARSHAL"or sender=="AIRBOSS"then
Sender="MarshalCall"
else
self:E(self.lid..string.format("ERROR: Unknown radio sender %s!",tostring(sender)))
return
end
local numbers=_split(tostring(number))
local wait=0
for i=1,#numbers do
local n=numbers[i]
local N=string.format("N%s",n)
local call=self[Sender][N]
local filename=self:_RadioFilename(call,false,Sender)
USERSOUND:New(filename):ToGroup(playerData.group,delay+wait)
wait=wait+call.duration
end
return wait
end
function AIRBOSS:_Number2Radio(radio,number,delay,interval,pilotcall)
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars,c)
end
return chars
end
local Sender=""
if radio.alias=="LSO"then
Sender="LSOCall"
elseif radio.alias=="MARSHAL"then
Sender="MarshalCall"
else
self:E(self.lid..string.format("ERROR: Unknown radio alias %s!",tostring(radio.alias)))
end
if pilotcall then
Sender="PilotCall"
end
if Sender==""then
self:E(self.lid..string.format("ERROR: Sender unknown!"))
return
end
local numbers=_split(tostring(number))
local wait=0
for i=1,#numbers do
local n=numbers[i]
local N=string.format("N%s",n)
local call=self[Sender][N]
if interval and i==1 then
self:RadioTransmission(radio,call,false,delay,interval)
else
self:RadioTransmission(radio,call,false,delay)
end
wait=wait+call.duration
end
return wait
end
function AIRBOSS:_MarshallInboundCall(unit,modex)
local vectorCarrier=self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate())
local bearing=UTILS.Round(unit:GetCoordinate():GetAngleDegrees(vectorCarrier),0)
local distance=UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0)
local angels=UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0)
local state=UTILS.Round(self:_GetFuelState(unit)/1000,1)
local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f",modex,bearing,distance,angels,state)
self:T(self.lid..text)
local FS=UTILS.Split(string.format("%.1f",state),".")
local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper())
self:RadioTransmission(self.MarshalRadio,inboundcall)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARSHAL,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.MARKINGMOMS,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,tostring(bearing),nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.FOR,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,tostring(distance),nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.ANGELS,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,tostring(angels),nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.STATE,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,FS[1],nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.POINT,nil,nil,nil,nil,true)
self:_Number2Radio(self.MarshalRadio,FS[2],nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true)
end
function AIRBOSS:_CommencingCall(unit,modex)
local text=string.format("%s, commencing",modex)
self:T(self.lid..text)
local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK,unit.UnitName:upper(),text,self.Tmessage,nil,unit.UnitName:upper())
self:RadioTransmission(self.MarshalRadio,commencingCall)
self:_Number2Radio(self.MarshalRadio,modex,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.COMMENCING,nil,nil,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.MarshalRadio.CLICK,nil,nil,nil,nil,true)
end
function AIRBOSS:_LSOCallAircraftBall(modex,nickname,fuelstate)
local text=string.format("%s Ball, %.1f.",nickname,fuelstate)
self:T(self.lid..text)
local NICKNAME=nickname:upper()
local FS=UTILS.Split(string.format("%.1f",fuelstate),".")
local call=self:_NewRadioCall(self.PilotCall[NICKNAME],modex,text,self.Tmessage,nil,modex)
self:RadioTransmission(self.LSORadio,call,nil,nil,nil,nil,true)
self:RadioTransmission(self.LSORadio,self.PilotCall.BALL,nil,nil,nil,nil,true)
self:_Number2Radio(self.LSORadio,FS[1],nil,nil,true)
self:RadioTransmission(self.LSORadio,self.PilotCall.POINT,nil,nil,nil,nil,true)
self:_Number2Radio(self.LSORadio,FS[2],nil,nil,true)
self:RadioTransmission(self.LSORadio,self.LSOCall.CLICK)
end
function AIRBOSS:_MarshalCallGasAtTanker(modex)
local text=string.format("Bingo fuel! Going for gas at the recovery tanker.")
self:T(self.lid..text)
local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex)
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATTANKER,nil,nil,nil,true,true)
end
function AIRBOSS:_MarshalCallGasAtDivert(modex,divertname)
local text=string.format("Bingo fuel! Going for gas at divert field %s.",divertname)
self:T(self.lid..text)
local call=self:_NewRadioCall(self.PilotCall.BINGOFUEL,modex,text,self.Tmessage,nil,modex)
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,nil,true)
self:RadioTransmission(self.MarshalRadio,self.PilotCall.GASATDIVERT,nil,nil,nil,true,true)
end
function AIRBOSS:_MarshalCallRecoveryStopped(case)
local text=string.format("Case %d recovery ops are stopped. Deck is closed.",case)
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.CASE,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,tostring(case))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERYOPSSTOPPED,nil,nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DECKCLOSED,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallRecoveryPausedUntilFurtherNotice()
local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDNOTICE,"AIRBOSS",nil,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallRecoveryPausedResumedAt(clock)
local _clock=UTILS.Split(clock,"+")
local CT=UTILS.Split(_clock[1],":")
local text=string.format("aircraft recovery is paused and will be resumed at %s.",clock)
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.RECOVERYPAUSEDRESUMED,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,CT[1])
self:_Number2Radio(self.MarshalRadio,CT[2])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallClearedForRecovery(modex,case)
local text=string.format("you're cleared for Case %d recovery.",case)
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.CLEAREDFORRECOVERY,"MARSHAL",text,self.Tmessage,modex)
local delay=2
self:RadioTransmission(self.MarshalRadio,call,nil,delay)
self:_Number2Radio(self.MarshalRadio,tostring(case),delay)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RECOVERY,nil,delay,nil,true)
end
function AIRBOSS:_MarshalCallResumeRecovery()
local call=self:_NewRadioCall(self.MarshalCall.RESUMERECOVERY,"AIRBOSS",nil,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallNewFinalBearing(FB)
local text=string.format("new final bearing %03d°.",FB)
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.NEWFB,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",FB),nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallCarrierTurnTo(hdg)
local text=string.format("carrier is now starting turn to heading %03d°.",hdg)
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.CARRIERTURNTOHEADING,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",hdg),nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallStackFull(modex,nwaiting)
local text=string.format("Marshal stack is currently full. Hold outside 10 NM zone and wait for further instructions. ")
if nwaiting==1 then
text=text..string.format("There is one flight ahead of you.")
elseif nwaiting>1 then
text=text..string.format("There are %d flights ahead of you.",nwaiting)
else
text=text..string.format("You are next in line.")
end
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.STACKFULL,"AIRBOSS",text,self.Tmessage,modex)
self:RadioTransmission(self.MarshalRadio,call,nil,nil,nil,true)
end
function AIRBOSS:_MarshalCallRecoveryStart(case)
local radial=self:GetRadial(case,true,true,false)
local text=string.format("Starting aircraft recovery Case %d ops.",case)
if case==1 then
text=text..string.format(" BRC %03d°.",self:GetBRC())
elseif case==2 then
text=text..string.format(" Marshal radial %03d°. BRC %03d°.",radial,self:GetBRC())
elseif case==3 then
text=text..string.format(" Marshal radial %03d°. Final heading %03d°.",radial,self:GetFinalBearing(false))
end
self:T(self.lid..text)
local call=self:_NewRadioCall(self.MarshalCall.STARTINGRECOVERY,"AIRBOSS",text,self.Tmessage,"99")
self:RadioTransmission(self.MarshalRadio,call)
self:_Number2Radio(self.MarshalRadio,tostring(case),nil,0.1)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.OPS)
if case>1 then
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.MARSHALRADIAL)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",radial),nil,0.2)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES,nil,nil,nil,true)
end
end
function AIRBOSS:_MarshalCallArrived(modex,case,brc,altitude,charlie,qfe)
self:F({modex=modex,case=case,brc=brc,altitude=altitude,charlie=charlie,qfe=qfe})
local angels=self:_GetAngels(altitude)
local QFE=UTILS.Split(string.format("%.2f",qfe),".")
local clock=UTILS.Split(charlie,"+")
local CT=UTILS.Split(clock[1],":")
local text=string.format("Case %d, expected BRC %03d°, hold at angels %d. Expected Charlie Time %s. Altimeter %.2f. Report see me.",case,brc,angels,charlie,qfe)
self:T(self.lid..text)
local casecall=self:_NewRadioCall(self.MarshalCall.CASE,"MARSHAL",text,self.Tmessage,modex)
self:RadioTransmission(self.MarshalRadio,casecall)
self:_Number2Radio(self.MarshalRadio,tostring(case))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.BRC)
self:_Number2Radio(self.MarshalRadio,string.format("%03d",brc))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.DEGREES)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOLDATANGELS,nil,nil,0.5)
self:_Number2Radio(self.MarshalRadio,tostring(angels))
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.EXPECTED,nil,nil,0.5)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.CHARLIETIME)
self:_Number2Radio(self.MarshalRadio,CT[1])
self:_Number2Radio(self.MarshalRadio,CT[2])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.HOURS)
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.ALTIMETER,nil,nil,0.5)
self:_Number2Radio(self.MarshalRadio,QFE[1])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.POINT)
self:_Number2Radio(self.MarshalRadio,QFE[2])
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.REPORTSEEME,nil,nil,0.5,true)
end
function AIRBOSS:_AddF10Commands(_unitName)
self:F(_unitName)
local _unit,playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and playername then
local group=_unit:GetGroup()
local gid=group:GetID()
if group and gid then
if not self.menuadded[gid]then
self.menuadded[gid]=true
local _rootPath=nil
if AIRBOSS.MenuF10Root then
if self.menusingle then
_rootPath=AIRBOSS.MenuF10Root
else
_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10Root)
end
else
if AIRBOSS.MenuF10[gid]==nil then
AIRBOSS.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid,"Airboss")
end
if self.menusingle then
_rootPath=AIRBOSS.MenuF10[gid]
else
_rootPath=missionCommands.addSubMenuForGroup(gid,self.alias,AIRBOSS.MenuF10[gid])
end
end
local _helpPath=missionCommands.addSubMenuForGroup(gid,"Help",_rootPath)
if self.menumarkzones then
local _markPath=missionCommands.addSubMenuForGroup(gid,"Mark Zones",_helpPath)
if self.menusmokezones then
missionCommands.addCommandForGroup(gid,"Smoke Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,false)
end
missionCommands.addCommandForGroup(gid,"Flare Pattern Zones",_markPath,self._MarkCaseZones,self,_unitName,true)
if self.menusmokezones then
missionCommands.addCommandForGroup(gid,"Smoke Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,false)
end
missionCommands.addCommandForGroup(gid,"Flare Marshal Zone",_markPath,self._MarkMarshalZone,self,_unitName,true)
end
local _skillPath=missionCommands.addSubMenuForGroup(gid,"Skill Level",_helpPath)
missionCommands.addCommandForGroup(gid,"Flight Student",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.EASY)
missionCommands.addCommandForGroup(gid,"Naval Aviator",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.NORMAL)
missionCommands.addCommandForGroup(gid,"TOPGUN Graduate",_skillPath,self._SetDifficulty,self,_unitName,AIRBOSS.Difficulty.HARD)
missionCommands.addCommandForGroup(gid,"Hints On/Off",_skillPath,self._SetHintsOnOff,self,_unitName)
missionCommands.addCommandForGroup(gid,"My Status",_helpPath,self._DisplayPlayerStatus,self,_unitName)
missionCommands.addCommandForGroup(gid,"Attitude Monitor",_helpPath,self._DisplayAttitude,self,_unitName)
missionCommands.addCommandForGroup(gid,"Radio Check LSO",_helpPath,self._LSORadioCheck,self,_unitName)
missionCommands.addCommandForGroup(gid,"Radio Check Marshal",_helpPath,self._MarshalRadioCheck,self,_unitName)
missionCommands.addCommandForGroup(gid,"Subtitles On/Off",_helpPath,self._SubtitlesOnOff,self,_unitName)
missionCommands.addCommandForGroup(gid,"Trapsheet On/Off",_helpPath,self._TrapsheetOnOff,self,_unitName)
local _kneeboardPath=missionCommands.addSubMenuForGroup(gid,"Kneeboard",_rootPath)
local _resultsPath=missionCommands.addSubMenuForGroup(gid,"Results",_kneeboardPath)
missionCommands.addCommandForGroup(gid,"Greenie Board",_resultsPath,self._DisplayScoreBoard,self,_unitName)
missionCommands.addCommandForGroup(gid,"My LSO Grades",_resultsPath,self._DisplayPlayerGrades,self,_unitName)
missionCommands.addCommandForGroup(gid,"Last Debrief",_resultsPath,self._DisplayDebriefing,self,_unitName)
if self.skipperMenu then
local _skipperPath=missionCommands.addSubMenuForGroup(gid,"Skipper",_kneeboardPath)
local _menusetspeed=missionCommands.addSubMenuForGroup(gid,"Set Speed",_skipperPath)
missionCommands.addCommandForGroup(gid,"10 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,10)
missionCommands.addCommandForGroup(gid,"15 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,15)
missionCommands.addCommandForGroup(gid,"20 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,20)
missionCommands.addCommandForGroup(gid,"25 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,25)
missionCommands.addCommandForGroup(gid,"30 knots",_menusetspeed,self._SkipperRecoverySpeed,self,_unitName,30)
local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Time",_skipperPath)
missionCommands.addCommandForGroup(gid,"15 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,15)
missionCommands.addCommandForGroup(gid,"30 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,30)
missionCommands.addCommandForGroup(gid,"45 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,45)
missionCommands.addCommandForGroup(gid,"60 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,60)
missionCommands.addCommandForGroup(gid,"90 min",_menusetrtime,self._SkipperRecoveryTime,self,_unitName,90)
local _menusetrtime=missionCommands.addSubMenuForGroup(gid,"Set Marshal Radial",_skipperPath)
missionCommands.addCommandForGroup(gid,"+30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,30)
missionCommands.addCommandForGroup(gid,"+15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,15)
missionCommands.addCommandForGroup(gid,"0°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,0)
missionCommands.addCommandForGroup(gid,"-15°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-15)
missionCommands.addCommandForGroup(gid,"-30°",_menusetrtime,self._SkipperRecoveryOffset,self,_unitName,-30)
missionCommands.addCommandForGroup(gid,"U-turn On/Off",_skipperPath,self._SkipperRecoveryUturn,self,_unitName)
missionCommands.addCommandForGroup(gid,"Start CASE I",_skipperPath,self._SkipperStartRecovery,self,_unitName,1)
missionCommands.addCommandForGroup(gid,"Start CASE II",_skipperPath,self._SkipperStartRecovery,self,_unitName,2)
missionCommands.addCommandForGroup(gid,"Start CASE III",_skipperPath,self._SkipperStartRecovery,self,_unitName,3)
missionCommands.addCommandForGroup(gid,"Stop Recovery",_skipperPath,self._SkipperStopRecovery,self,_unitName)
end
missionCommands.addCommandForGroup(gid,"Carrier Info",_kneeboardPath,self._DisplayCarrierInfo,self,_unitName)
missionCommands.addCommandForGroup(gid,"Weather Report",_kneeboardPath,self._DisplayCarrierWeather,self,_unitName)
missionCommands.addCommandForGroup(gid,"Set Section",_kneeboardPath,self._SetSection,self,_unitName)
missionCommands.addCommandForGroup(gid,"Marshal Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Marshal")
missionCommands.addCommandForGroup(gid,"Pattern Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Pattern")
missionCommands.addCommandForGroup(gid,"Waiting Queue",_kneeboardPath,self._DisplayQueue,self,_unitName,"Waiting")
missionCommands.addCommandForGroup(gid,"Request Marshal",_rootPath,self._RequestMarshal,self,_unitName)
missionCommands.addCommandForGroup(gid,"Request Commence",_rootPath,self._RequestCommence,self,_unitName)
missionCommands.addCommandForGroup(gid,"Request Refueling",_rootPath,self._RequestRefueling,self,_unitName)
missionCommands.addCommandForGroup(gid,"Spinning",_rootPath,self._RequestSpinning,self,_unitName)
missionCommands.addCommandForGroup(gid,"Emergency Landing",_rootPath,self._RequestEmergency,self,_unitName)
missionCommands.addCommandForGroup(gid,"[Reset My Status]",_rootPath,self._ResetPlayerStatus,self,_unitName)
end
else
self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.",_unitName))
end
else
self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName))
end
end
function AIRBOSS:_SkipperStartRecovery(_unitName,case)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("affirm, Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.",case,self.skipperTime,self.skipperSpeed,tostring(self.skipperUturn))
if case>1 then
text=text..string.format(" Marshal radial %d°.",self.skipperOffset)
end
if self:IsRecovering()then
text="negative, carrier is already recovering."
self:MessageToPlayer(playerData,text,"AIRBOSS")
return
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
local t0=timer.getAbsTime()+5*60
local t9=t0+self.skipperTime*60
local C0=UTILS.SecondsToClock(t0)
local C9=UTILS.SecondsToClock(t9)
self:AddRecoveryWindow(C0,C9,case,self.skipperOffset,true,self.skipperSpeed,self.skipperUturn)
end
end
end
function AIRBOSS:_SkipperStopRecovery(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text="roger, stopping recovery right away."
if not self:IsRecovering()then
text="negative, carrier is currently not recovering."
self:MessageToPlayer(playerData,text,"AIRBOSS")
return
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
self:RecoveryStop()
end
end
end
function AIRBOSS:_SkipperRecoveryOffset(_unitName,offset)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.",offset)
self:MessageToPlayer(playerData,text,"AIRBOSS")
self.skipperOffset=offset
end
end
end
function AIRBOSS:_SkipperRecoveryTime(_unitName,time)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("roger, manual recovery time set to %d min.",time)
self:MessageToPlayer(playerData,text,"AIRBOSS")
self.skipperTime=time
end
end
end
function AIRBOSS:_SkipperRecoverySpeed(_unitName,speed)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("roger, wind on deck set to %d knots.",speed)
self:MessageToPlayer(playerData,text,"AIRBOSS")
self.skipperSpeed=speed
end
end
end
function AIRBOSS:_SkipperRecoveryUturn(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
self.skipperUturn=not self.skipperUturn
local text=string.format("roger, U-turn is now %s.",tostring(self.skipperUturn))
self:MessageToPlayer(playerData,text,"AIRBOSS")
end
end
end
function AIRBOSS:_ResetPlayerStatus(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text="roger, status reset executed! You have been removed from all queues."
self:MessageToPlayer(playerData,text,"AIRBOSS")
self:_RemoveFlight(playerData)
if playerData.debriefschedulerID and self.Scheduler then
self.Scheduler:Stop(playerData.debriefschedulerID)
end
self:_InitPlayer(playerData)
end
end
end
function AIRBOSS:_RequestMarshal(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
if self.xtVoiceOvers then
self:_MarshallInboundCall(_unit,playerData.onboard)
end
local inCCA=playerData.unit:IsInZone(self.zoneCCA)
if inCCA then
if self:_InQueue(self.Qmarshal,playerData.group)then
local text=string.format("negative, you are already in the Marshal queue. New marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif self:_InQueue(self.Qpattern,playerData.group)then
local text=string.format("negative, you are already in the Pattern queue. Marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif self:_InQueue(self.Qwaiting,playerData.group)then
local text=string.format("negative, you are in the Waiting queue with %d flights ahead of you. Marshal request denied!",#self.Qwaiting)
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif not _unit:InAir()then
local text=string.format("negative, you are not airborne. Marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
elseif playerData.name~=playerData.seclead then
local text=string.format("negative, your section lead %s needs to request Marshal.",playerData.seclead)
self:MessageToPlayer(playerData,text,"MARSHAL")
else
local freestack=self:_GetFreeStack(playerData.ai)
if freestack then
self:_MarshalPlayer(playerData,freestack)
else
self:_WaitPlayer(playerData)
end
end
else
local text=string.format("negative, you are not inside CCA. Marshal request denied!")
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
end
end
function AIRBOSS:_RequestEmergency(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=""
if not self.emergency then
text="negative, no emergency landings on my carrier. We are currently busy. See how you get along!"
elseif not _unit:InAir()then
local zone=self:_GetZoneCarrierBox()
if playerData.unit:IsInZone(zone)then
text="roger, you are now technically in the bolter pattern. Your next step after takeoff is abeam!"
local lead=self:_GetFlightLead(playerData)
self:_SetPlayerStep(lead,AIRBOSS.PatternStep.BOLTER)
for _,sec in pairs(lead.section)do
local sectionmember=sec
self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.BOLTER)
end
self:_RemoveFlightFromQueue(self.Qwaiting,lead)
if self:_InQueue(self.Qmarshal,lead.group)then
self:_RemoveFlightFromMarshalQueue(lead)
else
if not self:_InQueue(self.Qpattern,lead.group)then
self:_AddFlightToPatternQueue(lead)
end
end
else
text=string.format("negative, you are not airborne. Request denied!")
end
else
text="affirmative, you can bypass the pattern and are cleared for final approach!"
local lead=self:_GetFlightLead(playerData)
self:_SetPlayerStep(lead,AIRBOSS.PatternStep.EMERGENCY)
for _,sec in pairs(lead.section)do
local sectionmember=sec
self:_SetPlayerStep(sectionmember,AIRBOSS.PatternStep.EMERGENCY)
self:_RemoveFlightFromQueue(self.Qspinning,sectionmember)
end
self:_RemoveFlightFromQueue(self.Qwaiting,lead)
if self:_InQueue(self.Qmarshal,lead.group)then
self:_RemoveFlightFromMarshalQueue(lead)
else
if not self:_InQueue(self.Qpattern,lead.group)then
self:_AddFlightToPatternQueue(lead)
end
end
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
end
end
end
function AIRBOSS:_RequestSpinning(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=""
if not self:_InQueue(self.Qpattern,playerData.group)then
text="negative, you have to be in the pattern to spin it!"
elseif playerData.step==AIRBOSS.PatternStep.SPINNING then
text="negative, you are already spinning."
elseif not(playerData.step==AIRBOSS.PatternStep.BREAKENTRY or
playerData.step==AIRBOSS.PatternStep.EARLYBREAK or
playerData.step==AIRBOSS.PatternStep.LATEBREAK)then
text="negative, you have to be in the right step to spin it!"
else
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.SPINNING)
table.insert(self.Qspinning,playerData)
local call=self:_NewRadioCall(self.LSOCall.SPINIT,"AIRBOSS","Spin it!",self.Tmessage,playerData.onboard)
self:RadioTransmission(self.LSORadio,call,nil,nil,nil,true)
if playerData.difficulty==AIRBOSS.Difficulty.EASY then
local text="Climb to 1200 feet and proceed to the initial again."
self:MessageToPlayer(playerData,text,"AIRBOSS","")
end
return
end
self:MessageToPlayer(playerData,text,"AIRBOSS")
end
end
end
function AIRBOSS:_RequestCommence(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
if self.xtVoiceOvers then
self:_CommencingCall(_unit,playerData.onboard)
end
local text=""
local cleared=false
if _unit:IsInZone(self.zoneCCA)then
local stack=playerData.flag
local _,npattern=self:_GetQueueInfo(self.Qpattern)
if self:_InQueue(self.Qpattern,playerData.group)then
text=string.format("negative, %s, you are already in the Pattern queue.",playerData.name)
elseif not _unit:InAir()then
text=string.format("negative, %s, you are not airborne.",playerData.name)
elseif playerData.seclead~=playerData.name then
text=string.format("negative, %s, your section leader %s has to request commence!",playerData.name,playerData.seclead)
elseif stack>1 then
text=string.format("negative, %s, it's not your turn yet! You are in stack no. %s.",playerData.name,stack)
elseif npattern>=self.Nmaxpattern then
text=string.format("negative ghostrider, pattern is full!\nThere are %d aircraft currently in the pattern.",npattern)
elseif self:IsRecovering()==false and not self.airbossnice then
if self.recoverywindow then
local clock=UTILS.SecondsToClock(self.recoverywindow.START)
text=string.format("negative, carrier is currently not recovery. Next window will open at %s.",clock)
else
text=string.format("negative, carrier is not recovering. No future windows planned.")
end
elseif not self:_InQueue(self.Qmarshal,playerData.group)and not self.airbossnice then
text="negative, you have to request Marshal before you can commence."
else
text=text.."roger."
if not self:IsRecovering()then
text=text.." Carrier is not recovering currently! However, you are cleared anyway as I have a nice day."
end
if not self:_InQueue(self.Qmarshal,playerData.group)then
playerData.case=self.case
if self.TACANon and playerData.difficulty~=AIRBOSS.Difficulty.HARD then
local radial=self:GetRadial(playerData.case,true,true,true)
if playerData.case==1 then
radial=self:GetBRC()
end
text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n",radial,self.TACANchannel,self.TACANmode,self.TACANmorse)
end
for _,flight in pairs(playerData.section)do
flight.case=playerData.case
end
self:_AddFlightToPatternQueue(playerData)
end
cleared=true
end
else
text=string.format("negative, %s, you are not inside the CCA!",playerData.name)
end
self:T(self.lid..text)
self:MessageToPlayer(playerData,text,"MARSHAL")
if cleared then
self:_Commencing(playerData,false)
end
end
end
end
function AIRBOSS:_RequestRefueling(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text
if self.tanker then
if _unit:IsInZone(self.zoneCCA)then
if self.tanker:IsRunning()or self.tanker:IsRefueling()then
local angels=self:_GetAngels(self.tanker.altitude)
text=string.format("affirmative, proceed to tanker at angels %d.",angels)
if self.tanker.TACANon then
text=text..string.format("\nTanker TACAN channel %d%s (%s).",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse)
text=text..string.format("\nRadio frequency %.3f MHz AM.",self.tanker.RadioFreq)
end
if self.tanker:IsRefueling()then
text=text.."\nTanker is currently refueling. You might have to queue up."
end
self:_RemoveFlightFromMarshalQueue(playerData,true)
self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.REFUELING)
for _,sec in pairs(playerData.section)do
local sectext="follow your section leader to the tanker."
self:MessageToPlayer(sec,sectext,"MARSHAL")
self:_SetPlayerStep(sec,AIRBOSS.PatternStep.REFUELING)
end
elseif self.tanker:IsReturning()then
text="negative, tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can."
end
else
text="negative, you are not inside the CCA yet."
end
else
text="negative, no refueling tanker available."
end
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
end
function AIRBOSS:_RemoveSectionMember(playerData,sectionmember)
for i,_flight in pairs(playerData.section)do
local flight=_flight
if flight.name==sectionmember.name then
table.remove(playerData.section,i)
return true
end
end
return false
end
function AIRBOSS:_SetSection(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local mycoord=_unit:GetCoordinate()
local dmax=self.maxsectiondistance
local text
if self.NmaxSection==0 then
text=string.format("negative, setting sections is disabled in this mission. You stay alone.")
elseif self:_InQueue(self.Qmarshal,playerData.group)then
text=string.format("negative, you are already in the Marshal queue. Setting section not possible any more!")
elseif self:_InQueue(self.Qpattern,playerData.group)then
text=string.format("negative, you are already in the Pattern queue. Setting section not possible any more!")
else
if playerData.seclead~=playerData.name then
local lead=self.players[playerData.seclead]
if lead then
local removed=self:_RemoveSectionMember(lead,playerData)
if removed then
self:MessageToPlayer(lead,string.format("Flight %s has been removed from your section.",playerData.name),"AIRBOSS","",5)
self:MessageToPlayer(playerData,string.format("You have been removed from %s's section.",lead.name),"AIRBOSS","",5)
end
end
end
local section={}
for _,_flight in pairs(self.flights)do
local flight=_flight
if flight.ai==false and flight.groupname~=playerData.groupname and#flight.section==0 and flight.seclead==flight.name then
local distance=flight.group:GetCoordinate():Get3DDistance(mycoord)
if distance<dmax then
self:T(self.lid..string.format("Found potential section member %s for lead %s at distance %.1f m.",flight.name,playerData.name,distance))
table.insert(section,{flight=flight,distance=distance})
end
end
end
table.sort(section,function(a,b)
return a.distance<b.distance
end)
playerData.seclead=playerData.name
for _,_flight in pairs(playerData.section)do
local flight=_flight
local gotit=false
for _,_new in pairs(section)do
local newflight=_new.flight
if newflight.name==flight.name then
gotit=true
end
end
if not gotit then
self:MessageToPlayer(flight,string.format("you were removed from %s's section and are on your own now.",playerData.name),"AIRBOSS","",5)
flight.seclead=flight.name
self:_RemoveSectionMember(playerData,flight)
end
end
for i,_new in pairs(section)do
local newflight=_new.flight
for _,_flight in pairs(playerData.section)do
local currentflight=_flight
if newflight.name==currentflight.name then
table.remove(section,i)
end
end
end
text=string.format("Registered flight section:")
text=text..string.format("\n- %s (lead)",playerData.seclead)
for _,_flight in pairs(playerData.section)do
local flight=_flight
text=text..string.format("\n- %s",flight.name)
end
for i=1,math.min(self.NmaxSection-#playerData.section,#section)do
local flight=section[i].flight
text=text..string.format("\n- %s",flight.name)
flight.seclead=playerData.name
flight.case=playerData.case
self:MessageToPlayer(flight,string.format("your section lead is now %s.",playerData.name),"AIRBOSS")
table.insert(playerData.section,flight)
end
if#playerData.section==0 then
text=text..string.format("\n- No other human flights found within radius of %.1f meters!",dmax)
end
end
self:MessageToPlayer(playerData,text,"MARSHAL")
end
end
end
function AIRBOSS:_DisplayScoreBoard(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local _playerResults={}
for playerName,playerGrades in pairs(self.playerscores)do
if playerGrades then
local Paverage=0
local n=0
for _,_grade in pairs(playerGrades)do
local grade=_grade
if grade.finalscore then
Paverage=Paverage+grade.finalscore
n=n+1
else
end
end
if n>0 then
_playerResults[playerName]=Paverage/n
end
end
end
local text=string.format("Greenie Board (top ten):")
local i=1
for _playerName,_points in UTILS.spairs(_playerResults,function(t,a,b)
return t[b]<t[a]
end)do
text=text..string.format("\n[%d] %s %.1f||",i,_playerName,_points)
local playerGrades=self.playerscores[_playerName]
for _,_grade in pairs(playerGrades)do
local grade=_grade
if grade.finalscore then
text=text..string.format("%.1f|",grade.points)
elseif grade.points>=0 then
text=text..string.format("(%.1f)",grade.points)
end
end
i=i+1
if i>10 then
break
end
end
if i==1 then
text=text.."\nNo results yet."
end
local playerData=self.players[_playername]
if playerData.client then
MESSAGE:New(text,30,nil,true):ToClient(playerData.client)
end
end
end
function AIRBOSS:_DisplayPlayerGrades(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("Your last 10 grades, %s:",_playername)
local playerGrades=self.playerscores[_playername]or{}
local p=0
local n=0
local m=0
for i=#playerGrades,1,-1 do
local grade=playerGrades[i]
if grade.points>=0 then
local points=grade.finalscore or grade.points
if m<10 then
text=text..string.format("\n[%d] %s %.1f PT - %s",i,grade.grade,points,grade.details)
if grade.wire and grade.wire<=4 then
text=text..string.format(" %d-wire",grade.wire)
end
if grade.Tgroove and grade.Tgroove<=360 then
text=text..string.format(" Tgroove=%.1f s",grade.Tgroove)
end
end
if grade.finalscore then
p=p+grade.finalscore
n=n+1
end
m=m+1
end
end
if n>0 then
text=text..string.format("\nAverage points = %.1f",p/n)
else
text=text..string.format("\nNo data available.")
end
if playerData.client then
MESSAGE:New(text,30,nil,true):ToClient(playerData.client)
end
end
end
end
function AIRBOSS:_DisplayDebriefing(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local text=string.format("Debriefing:")
if#playerData.lastdebrief>0 then
text=text..string.format("\n================================\n")
for _,_data in pairs(playerData.lastdebrief)do
local step=_data.step
local comment=_data.hint
text=text..string.format("* %s:",step)
text=text..string.format("%s\n",comment)
end
else
text=text.." Nothing to show yet."
end
self:MessageToPlayer(playerData,text,nil,"",30,true)
end
end
end
function AIRBOSS:_DisplayQueue(_unitname,qname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local queue=nil
if qname=="Marshal"then
queue=self.Qmarshal
elseif qname=="Pattern"then
queue=self.Qpattern
elseif qname=="Waiting"then
queue=self.Qwaiting
end
local Nqueue,nqueue=self:_GetQueueInfo(queue,playerData.case)
local text=string.format("%s Queue:",qname)
if#queue==0 then
text=text.." empty"
else
local N=0
if qname=="Marshal"then
for i,_flight in pairs(queue)do
local flight=_flight
local charlie=self:_GetCharlieTime(flight)
local Charlie=UTILS.SecondsToClock(charlie)
local stack=flight.flag
local angels=self:_GetAngels(self:_GetMarshalAltitude(stack,flight.case))
local _,nunit,nsec=self:_GetFlightUnits(flight,true)
local nick=self:_GetACNickname(flight.actype)
N=N+nunit
text=text..string.format("\n[Stack %d] %s (%s*%d+%d): Case %d, Angels %d, Charlie %s",stack,flight.onboard,nick,nunit,nsec,flight.case,angels,tostring(Charlie))
end
elseif qname=="Pattern"or qname=="Waiting"then
for i,_flight in pairs(queue)do
local flight=_flight
local _,nunit,nsec=self:_GetFlightUnits(flight,true)
local nick=self:_GetACNickname(flight.actype)
local ptime=UTILS.SecondsToClock(timer.getAbsTime()-flight.time)
N=N+nunit
text=text..string.format("\n[%d] %s (%s*%d+%d): Case %d, T=%s",i,flight.onboard,nick,nunit,nsec,flight.case,ptime)
end
end
text=text..string.format("\nTotal AC: %d (airborne %d)",N,nqueue)
end
self:MessageToPlayer(playerData,text,nil,"",nil,true)
end
end
end
function AIRBOSS:_DisplayCarrierInfo(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local coord=self:GetCoordinate()
local carrierheading=self.carrier:GetHeading()
local carrierspeed=UTILS.MpsToKnots(self.carrier:GetVelocityMPS())
local tacan="unknown"
local icls="unknown"
if self.TACANon and self.TACANchannel~=nil then
tacan=string.format("%d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse)
end
if self.ICLSon and self.ICLSchannel~=nil then
icls=string.format("%d (%s)",self.ICLSchannel,self.ICLSmorse)
end
local wind=UTILS.MpsToKnots(select(1,self:GetWindOnDeck()))
local Nmarshal,nmarshal=self:_GetQueueInfo(self.Qmarshal,playerData.case)
local Npattern,npattern=self:_GetQueueInfo(self.Qpattern)
local Nspinning,nspinning=self:_GetQueueInfo(self.Qspinning)
local Nwaiting,nwaiting=self:_GetQueueInfo(self.Qwaiting)
local Ntotal,ntotal=self:_GetQueueInfo(self.flights)
local Tabs=timer.getAbsTime()
local recoverytext="Recovery time windows (max 5):"
if#self.recoverytimes==0 then
recoverytext=recoverytext.." none."
else
local rw=0
for _,_recovery in pairs(self.recoverytimes)do
local recovery=_recovery
if Tabs<recovery.STOP then
recoverytext=recoverytext..string.format("\n* %s - %s: Case %d (%d°)",UTILS.SecondsToClock(recovery.START),UTILS.SecondsToClock(recovery.STOP),recovery.CASE,recovery.OFFSET)
if recovery.WIND then
recoverytext=recoverytext..string.format(" @ %.1f kts wind",recovery.SPEED)
end
rw=rw+1
if rw>=5 then
break
end
end
end
end
local tankertext=nil
if self.tanker then
tankertext=string.format("Recovery tanker frequency %.3f MHz\n",self.tanker.RadioFreq)
if self.tanker.TACANon then
tankertext=tankertext..string.format("Recovery tanker TACAN %d%s (%s)",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse)
else
tankertext=tankertext.."Recovery tanker TACAN n/a"
end
end
local state=self:GetState()
if state=="Idle"then
state="Deck closed"
end
if self.turning then
state=state.." (currently turning)"
end
local text=string.format("%s info:\n",self.alias)
text=text..string.format("================================\n")
text=text..string.format("Carrier state: %s\n",state)
if self.case==1 then
text=text..string.format("Case %d recovery ops\n",self.case)
else
local radial=self:GetRadial(self.case,true,true,false)
text=text..string.format("Case %d recovery ops\nMarshal radial %03d°\n",self.case,radial)
end
text=text..string.format("BRC %03d° - FB %03d°\n",self:GetBRC(),self:GetFinalBearing(true))
text=text..string.format("Speed %.1f kts - Wind on deck %.1f kts\n",carrierspeed,wind)
text=text..string.format("Tower frequency %.3f MHz\n",self.TowerFreq)
text=text..string.format("Marshal radio %.3f MHz\n",self.MarshalFreq)
text=text..string.format("LSO radio %.3f MHz\n",self.LSOFreq)
text=text..string.format("TACAN Channel %s\n",tacan)
text=text..string.format("ICLS Channel %s\n",icls)
if tankertext then
text=text..tankertext.."\n"
end
text=text..string.format("# A/C total %d (%d)\n",Ntotal,ntotal)
text=text..string.format("# A/C marshal %d (%d)\n",Nmarshal,nmarshal)
text=text..string.format("# A/C pattern %d (%d) - spinning %d (%d)\n",Npattern,npattern,Nspinning,nspinning)
text=text..string.format("# A/C waiting %d (%d)\n",Nwaiting,nwaiting)
text=text..string.format(recoverytext)
self:T2(self.lid..text)
self:MessageToPlayer(playerData,text,nil,"",30,true)
else
self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername))
end
end
end
function AIRBOSS:_DisplayCarrierWeather(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local text=""
local coord=self:GetCoordinate()
local T=coord:GetTemperature()
local P=coord:GetPressure()
local Wd,Ws=self:GetWind(nil,true)
local Bn,Bd=UTILS.BeaufortScale(Ws)
local WodPA,WodPP=self:GetWindOnDeck()
local WodPA=UTILS.MpsToKnots(WodPA)
local WodPP=UTILS.MpsToKnots(WodPP)
local WD=string.format('%03d°',Wd)
local Ts=string.format("%d°C",T)
local tT=string.format("%d°C",T)
local tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws))
local tP=string.format("%.2f inHg",UTILS.hPa2inHg(P))
text=text..string.format("Weather Report at Carrier %s:\n",self.alias)
text=text..string.format("================================\n")
text=text..string.format("Temperature %s\n",tT)
text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd)
text=text..string.format("Wind on deck || %.1f kts, == %.1f kts\n",WodPA,WodPP)
text=text..string.format("QFE %.1f hPa = %s",P,tP)
if self.staticweather then
local clouds,visibility,fog,dust=self:_GetStaticWeather()
text=text..string.format("\nVisibility %.1f NM",UTILS.MetersToNM(visibility))
text=text..string.format("\nCloud base %d ft",UTILS.MetersToFeet(clouds.base))
text=text..string.format("\nCloud thickness %d ft",UTILS.MetersToFeet(clouds.thickness))
text=text..string.format("\nCloud density %d",clouds.density)
text=text..string.format("\nPrecipitation %d",clouds.iprecptns)
if fog then
text=text..string.format("\nFog thickness %d ft",UTILS.MetersToFeet(fog.thickness))
text=text..string.format("\nFog visibility %d ft",UTILS.MetersToFeet(fog.visibility))
else
text=text..string.format("\nNo fog")
end
if dust then
text=text..string.format("\nDust density %d",dust)
else
text=text..string.format("\nNo dust")
end
end
self:T2(self.lid..text)
self:MessageToPlayer(self.players[playername],text,nil,"",30,true)
else
self:E(self.lid..string.format("ERROR! Could not find player unit in CarrierWeather! Unit name = %s",_unitname))
end
end
function AIRBOSS:_SetDifficulty(_unitname,difficulty)
self:T2({difficulty=difficulty,unitname=_unitname})
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.difficulty=difficulty
local text=string.format("roger, your skill level is now: %s.",difficulty)
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
else
self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername))
end
if playerData.difficulty==AIRBOSS.Difficulty.HARD then
playerData.showhints=false
else
playerData.showhints=true
end
end
end
function AIRBOSS:_SetHintsOnOff(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.showhints=not playerData.showhints
local text=""
if playerData.showhints==true then
text=string.format("roger, hints are now ON.")
else
text=string.format("affirm, hints are now OFF.")
end
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
end
end
end
function AIRBOSS:_DisplayAttitude(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.attitudemonitor=not playerData.attitudemonitor
end
end
end
function AIRBOSS:_SubtitlesOnOff(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
playerData.subtitles=not playerData.subtitles
local text=""
if playerData.subtitles==true then
text=string.format("roger, subtitiles are now ON.")
elseif playerData.subtitles==false then
text=string.format("affirm, subtitiles are now OFF.")
end
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
end
end
end
function AIRBOSS:_TrapsheetOnOff(_unitname)
self:F2(_unitname)
local unit,playername=self:_GetPlayerUnitAndName(_unitname)
if unit and playername then
local playerData=self.players[playername]
if playerData then
local text=""
if self.trapsheet then
playerData.trapon=not playerData.trapon
if playerData.trapon==true then
text=string.format("roger, your trapsheets are now SAVED.")
else
text=string.format("affirm, your trapsheets are NOT SAVED.")
end
else
text="negative, trap sheet data recorder is broken on this carrier."
end
self:MessageToPlayer(playerData,text,nil,playerData.name,5)
end
end
end
function AIRBOSS:_DisplayPlayerStatus(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local steptext=playerData.step
if playerData.step==AIRBOSS.PatternStep.HOLDING then
if playerData.holding==nil then
steptext="Transit to Marshal"
elseif playerData.holding==false then
steptext="Marshal (outside zone)"
elseif playerData.holding==true then
steptext="Marshal Stack Holding"
end
end
local stack=playerData.flag
local stacktext=nil
if stack>0 then
local stackalt=self:_GetMarshalAltitude(stack)
local angels=self:_GetAngels(stackalt)
stacktext=string.format("Marshal Stack %d, Angels %d\n",stack,angels)
if playerData.step==AIRBOSS.PatternStep.HOLDING and playerData.case>1 then
local radial=self:GetRadial(playerData.case,true,true,true)
stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n",radial,angels+15)
end
end
local fuel=playerData.unit:GetFuel()*100
local fuelstate=self:_GetFuelState(playerData.unit)
local _,nunitsGround=self:_GetFlightUnits(playerData,true)
local _,nunitsAirborne=self:_GetFlightUnits(playerData,false)
local text=string.format("Status of player %s (%s)\n",playerData.name,playerData.callsign)
text=text..string.format("================================\n")
text=text..string.format("Step: %s\n",steptext)
if stacktext then
text=text..stacktext
end
text=text..string.format("Recovery Case: %d\n",playerData.case)
text=text..string.format("Skill Level: %s\n",playerData.difficulty)
text=text..string.format("Modex: %s (%s)\n",playerData.onboard,self:_GetACNickname(playerData.actype))
text=text..string.format("Fuel State: %.1f lbs/1000 (%.1f %%)\n",fuelstate/1000,fuel)
text=text..string.format("# units: %d (%d airborne)\n",nunitsGround,nunitsAirborne)
text=text..string.format("Section Lead: %s (%d/%d)",tostring(playerData.seclead),#playerData.section+1,self.NmaxSection+1)
for _,_sec in pairs(playerData.section)do
local sec=_sec
text=text..string.format("\n- %s",sec.name)
end
if playerData.step==AIRBOSS.PatternStep.INITIAL then
local zoneinitial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false))
local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneinitial)
local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneinitial))
local brc=self:GetBRC()
text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°",flyhdg,flydist,brc)
elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then
local zoneplatform=self:_GetZonePlatform(playerData.case):GetCoordinate()
local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneplatform)
local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneplatform))
local hdg=self:GetRadial(playerData.case,true,true,true)
text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°",flyhdg,flydist,hdg)
end
self:MessageToPlayer(playerData,text,nil,"",30,true)
else
self:E(self.lid..string.format("ERROR: playerData=nil. Unit name=%s, player name=%s",_unitName,_playername))
end
else
self:E(self.lid..string.format("ERROR: could not find player for unit %s",_unitName))
end
end
function AIRBOSS:_MarkMarshalZone(_unitName,flare)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local stack=playerData.flag
local case=playerData.case
local text=""
if stack>0 then
local zoneHolding=self:_GetZoneHolding(case,stack)
local zoneThree=self:_GetZoneCommence(case,stack)
local patternalt=self:_GetMarshalAltitude(stack,case)
patternalt=5
text="roger, marking"
if flare then
text=text..string.format("\n* Marshal zone stack %d with WHITE flares.",stack)
zoneHolding:FlareZone(FLARECOLOR.White,45,nil,patternalt)
text=text.."\n* Commence zone with RED flares."
zoneThree:FlareZone(FLARECOLOR.Red,45,nil,patternalt)
else
text=text..string.format("\n* Marshal zone stack %d with WHITE smoke.",stack)
zoneHolding:SmokeZone(SMOKECOLOR.White,45,patternalt)
text=text.."\n* Commence zone with RED smoke."
zoneThree:SmokeZone(SMOKECOLOR.Red,45,patternalt)
end
else
text="negative, you are currently not in a Marshal stack. No zones will be marked!"
end
self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name)
end
end
end
function AIRBOSS:_MarkCaseZones(_unitName,flare)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
local case=playerData.case
local text=string.format("affirm, marking CASE %d zones",case)
if flare then
if case==1 or case==2 then
text=text.."\n* initial with GREEN flares"
self:_GetZoneInitial(case):FlareZone(FLARECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* approach corridor with GREEN flares"
self:_GetZoneCorridor(case):FlareZone(FLARECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* platform with RED flares"
self:_GetZonePlatform(case):FlareZone(FLARECOLOR.Red,45)
end
if case==3 then
text=text.."\n* dirty up with YELLOW flares"
self:_GetZoneDirtyUp(case):FlareZone(FLARECOLOR.Yellow,45)
end
if case==2 or case==3 then
if math.abs(self.holdingoffset)>0 then
self:_GetZoneArcIn(case):FlareZone(FLARECOLOR.White,45)
text=text.."\n* arc turn in with WHITE flares"
self:_GetZoneArcOut(case):FlareZone(FLARECOLOR.White,45)
text=text.."\n* arc turn out with WHITE flares"
end
end
if case==3 then
text=text.."\n* bullseye with GREEN flares"
self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green,45)
end
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
text=text.."\n* abeam landing stop with RED flares"
local ALSPT=self:_GetZoneAbeamLandingSpot()
ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(110))
text=text.."\n* primary landing spot with GREEN flares"
local LSPT=self:_GetZoneLandingSpot()
LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight)
end
else
if case==1 or case==2 then
text=text.."\n* initial with GREEN smoke"
self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* approach corridor with GREEN smoke"
self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45)
end
if case==2 or case==3 then
text=text.."\n* platform with RED smoke"
self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red,45)
end
if case==2 or case==3 then
if math.abs(self.holdingoffset)>0 then
self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45)
text=text.."\n* arc turn in with BLUE smoke"
self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45)
text=text.."\n* arc turn out with BLUE smoke"
end
end
if case==3 then
text=text.."\n* dirty up with ORANGE smoke"
self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45)
end
if case==3 then
text=text.."\n* bullseye with GREEN smoke"
self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.Green,45)
end
end
self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name)
end
end
end
function AIRBOSS:_LSORadioCheck(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
self:RadioTransmission(self.LSORadio,self.LSOCall.RADIOCHECK,nil,nil,nil,true)
end
end
end
function AIRBOSS:_MarshalRadioCheck(_unitName)
self:F(_unitName)
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
if _unit and _playername then
local playerData=self.players[_playername]
if playerData then
self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RADIOCHECK,nil,nil,nil,true)
end
end
end
function AIRBOSS:_SaveTrapSheet(playerData,grade)
if playerData.trapsheet==nil or#playerData.trapsheet==0 or not io then
return
end
local function _savefile(filename,data)
local f=io.open(filename,"wb")
if f then
f:write(data)
f:close()
else
self:E(self.lid..string.format("ERROR: could not save trap sheet to file %s.\nFile may contain invalid characters.",tostring(filename)))
end
end
local path=self.trappath
if lfs then
path=path or lfs.writedir()
end
local filename=nil
for i=1,9999 do
if self.trapprefix then
filename=string.format("%s_%s-%04d.csv",self.trapprefix,playerData.actype,i)
else
local name=UTILS.ReplaceIllegalCharacters(playerData.name,"_")
filename=string.format("AIRBOSS-%s_Trapsheet-%s_%s-%04d.csv",self.alias,name,playerData.actype,i)
end
if path~=nil then
filename=path.."\\"..filename
end
local _exists=UTILS.FileExists(filename)
if not _exists then
break
end
end
local text=string.format("Saving player %s trapsheet to file %s",playerData.name,filename)
self:I(self.lid..text)
local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step,Grade,Points,Details\n"
local g0=playerData.trapsheet[1]
local T0=g0.Time
for i=1,#playerData.trapsheet do
local groove=playerData.trapsheet[i]
local t=groove.Time-T0
local a=UTILS.MetersToNM(groove.Rho or 0)
local b=-groove.X or 0
local c=groove.Z or 0
local d=UTILS.MetersToFeet(groove.Alt or 0)
local e=groove.AoA or 0
local f=groove.GSE or 0
local g=-groove.LUE or 0
local h=UTILS.MpsToKnots(groove.Vel or 0)
local i=(groove.Vy or 0)*196.85
local j=groove.Gamma or 0
local k=groove.Pitch or 0
local l=groove.Roll or 0
local m=groove.Yaw or 0
local n=self:_GS(groove.Step,-1)or"n/a"
local o=groove.Grade or"n/a"
local p=groove.GradePoints or 0
local q=groove.GradeDetail or"n/a"
data=data..string.format("%.2f,%.3f,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s,%s,%.1f,%s\n",t,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q)
end
_savefile(filename,data)
end
function AIRBOSS:onbeforeSave(From,Event,To,path,filename)
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
function AIRBOSS:onafterSave(From,Event,To,path,filename)
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
if lfs then
path=path or lfs.writedir()
end
filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n"
local n=0
for playername,grades in pairs(self.playerscores)do
for i,_grade in pairs(grades)do
local grade=_grade
local wire="n/a"
if grade.wire and grade.wire<=4 then
wire=tostring(grade.wire)
end
local Tgroove="n/a"
if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then
Tgroove=tostring(UTILS.Round(grade.Tgroove,1))
end
local finalscore="n/a"
if grade.finalscore then
finalscore=tostring(UTILS.Round(grade.finalscore,1))
end
scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",playername,i,finalscore,grade.points,grade.grade,grade.details,wire,Tgroove,grade.case,grade.wind,grade.modex,grade.airframe,grade.carriertype,grade.carriername,grade.theatre,grade.mitime,grade.midate,grade.osdate)
n=n+1
end
end
local text=string.format("Saving %d player LSO grades to file %s",n,filename)
self:I(self.lid..text)
_savefile(filename,scores)
end
function AIRBOSS:onbeforeLoad(From,Event,To,path,filename)
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
if not io then
self:E(self.lid.."WARNING: io not desanitized. Can't load player grades.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
if lfs then
path=path or lfs.writedir()
end
filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: Player LSO grades file %s does not exist.",filename))
return false
end
end
function AIRBOSS:onafterLoad(From,Event,To,path,filename)
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
if lfs then
path=path or lfs.writedir()
end
filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias)
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading player LSO grades from file %s",filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local data=_loadfile(filename)
local playergrades=UTILS.Split(data,"\n")
table.remove(playergrades,1)
self.playerscores={}
local n=0
for _,gradeline in pairs(playergrades)do
local gradedata=UTILS.Split(gradeline,",")
self:T2(gradedata)
local grade={}
local playername=gradedata[1]
if gradedata[3]~=nil and gradedata[3]~="n/a"then
grade.finalscore=tonumber(gradedata[3])
end
grade.points=tonumber(gradedata[4])
grade.grade=tostring(gradedata[5])
grade.details=tostring(gradedata[6])
if gradedata[7]~=nil and gradedata[7]~="n/a"then
grade.wire=tonumber(gradedata[7])
end
if gradedata[8]~=nil and gradedata[8]~="n/a"then
grade.Tgroove=tonumber(gradedata[8])
end
grade.case=tonumber(gradedata[9])
grade.wind=gradedata[10]or"n/a"
grade.modex=gradedata[11]or"n/a"
grade.airframe=gradedata[12]or"n/a"
grade.carriertype=gradedata[13]or"n/a"
grade.carriername=gradedata[14]or"n/a"
grade.theatre=gradedata[15]or"n/a"
grade.mitime=gradedata[16]or"n/a"
grade.midate=gradedata[17]or"n/a"
grade.osdate=gradedata[18]or"n/a"
self.playerscores[playername]=self.playerscores[playername]or{}
table.insert(self.playerscores[playername],grade)
n=n+1
self:T2({playername,self.playerscores[playername]})
end
local text=string.format("Loaded %d player LSO grades from file %s",n,filename)
self:I(self.lid..text)
end
function AIRBOSS:onafterLSOGrade(From,Event,To,playerData,grade)
if self.funkmanSocket then
local trapsheet={};trapsheet.X={};trapsheet.Z={};trapsheet.AoA={};trapsheet.Alt={}
for i=1,#playerData.trapsheet do
local ts=playerData.trapsheet[i]
table.insert(trapsheet.X,UTILS.Round(ts.X,1))
table.insert(trapsheet.Z,UTILS.Round(ts.Z,1))
table.insert(trapsheet.AoA,UTILS.Round(ts.AoA,2))
table.insert(trapsheet.Alt,UTILS.Round(ts.Alt,1))
end
local result={}
result.command=SOCKET.DataType.LSOGRADE
result.name=playerData.name
result.trapsheet=trapsheet
result.airframe=grade.airframe
result.mitime=grade.mitime
result.midate=grade.midate
result.wind=grade.wind
result.carriertype=grade.carriertype
result.carriername=grade.carriername
result.carrierrwy=grade.carrierrwy
result.landingdist=self.carrierparam.landingdist
result.theatre=grade.theatre
result.case=playerData.case
result.Tgroove=grade.Tgroove
result.wire=grade.wire
result.grade=grade.grade
result.points=grade.points
result.details=grade.details
self:T(self.lid.."Result onafterLSOGrade")
self:T(result)
self.funkmanSocket:SendTable(result)
end
end
RECOVERYTANKER={
ClassName="RECOVERYTANKER",
Debug=false,
lid=nil,
carrier=nil,
carriertype=nil,
tankergroupname=nil,
tanker=nil,
airbase=nil,
beacon=nil,
TACANchannel=nil,
TACANmode=nil,
TACANmorse=nil,
TACANon=nil,
RadioFreq=nil,
RadioModu=nil,
altitude=nil,
speed=nil,
distStern=nil,
distBow=nil,
dTupdate=nil,
Dupdate=nil,
Hupdate=nil,
Tupdate=nil,
takeoff=nil,
lowfuel=nil,
respawn=nil,
respawninair=nil,
uncontrolledac=nil,
orientation=nil,
orientlast=nil,
position=nil,
alias=nil,
uid=0,
awacs=nil,
callsignname=nil,
callsignnumber=nil,
modex=nil,
eplrs=nil,
recovery=nil,
terminaltype=nil,
unlimitedfuel=false,
}
_RECOVERYTANKERID=0
RECOVERYTANKER.version="1.0.10"
function RECOVERYTANKER:New(carrierunit,tankergroupname)
local self=BASE:Inherit(self,FSM:New())
if type(carrierunit)=="string"then
self.carrier=UNIT:FindByName(carrierunit)
else
self.carrier=carrierunit
end
self.carriertype=self.carrier:GetTypeName()
self.tankergroupname=tankergroupname
_RECOVERYTANKERID=_RECOVERYTANKERID+1
self.uid=_RECOVERYTANKERID
self.carrier:SetState(self.carrier,string.format("RECOVERYTANKER_%d",self.uid),self)
self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.tankergroupname,_RECOVERYTANKERID)
self.lid=string.format("RECOVERYTANKER %s | ",self.alias)
self:SetAltitude()
self:SetSpeed()
self:SetRacetrackDistances()
self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName()))
self:SetTakeoffHot()
self:SetLowFuelThreshold()
self:SetRespawnOnOff()
self:SetTACAN()
self:SetRadio()
self:SetPatternUpdateDistance()
self:SetPatternUpdateHeading()
self:SetPatternUpdateInterval()
self:SetAWACS(false)
self:SetRecoveryAirboss(false)
self.terminaltype=AIRBASE.TerminalType.OpenMedOrBig
if false then
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","RefuelStart","Refueling")
self:AddTransition("*","RefuelStop","Running")
self:AddTransition("*","Run","Running")
self:AddTransition("Running","RTB","Returning")
self:AddTransition("Returning","Returned","Returned")
self:AddTransition("*","Status","*")
self:AddTransition("Running","PatternUpdate","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function RECOVERYTANKER:SetUnlimitedFuel(OnOff)
self.unlimitedfuel=OnOff
return self
end
function RECOVERYTANKER:SetSpeed(speed)
self.speed=UTILS.KnotsToMps(speed or 274)
return self
end
function RECOVERYTANKER:SetAltitude(altitude)
self.altitude=UTILS.FeetToMeters(altitude or 6000)
return self
end
function RECOVERYTANKER:SetRacetrackDistances(distbow,diststern)
self.distBow=UTILS.NMToMeters(distbow or 10)
self.distStern=-UTILS.NMToMeters(diststern or 4)
return self
end
function RECOVERYTANKER:SetPatternUpdateInterval(interval)
self.dTupdate=(interval or 10)*60
return self
end
function RECOVERYTANKER:SetPatternUpdateDistance(distancechange)
self.Dupdate=UTILS.NMToMeters(distancechange or 5)
return self
end
function RECOVERYTANKER:SetPatternUpdateHeading(headingchange)
self.Hupdate=headingchange or 5
return self
end
function RECOVERYTANKER:SetLowFuelThreshold(fuelthreshold)
self.lowfuel=fuelthreshold or 10
return self
end
function RECOVERYTANKER:SetHomeBase(airbase,terminaltype)
if type(airbase)=="string"then
self.airbase=AIRBASE:FindByName(airbase)
else
self.airbase=airbase
end
if not self.airbase then
self:E(self.lid.."ERROR: Airbase is nil!")
end
if terminaltype then
self.terminaltype=terminaltype
end
return self
end
function RECOVERYTANKER:SetRecoveryAirboss(switch)
if switch==true or switch==nil then
self.recovery=true
else
self.recovery=false
end
return self
end
function RECOVERYTANKER:SetAWACS(switch,eplrs)
if switch==nil or switch==true then
self.awacs=true
else
self.awacs=false
end
if eplrs==nil or eplrs==true then
self.eplrs=true
else
self.eplrs=false
end
return self
end
function RECOVERYTANKER:SetCallsign(callsignname,callsignnumber)
self.callsignname=callsignname
self.callsignnumber=callsignnumber
return self
end
function RECOVERYTANKER:SetModex(modex)
self.modex=modex
return self
end
function RECOVERYTANKER:SetTakeoff(takeofftype)
self.takeoff=takeofftype
return self
end
function RECOVERYTANKER:SetTakeoffHot()
self:SetTakeoff(SPAWN.Takeoff.Hot)
return self
end
function RECOVERYTANKER:SetTakeoffCold()
self:SetTakeoff(SPAWN.Takeoff.Cold)
return self
end
function RECOVERYTANKER:SetTakeoffAir()
self:SetTakeoff(SPAWN.Takeoff.Air)
return self
end
function RECOVERYTANKER:SetRespawnOn()
self.respawn=true
return self
end
function RECOVERYTANKER:SetRespawnOff()
self.respawn=false
return self
end
function RECOVERYTANKER:SetRespawnOnOff(switch)
if switch==nil or switch==true then
self.respawn=true
else
self.respawn=false
end
return self
end
function RECOVERYTANKER:SetRespawnInAir()
self.respawninair=true
return self
end
function RECOVERYTANKER:SetUseUncontrolledAircraft()
self.uncontrolledac=true
return self
end
function RECOVERYTANKER:SetTACANoff()
self.TACANon=false
return self
end
function RECOVERYTANKER:SetTACAN(channel,morse,mode)
self.TACANchannel=channel or 1
self.TACANmode=mode or"Y"
self.TACANmorse=morse or"TKR"
self.TACANon=true
return self
end
function RECOVERYTANKER:SetRadio(frequency,modulation)
self.RadioFreq=frequency or 251
self.RadioModu=modulation or"AM"
return self
end
function RECOVERYTANKER:SetDebugModeON()
self.Debug=true
return self
end
function RECOVERYTANKER:SetDebugModeOFF()
self.Debug=false
return self
end
function RECOVERYTANKER:IsReturning()
return self:is("Returning")
end
function RECOVERYTANKER:IsReturned()
return self:is("Returned")
end
function RECOVERYTANKER:IsRunning()
return self:is("Running")
end
function RECOVERYTANKER:IsRefueling()
return self:is("Refueling")
end
function RECOVERYTANKER:IsStopped()
return self:is("Stopped")
end
function RECOVERYTANKER:GetAlias()
return self.alias
end
function RECOVERYTANKER:GetUnitName()
local unit=self.tanker:GetUnit(1)
if unit then
return unit:GetName()
end
return nil
end
function RECOVERYTANKER:onafterStart(From,Event,To)
self:I(string.format("Starting Recovery Tanker v%s for carrier unit %s of type %s for tanker group %s.",RECOVERYTANKER.version,self.carrier:GetName(),self.carriertype,self.tankergroupname))
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Refueling,self._RefuelingStart)
self:HandleEvent(EVENTS.RefuelingStop,self._RefuelingStop)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,self._OnEventCrashOrDead)
local Spawn=SPAWN:NewWithAlias(self.tankergroupname,self.alias)
if self.unlimitedfuel then
Spawn:OnSpawnGroup(
function(grp)
grp:CommandSetUnlimitedFuel(self.unlimitedfuel)
end
)
end
Spawn:InitRadioCommsOnOff(true)
Spawn:InitRadioFrequency(self.RadioFreq)
Spawn:InitRadioModulation(self.RadioModu)
Spawn:InitModex(self.modex)
if self.takeoff==SPAWN.Takeoff.Air then
local hdg=self.carrier:GetHeading()
local dist=-self.distStern+UTILS.NMToMeters(4)
local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg+190):SetAltitude(self.altitude)
Spawn:InitHeading(hdg+10)
self.tanker=Spawn:SpawnFromCoordinate(Carrier)
else
if self.uncontrolledac then
self.tanker=GROUP:FindByName(self.tankergroupname)
if self.tanker:IsAlive()then
self.tanker:StartUncontrolled()
else
self:E(string.format("ERROR: No uncontrolled (alive) tanker group with name %s could be found!",self.tankergroupname))
return
end
else
self.tanker=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,self.terminaltype)
end
end
self:ScheduleOnce(1,self._InitRoute,self,-self.distStern+UTILS.NMToMeters(3))
if self.TACANon then
self:_ActivateTACAN(2)
end
if self.callsignname then
self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,2)
end
if self.eplrs then
self.tanker:CommandEPLRS(true,3)
end
self.orientation=self.carrier:GetOrientationX()
self.orientlast=self.carrier:GetOrientationX()
self.position=self.carrier:GetCoordinate()
self:__Status(10)
return self
end
function RECOVERYTANKER:onafterStatus(From,Event,To)
local time=timer.getTime()
if self.tanker and self.tanker:IsAlive()then
local fuel=self.tanker:GetFuel()*100
local life=self.tanker:GetUnit(1):GetLife()
local life0=self.tanker:GetUnit(1):GetLife0()
local lifeR=self.tanker:GetUnit(1):GetLifeRelative()
local text=string.format("Recovery tanker %s: state=%s fuel=%.1f, life=%.1f/%.1f=%d",self.tanker:GetName(),self:GetState(),fuel,life,life0,lifeR*100)
self:T(self.lid..text)
MESSAGE:New(text,10):ToAllIf(self.Debug)
if self:IsRunning()then
if fuel<self.lowfuel then
if(self.takeoff==SPAWN.Takeoff.Air or self.respawninair)and not self.recovery then
if self.respawn then
local text=string.format("Respawning recovery tanker %s in air.",self.tanker:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.tanker:InitHeading(self.tanker:GetHeading())
self.tanker:InitRadioCommsOnOff(true)
self.tanker:InitRadioFrequency(self.RadioFreq)
self.tanker:InitRadioModulation(self.RadioModu)
self.tanker:InitModex(self.modex)
self.tanker=self.tanker:Respawn(nil,true)
self:__PatternUpdate(2)
if self.TACANon then
self:_ActivateTACAN(3)
end
if self.callsignname then
self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,3)
end
if self.eplrs then
self.tanker:CommandEPLRS(true,4)
end
end
else
self:RTB(self.airbase)
end
else
if self.Tupdate then
local dt=time-self.Tupdate
local updatepattern=self:_CheckPatternUpdate(dt)
if updatepattern then
self:PatternUpdate()
end
end
end
elseif self:IsReturning()then
self:T2(self.lid.."Tanker is returning.")
elseif self:IsReturned()then
self:T2(self.lid.."Tanker returned. waiting for engine shutdown.")
end
if not self:IsStopped()then
self:__Status(-30)
end
else
if not self:IsStopped()then
self:E(self.lid.."Recovery tanker is NOT alive (and not stopped)!")
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RECOVERYTANKER:onafterPatternUpdate(From,Event,To)
local text=string.format("Updating recovery tanker %s racetrack pattern.",self.tanker:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
local hdg=self.carrier:GetHeading()
local Carrier=self.carrier:GetCoordinate()
local p0=self.tanker:GetCoordinate():Translate(UTILS.NMToMeters(1),self.tanker:GetHeading(),true)
local p1=Carrier:Translate(self.distStern,hdg):SetAltitude(self.altitude)
local p2=Carrier:Translate(self.distBow,hdg):SetAltitude(self.altitude)
local taskorbit=self.tanker:TaskOrbit(p1,self.altitude,self.speed,p2)
if self.Debug then
p0:MarkToAll("Waypoint P0 "..self.tanker:GetName())
p1:MarkToAll("Racetrack P1 "..self.tanker:GetName())
p2:MarkToAll("Racetrack P2 "..self.tanker:GetName())
end
local wp={}
wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed),{},"Current Position")
wp[2]=p0:WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed),{taskorbit},"Tanker Orbit")
self.tanker:WayPointInitialize(wp)
local taskroll=self.tanker:EnRouteTaskTanker()
if self.awacs then
taskroll=self.tanker:EnRouteTaskAWACS()
end
local taskroute=self.tanker:TaskRoute(wp)
local taskcombo=self.tanker:TaskCombo({taskroll,taskroute})
self.tanker:SetTask(taskcombo,1)
self.Tupdate=timer.getTime()
end
function RECOVERYTANKER:onafterRTB(From,Event,To,airbase)
airbase=airbase or self.airbase
local text=string.format("Recovery tanker %s returning to airbase %s.",self.tanker:GetName(),airbase:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
local wp={}
local speed=self.tanker:GetSpeedMax()*0.75
wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil,speed,{},"Current Position")
if self.recovery then
wp[2]=self.tanker:GetCoordinate():Translate(UTILS.NMToMeters(10),self.tanker:GetHeading(),true):WaypointAirTurningPoint(nil,speed,{},"WP")
else
wp[2]=airbase:GetCoordinate():SetAltitude(500):WaypointAirLanding(speed,airbase,nil,"Land at airbase")
end
self.tanker:WayPointInitialize(wp)
self.tanker:Route(wp,1)
end
function RECOVERYTANKER:onafterReturned(From,Event,To,airbase)
if airbase then
local airbasename=airbase:GetName()
self:I(self.lid..string.format("Tanker returned to airbase %s",tostring(airbasename)))
else
self:E(self.lid..string.format("WARNING: Tanker landed but airbase (EventData.Place) is nil!"))
end
end
function RECOVERYTANKER:onafterStop(From,Event,To)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Refueling)
self:UnHandleEvent(EVENTS.RefuelingStop)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.Crash)
self.CallScheduler:Clear()
if self.tanker and self.tanker:IsAlive()then
self:I(self.lid.."Stopping FSM and despawning tanker.")
self.tanker:Destroy()
else
self:I(self.lid.."Stopping FSM. Tanker was not alive.")
end
end
function RECOVERYTANKER:OnEventLand(EventData)
local group=EventData.IniGroup
if group and group:IsAlive()then
local groupname=group:GetName()
if groupname==self.tanker:GetName()then
local airbase=nil
local airbasename="unknown"
if EventData.Place then
airbase=EventData.Place
airbasename=airbase:GetName()
end
local text=string.format("Recovery tanker group %s landed at airbase %s.",group:GetName(),airbasename)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self:__Returned(1,airbase)
end
end
end
function RECOVERYTANKER:OnEventEngineShutdown(EventData)
local group=EventData.IniGroup
if group and group:IsAlive()then
local groupname=group:GetName()
if groupname==self.tanker:GetName()and self.respawn then
local text=string.format("Respawning recovery tanker group %s.",group:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
group:InitRadioCommsOnOff(true)
group:InitRadioFrequency(self.RadioFreq)
group:InitRadioModulation(self.RadioModu)
group:InitModex(self.modex)
self:ScheduleOnce(1,GROUP.RespawnAtCurrentAirbase,group)
if self.TACANon then
self:_ActivateTACAN(3)
end
if self.callsignname then
self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,3)
end
if self.eplrs then
self.tanker:CommandEPLRS(true,4)
end
self:ScheduleOnce(2,RECOVERYTANKER._InitRoute,self,-self.distStern+UTILS.NMToMeters(3))
end
end
end
function RECOVERYTANKER:_RefuelingStart(EventData)
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()then
local receiver=EventData.IniUnit
local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
if dist>100 then
return
end
local text=string.format("Recovery tanker %s started refueling unit %s",self.tanker:GetName(),receiver:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self:RefuelStart(receiver)
end
end
function RECOVERYTANKER:_RefuelingStop(EventData)
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()then
local receiver=EventData.IniUnit
local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate())
if dist>100 then
return
end
local text=string.format("Recovery tanker %s stopped refueling unit %s",self.tanker:GetName(),receiver:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self:RefuelStop(receiver)
end
end
function RECOVERYTANKER:_OnEventCrashOrDead(EventData)
self:F2({eventdata=EventData})
if EventData and EventData.IniUnit then
local unit=EventData.IniUnit
local unitname=tostring(EventData.IniUnitName)
if EventData.IniGroupName==self.tanker:GetName()then
self:E(self.lid..string.format("Recovery tanker %s crashed!",unitname))
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RECOVERYTANKER:_InitPatternTaskFunction()
local carriername=self.carrier:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mycarrier = UNIT:FindByName(\"%s\") ',carriername)
DCSScript[#DCSScript+1]=string.format('local mytanker  = mycarrier:GetState(mycarrier, \"RECOVERYTANKER_%d\") ',self.uid)
DCSScript[#DCSScript+1]=string.format('mytanker:PatternUpdate()')
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function RECOVERYTANKER:_InitRoute(dist,delay)
dist=dist or UTILS.NMToMeters(8)
delay=delay or 1
self:T(self.lid..string.format("Initializing route of recovery tanker %s.",self.tanker:GetName()))
local Carrier=self.carrier:GetCoordinate()
local hdg=self.carrier:GetHeading()
local p=Carrier:Translate(dist,hdg+190):SetAltitude(self.altitude)
local speed=self.tanker:GetSpeedMax()*0.8
if self.Debug then
p:MarkToAll(string.format("Enter Pattern WP: alt=%d ft, speed=%d kts",UTILS.MetersToFeet(self.altitude),speed*0.539957))
end
local task=self:_InitPatternTaskFunction()
local wp={}
if self.takeoff==SPAWN.Takeoff.Air then
wp[#wp+1]=self.tanker:GetCoordinate():SetAltitude(self.altitude):WaypointAirTurningPoint(nil,speed,{},"Spawn Position")
else
wp[#wp+1]=Carrier:WaypointAirTakeOffParking()
end
wp[#wp+1]=p:WaypointAirTurningPoint(nil,speed,{task},"Enter Pattern")
self.tanker:Route(wp,delay)
self:__Run(1)
self.Tupdate=nil
end
function RECOVERYTANKER:_CheckPatternUpdate(dt)
local pos=self.carrier:GetCoordinate()
local vNew=self.carrier:GetOrientationX()
local vOld=self.orientation
local vLast=self.orientlast
vNew.y=0;vOld.y=0;vLast.y=0
local deltaHeading=math.deg(math.acos(UTILS.VecDot(vNew,vOld)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vOld)))
local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast)))
self.orientlast=vNew
local turning=deltaLast>=1
if turning then
self:T2(self.lid..string.format("Carrier is turning. Delta Heading = %.1f",deltaLast))
end
local Hchange=false
if math.abs(deltaHeading)>=self.Hupdate then
self:T(self.lid..string.format("Carrier heading changed by %d degrees. Turning=%s.",deltaHeading,tostring(turning)))
Hchange=true
end
local dist=pos:Get2DDistance(self.position)
local Dchange=false
if dist>self.Dupdate then
self:T(self.lid..string.format("Carrier position changed by %.1f NM. Turning=%s.",UTILS.MetersToNM(dist),tostring(turning)))
Dchange=true
end
local update=false
if self:IsRunning()and dt>self.dTupdate and not turning then
if Hchange or Dchange then
local text=string.format("Updating tanker %s pattern due to carrier position=%s or heading=%s change.",self.tanker:GetName(),tostring(Dchange),tostring(Hchange))
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.orientation=vNew
self.position=pos
update=true
end
end
return update
end
function RECOVERYTANKER:_ActivateTACAN(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,RECOVERYTANKER._ActivateTACAN,self)
else
local unit=self.tanker:GetUnit(1)
if unit and unit:IsAlive()then
local text=string.format("Activating TACAN beacon: channel=%d mode=%s, morse=%s.",self.TACANchannel,self.TACANmode,self.TACANmorse)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.beacon=BEACON:New(unit)
self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true)
else
self:E(self.lid.."ERROR: Recovery tanker is not alive!")
end
end
end
function RECOVERYTANKER:_Pattern()
local hdg=self.carrier:GetHeading()
local alt=self.altitude
local Carrier=self.carrier:GetCoordinate()
local width=UTILS.NMToMeters(8)
local p={}
p[1]=self.tanker:GetCoordinate()
p[2]=Carrier:SetAltitude(alt)
p[3]=p[2]:Translate(self.distBow,hdg)
p[4]=p[3]:Translate(width/math.sqrt(2),hdg-45)
p[5]=p[3]:Translate(width,hdg-90)
p[6]=p[5]:Translate(self.distStern-self.distBow,hdg)
p[7]=p[2]:Translate(self.distStern,hdg)
local wp={}
for i=1,#p do
local coord=p[i]
coord:MarkToAll(string.format("Waypoint %d",i))
table.insert(wp,coord:WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed)))
end
return wp
end
RESCUEHELO={
ClassName="RESCUEHELO",
Debug=false,
lid=nil,
carrier=nil,
carriertype=nil,
helogroupname=nil,
helo=nil,
airbase=nil,
takeoff=nil,
followset=nil,
formation=nil,
lowfuel=nil,
altitude=nil,
offsetX=nil,
offsetZ=nil,
rescuezone=nil,
respawn=nil,
respawninair=nil,
uncontrolledac=nil,
rescueon=nil,
rescueduration=nil,
rescuespeed=nil,
rescuestopboat=nil,
HeloFuel0=nil,
rtb=nil,
carrierstop=nil,
alias=nil,
uid=0,
modex=nil,
dtFollow=nil,
}
_RESCUEHELOID=0
RESCUEHELO.version="1.1.0"
function RESCUEHELO:New(carrierunit,helogroupname)
local self=BASE:Inherit(self,FSM:New())
if type(carrierunit)=="string"then
self.carrier=UNIT:FindByName(carrierunit)
else
self.carrier=carrierunit
end
self.carriertype=self.carrier:GetTypeName()
self.helogroupname=helogroupname
_RESCUEHELOID=_RESCUEHELOID+1
self.uid=_RESCUEHELOID
self.carrier:SetState(self.carrier,string.format("RESCUEHELO_%d",self.uid),self)
self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.helogroupname,_RESCUEHELOID)
self.lid=string.format("RESCUEHELO %s | ",self.alias)
self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName()))
self:SetTakeoffHot()
self:SetLowFuelThreshold()
self:SetAltitude()
self:SetOffsetX()
self:SetOffsetZ()
self:SetRespawnOn()
self:SetRescueOn()
self:SetRescueZone()
self:SetRescueHoverSpeed()
self:SetRescueDuration()
self:SetFollowTimeInterval()
self:SetRescueStopBoatOff()
self.rtb=false
self.carrierstop=false
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("Running","Rescue","Rescuing")
self:AddTransition("Running","RTB","Returning")
self:AddTransition("Rescuing","RTB","Returning")
self:AddTransition("Returning","Returned","Returned")
self:AddTransition("Running","Run","Running")
self:AddTransition("Returned","Run","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function RESCUEHELO:SetLowFuelThreshold(threshold)
self.lowfuel=threshold or 5
return self
end
function RESCUEHELO:SetHomeBase(airbase)
if type(airbase)=="string"then
self.airbase=AIRBASE:FindByName(airbase)
else
self.airbase=airbase
end
if not self.airbase then
self:E(self.lid.."ERROR: Airbase is nil!")
end
return self
end
function RESCUEHELO:SetRescueZone(radius)
radius=UTILS.NMToMeters(radius or 15)
self.rescuezone=ZONE_UNIT:New("Rescue Zone",self.carrier,radius)
return self
end
function RESCUEHELO:SetRescueHoverSpeed(speed)
self.rescuespeed=UTILS.KnotsToMps(speed or 5)
return self
end
function RESCUEHELO:SetRescueDuration(duration)
self.rescueduration=(duration or 5)*60
return self
end
function RESCUEHELO:SetRescueOn()
self.rescueon=true
return self
end
function RESCUEHELO:SetRescueOff()
self.rescueon=false
return self
end
function RESCUEHELO:SetRescueStopBoatOn()
self.rescuestopboat=true
return self
end
function RESCUEHELO:SetRescueStopBoatOff()
self.rescuestopboat=false
return self
end
function RESCUEHELO:SetTakeoff(takeofftype)
self.takeoff=takeofftype or SPAWN.Takeoff.Hot
return self
end
function RESCUEHELO:SetTakeoffHot()
self:SetTakeoff(SPAWN.Takeoff.Hot)
return self
end
function RESCUEHELO:SetTakeoffCold()
self:SetTakeoff(SPAWN.Takeoff.Cold)
return self
end
function RESCUEHELO:SetTakeoffAir()
self:SetTakeoff(SPAWN.Takeoff.Air)
return self
end
function RESCUEHELO:SetAltitude(alt)
self.altitude=alt or 70
return self
end
function RESCUEHELO:SetOffsetX(distance)
self.offsetX=distance or 200
return self
end
function RESCUEHELO:SetOffsetZ(distance)
self.offsetZ=distance or 240
return self
end
function RESCUEHELO:SetRespawnOn()
self.respawn=true
return self
end
function RESCUEHELO:SetRespawnOff()
self.respawn=false
return self
end
function RESCUEHELO:SetRespawnOnOff(switch)
if switch==nil or switch==true then
self.respawn=true
else
self.respawn=false
end
return self
end
function RESCUEHELO:SetRespawnInAir()
self.respawninair=true
return self
end
function RESCUEHELO:SetModex(modex)
self.modex=modex
return self
end
function RESCUEHELO:SetFollowTimeInterval(dt)
self.dtFollow=dt or 1.0
return self
end
function RESCUEHELO:SetUseUncontrolledAircraft()
self.uncontrolledac=true
return self
end
function RESCUEHELO:SetDebugModeON()
self.Debug=true
return self
end
function RESCUEHELO:SetDebugModeOFF()
self.Debug=false
return self
end
function RESCUEHELO:IsReturning()
return self:is("Returning")
end
function RESCUEHELO:IsRunning()
return self:is("Running")
end
function RESCUEHELO:IsRescuing()
return self:is("Rescuing")
end
function RESCUEHELO:IsStopped()
return self:is("Stopped")
end
function RESCUEHELO:GetAlias()
return self.alias
end
function RESCUEHELO:GetUnitName()
local unit=self.helo:GetUnit(1)
if unit then
return unit:GetName()
end
return nil
end
function RESCUEHELO:OnEventLand(EventData)
local group=EventData.IniGroup
if group and group:IsAlive()then
local groupname=group:GetName()
if groupname==self.helo:GetName()then
local airbase=nil
local airbasename="unknown"
if EventData.Place then
airbase=EventData.Place
airbasename=airbase:GetName()
end
local text=string.format("Rescue helo group %s landed at airbase %s.",groupname,airbasename)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
if self:IsRescuing()then
self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.",groupname))
end
if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then
if not self:IsRescuing()then
self:E(self.lid..string.format("WARNING: Rescue helo %s landed. This should not happen for Takeoff=Air or respawninair=true and no rescue operation in progress.",groupname))
end
end
self:__Returned(3,airbase)
end
end
end
function RESCUEHELO:_OnEventCrashOrEject(EventData)
self:F2({eventdata=EventData})
if EventData and EventData.IniUnit then
local unit=EventData.IniUnit
local unitname=tostring(EventData.IniUnitName)
if EventData.IniGroupName~=self.helo:GetName()then
local text=string.format("Unit %s crashed or ejected.",unitname)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
local Vec3=EventData.IniDCSUnit:getPoint()
local coord=COORDINATE:NewFromVec3(Vec3)
if coord and self.rescuezone:IsCoordinateInZone(coord)then
if self.Debug then
coord:MarkToCoalition(self.lid..string.format("Crash site of unit %s.",unitname),self.helo:GetCoalition())
end
local rightcoalition=EventData.IniGroup:GetCoalition()==self.helo:GetCoalition()
if self:IsRunning()and self.rescueon and rightcoalition then
self:Rescue(coord)
end
end
else
self:E(self.lid..string.format("Rescue helo %s crashed!",unitname))
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RESCUEHELO:onafterStart(From,Event,To)
local text=string.format("Starting Rescue Helo Formation v%s for carrier unit %s of type %s.",RESCUEHELO.version,self.carrier:GetName(),self.carriertype)
self:I(self.lid..text)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrEject)
self:HandleEvent(EVENTS.Ejection,self._OnEventCrashOrEject)
local delay=120
local Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias)
Spawn:InitModex(self.modex)
if self.takeoff==SPAWN.Takeoff.Air then
local hdg=self.carrier:GetHeading()
local dist=UTILS.NMToMeters(0.2)
local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg):SetAltitude(math.max(100,self.altitude))
Spawn:InitHeading(hdg)
self.helo=Spawn:SpawnFromCoordinate(Carrier)
delay=1
else
if self.uncontrolledac then
self.helo=GROUP:FindByName(self.helogroupname)
if self.helo and self.helo:IsAlive()then
self.helo:StartUncontrolled()
delay=60
else
self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname))
return
end
else
self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable)
if self.takeoff==SPAWN.Takeoff.Runway then
delay=5
elseif self.takeoff==SPAWN.Takeoff.Hot then
delay=30
elseif self.takeoff==SPAWN.Takeoff.Cold then
delay=60
end
end
end
self.followset=SET_GROUP:New()
self.followset:AddGroup(self.helo)
self.HeloFuel0=self.helo:GetFuel()
self.formation=AI_FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier","Follow Carrier at given parameters.")
self.formation:FormationCenterWing(-self.offsetX,50,math.abs(self.altitude),50,self.offsetZ,50)
self.formation:SetFollowTimeInterval(self.dtFollow)
self.formation:SetFlightModeFormation(self.helo)
self.formation:__Start(delay)
self:__Status(1)
return self
end
function RESCUEHELO:onafterStatus(From,Event,To)
local time=timer.getTime()
if self.helo and self.helo:IsAlive()then
local fuel=self.helo:GetFuel()*100
local fuelrel=fuel/self.HeloFuel0
local life=self.helo:GetUnit(1):GetLife()
local life0=self.helo:GetUnit(1):GetLife0()
local lifeR=self.helo:GetUnit(1):GetLifeRelative()
local text=string.format("Rescue Helo %s: state=%s fuel=%.1f, rel.fuel=%.1f, life=%.1f/%.1f=%d",self.helo:GetName(),self:GetState(),fuel,fuelrel,life,life0,lifeR*100)
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
if self:IsRunning()then
if fuel<self.lowfuel then
if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then
if self.respawn then
self.helo:InitModex(self.modex)
self.helo=self.helo:Respawn(nil,true)
end
else
self:RTB(self.airbase)
end
end
elseif self:IsRescuing()then
end
if not self:IsStopped()then
self:__Status(-30)
end
else
if not self:IsStopped()then
self:E(self.lid.."Rescue helo is NOT alive (and not stopped)!")
self:Stop()
if self.respawn then
self:__Start(5)
end
end
end
end
function RESCUEHELO:onafterRun(From,Event,To)
if self.formation:Is("Stopped")then
local text=string.format("Restarting formation of rescue helo %s.",self.helo:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
self.formation:Start()
end
end
function RESCUEHELO:_TaskRTB()
local carriername=self.carrier:GetName()
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mycarrier = UNIT:FindByName(\"%s\") ',carriername)
DCSScript[#DCSScript+1]=string.format('local myhelo    = mycarrier:GetState(mycarrier, \"RESCUEHELO_%d\") ',self.uid)
DCSScript[#DCSScript+1]=string.format('myhelo:RTB()')
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function RESCUEHELO:onafterRescue(From,Event,To,RescueCoord)
local text=string.format("Helo %s is send to rescue mission.",self.helo:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
local wp={}
local RescueTask={}
RescueTask.id="ControlledTask"
RescueTask.params={}
RescueTask.params.task=self.helo:TaskOrbit(RescueCoord,20,self.rescuespeed)
RescueTask.params.stopCondition={duration=self.rescueduration}
local TaskRTB=self:_TaskRTB()
local speed=self.helo:GetSpeedMax()*0.9
wp[1]=self.helo:GetCoordinate():WaypointAirTurningPoint(nil,speed,{},"Current Position")
wp[2]=RescueCoord:SetAltitude(50):WaypointAirTurningPoint(nil,speed,{RescueTask,TaskRTB},"Crash Site")
wp[3]=self.airbase:GetCoordinate():SetAltitude(50):WaypointAirTurningPoint(nil,speed,{},"RTB")
self.helo:WayPointInitialize(wp)
self.helo:Route(wp,1)
self.formation:Stop()
end
function RESCUEHELO:onafterRTB(From,Event,To,airbase)
airbase=airbase or self.airbase
local text=string.format("Rescue helo %s is returning to airbase %s.",self.helo:GetName(),airbase:GetName())
MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug)
self:T(self.lid..text)
if From=="Running"then
self.formation:Stop()
end
self:RouteRTB(airbase)
end
function RESCUEHELO:onafterReturned(From,Event,To,airbase)
if airbase then
local airbasename=airbase:GetName()
self:I(self.lid..string.format("Helo returned to airbase %s",tostring(airbasename)))
else
self:E(self.lid..string.format("WARNING: Helo landed but airbase (EventData.Place) is nil!"))
end
if self.respawn then
self.helo:InitModex(self.modex)
if self.helo and self.helo:IsAlive()then
self:ScheduleOnce(5,self.helo.RespawnAtCurrentAirbase,self.helo)
end
self:__Run(10)
end
end
function RESCUEHELO:onafterStop(From,Event,To)
self.formation:Stop()
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Ejection)
self.CallScheduler:Clear()
if self.helo and self.helo:IsAlive()then
self:I(self.lid.."Stopping FSM and despawning helo.")
self.helo:Destroy()
else
self:I(self.lid.."Stopping FSM. Helo was not alive.")
end
end
function RESCUEHELO:RouteRTB(RTBAirbase,Speed)
local Speed=Speed or self.helo:GetSpeedMax()*0.8
local coord=self.helo:GetCoordinate()
local PointFrom=coord:WaypointAirTurningPoint(nil,Speed,{},"Current")
local PointLanding=RTBAirbase:GetCoordinate():SetAltitude(20):WaypointAirLanding(Speed,RTBAirbase,{},"Landing")
local Points={PointFrom,PointLanding}
local Template=self.helo:GetTemplate()
Template.route.points=Points
self.helo:InitModex(self.modex)
self.helo=self.helo:Respawn(Template,true)
self.helo:Route(Points,1)
return self
end
ATIS={
ClassName="ATIS",
lid=nil,
theatre=nil,
airbasename=nil,
airbase=nil,
frequency=nil,
modulation=nil,
power=nil,
radioqueue=nil,
soundpath=nil,
relayunitname=nil,
towerfrequency=nil,
activerunway=nil,
subduration=nil,
metric=nil,
PmmHg=nil,
qnhonly=false,
TDegF=nil,
zuludiff=nil,
zulutimeonly=false,
magvar=nil,
ils={},
ndbinner={},
ndbouter={},
vor=nil,
tacan=nil,
rsbn=nil,
prmg={},
rwylength=nil,
elevation=nil,
runwaymag={},
runwaym2t=nil,
windtrue=nil,
altimeterQNH=nil,
usemarker=nil,
markerid=nil,
relHumidity=nil,
ReportmBar=false,
TransmitOnlyWithPlayers=false,
ATISforFARPs=false,
locale="en",
}
ATIS.Alphabet={
[1]="Alfa",
[2]="Bravo",
[3]="Charlie",
[4]="Delta",
[5]="Echo",
[6]="Delta",
[7]="Echo",
[8]="Foxtrot",
[9]="Golf",
[10]="Hotel",
[11]="India",
[12]="Juliett",
[13]="Kilo",
[14]="Lima",
[15]="Mike",
[16]="November",
[17]="Oscar",
[18]="Papa",
[19]="Quebec",
[20]="Romeo",
[21]="Sierra",
[22]="Tango",
[23]="Uniform",
[24]="Victor",
[25]="Whiskey",
[26]="Xray",
[27]="Yankee",
[28]="Zulu",
}
ATIS.RunwayM2T={
Caucasus=0,
Nevada=12,
Normandy=-10,
PersianGulf=2,
TheChannel=-10,
Syria=5,
MarianaIslands=2,
Falklands=12,
SinaiMap=5,
Kola=15,
Afghanistan=3,
Iraq=4.4,
GermanyCW=0.1,
}
ATIS.ICAOPhraseology={
Caucasus=true,
Nevada=false,
Normandy=true,
PersianGulf=true,
TheChannel=true,
Syria=true,
MarianaIslands=true,
Falklands=true,
SinaiMap=true,
Kola=true,
Afghanistan=true,
Iraq=true,
GermanyCW=true,
}
ATIS.Sound={
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},
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=
{
HOURS="hours",
TIME="Hours",
NOCLOUDINFO="Cloud coverage information not available",
OVERCAST="Overcast",
BROKEN="Broken clouds",
SCATTERED="Scattered clouds",
FEWCLOUDS="Few clouds",
NOCLOUDS="No clouds",
AIRPORT="Airport",
INFORMATION="Information",
SUNRISEAT="Sunrise at %s local time",
SUNSETAT="Sunset at %s local time",
WINDFROMMS="Wind from %s at %s m/s",
WINDFROMKNOTS="Wind from %s at %s knots",
GUSTING="gusting",
VISIKM="Visibility %s km",
VISISM="Visibility %s SM",
RAIN="rain",
TSTORM="thunderstorm",
SNOW="snow",
SSTROM="snowstorm",
FOG="fog",
DUST="dust",
PHENOMENA="Weather phenomena",
CLOUDBASEM="Cloud base %s, ceiling %s meters",
CLOUDBASEFT="Cloud base %s, ceiling %s feet",
TEMPERATURE="Temperature",
DEWPOINT="Dew point",
ALTIMETER="Altimeter",
ACTIVERUN="Active runway departure",
ACTIVELANDING="Active runway arrival",
LEFT="Left",
RIGHT="Right",
RWYLENGTH="Runway length",
METERS="meters",
FEET="feet",
ELEVATION="Elevation",
TOWERFREQ="Tower frequency",
ILSFREQ="ILS frequency",
OUTERNDB="Outer NDB frequency",
INNERNDB="Inner NDB frequency",
VORFREQ="VOR frequency",
VORFREQTTS="V O R frequency",
TACANCH="TACAN channel %dX Ray",
RSBNCH="RSBN channel",
PRMGCH="PRMG channel",
ADVISE="Advise on initial contact, you have information",
STATUTE="statute miles",
DEGREES="degrees Celsius",
FAHRENHEIT="degrees Fahrenheit",
INCHHG="inches of Mercury",
MMHG="millimeters of Mercury",
HECTO="hectopascals",
METERSPER="meters per second",
TACAN="tackan",
FARP="farp",
DELIMITER="point",
},
DE=
{
HOURS="Uhr",
TIME="Zeit",
NOCLOUDINFO="Informationen über Wolken nicht verfuegbar",
OVERCAST="Geschlossene Wolkendecke",
BROKEN="Stark bewoelkt",
SCATTERED="Bewoelkt",
FEWCLOUDS="Leicht bewoelkt",
NOCLOUDS="Klar",
AIRPORT="Flughafen",
INFORMATION="Information",
SUNRISEAT="Sonnenaufgang um %s lokaler Zeit",
SUNSETAT="Sonnenuntergang um %s lokaler Zeit",
WINDFROMMS="Wind aus %s mit %s m/s",
WINDFROMKNOTS="Wind aus %s mit %s Knoten",
GUSTING="boeig",
VISIKM="Sichtweite %s km",
VISISM="Sichtweite %s Meilen",
RAIN="Regen",
TSTORM="Gewitter",
SNOW="Schnee",
SSTROM="Schneesturm",
FOG="Nebel",
DUST="Staub",
PHENOMENA="Wetter Phaenomene",
CLOUDBASEM="Wolkendecke von %s bis %s Meter",
CLOUDBASEFT="Wolkendecke von %s bis %s Fuß",
TEMPERATURE="Temperatur",
DEWPOINT="Taupunkt",
ALTIMETER="Hoehenmesser",
ACTIVERUN="Aktive Startbahn",
ACTIVELANDING="Aktive Landebahn",
LEFT="Links",
RIGHT="Rechts",
RWYLENGTH="Startbahn",
METERS="Meter",
FEET="Fuß",
ELEVATION="Hoehe",
TOWERFREQ="Kontrollturm Frequenz",
ILSFREQ="ILS Frequenz",
OUTERNDB="Aeussere NDB Frequenz",
INNERNDB="Innere NDB Frequenz",
VORFREQ="VOR Frequenz",
VORFREQTTS="V O R Frequenz",
TACANCH="TACAN Kanal %d Xaver",
RSBNCH="RSBN Kanal",
PRMGCH="PRMG Kanal",
ADVISE="Hinweis bei Erstkontakt, Sie haben Informationen",
STATUTE="englische Meilen",
DEGREES="Grad Celsius",
FAHRENHEIT="Grad Fahrenheit",
INCHHG="Inches H G",
MMHG="Millimeter H G",
HECTO="Hektopascal",
METERSPER="Meter pro Sekunde",
TACAN="Tackan",
FARP="Farp",
DELIMITER="Komma",
},
ES=
{
HOURS="horas",
TIME="horas",
NOCLOUDINFO="Información sobre capa de nubes no disponible",
OVERCAST="Nublado",
BROKEN="Nubes rotas",
SCATTERED="Nubes dispersas",
FEWCLOUDS="Ligeramente nublado",
NOCLOUDS="Despejado",
AIRPORT="Aeropuerto",
INFORMATION="Informacion",
SUNRISEAT="Amanecer a las %s hora local",
SUNSETAT="Puesta de sol a las %s hora local",
WINDFROMMS="Viento procedente de %s con %s m/s",
WINDFROMKNOTS="Viento de %s con %s nudos",
GUSTING="ráfagas",
VISIKM="Visibilidad %s km",
VISISM="Visibilidad %s millas",
RAIN="Lluvia",
TSTORM="Tormenta",
SNOW="Nieve",
SSTROM="Tormenta de nieve",
FOG="Niebla",
DUST="Polvo",
PHENOMENA="Fenómenos meteorológicos",
CLOUDBASEM="Capa de nubes de %s a %s metros",
CLOUDBASEFT="Capa de nubes de %s a %s pies",
TEMPERATURE="Temperatura",
DEWPOINT="Punto de rocio",
ALTIMETER="Altímetro",
ACTIVERUN="Pista activa",
ACTIVELANDING="Pista de aterrizaje activa",
LEFT="Izquierda",
RIGHT="Derecha",
RWYLENGTH="Longitud de pista",
METERS="Metro",
FEET="Pie",
ELEVATION="Elevación",
TOWERFREQ="Frecuencias de la torre de control",
ILSFREQ="Fecuencia ILS",
OUTERNDB="Frecuencia NDB externa",
INNERNDB="Frecuencia NDB interior",
VORFREQ="Frecuencia VOR",
VORFREQTTS="Frecuencia V O R",
TACANCH="Canal TACAN %d Xaver",
RSBNCH="Canal RSBN",
PRMGCH="Canal PRMG",
ADVISE="Avise en el contacto inicial a torre de que tiene la informacion",
STATUTE="Millas inglesas",
DEGREES="Grados Celsius",
FAHRENHEIT="Grados Fahrenheit",
INCHHG="Pulgadas de mercurio",
MMHG="Milímeteros de Mercurio",
HECTO="Hectopascales",
METERSPER="Metros por segundo",
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.1"
function ATIS:New(AirbaseName,Frequency,Modulation)
local self=BASE:Inherit(self,FSM:New())
self.airbasename=AirbaseName
self.airbase=AIRBASE:FindByName(AirbaseName)
if self.airbase==nil then
self:E("ERROR: Airbase %s for ATIS could not be found!",tostring(AirbaseName))
return nil
end
self.frequency=Frequency or 143.00
self.modulation=Modulation or 0
self.theatre=env.mission.theatre
self.lid=string.format("ATIS %s | ",self.airbasename)
_ATIS[#_ATIS+1]=self
self:SetSoundfilesPath()
self:SetSubtitleDuration()
self:SetMagneticDeclination()
self:SetRunwayCorrectionMagnetic2True()
self:SetRadioPower()
self:SetAltimeterQNH(true)
self:SetMapMarks(false)
self:SetRelativeHumidity()
self:SetQueueUpdateTime()
self:SetReportmBar(false)
self:_InitLocalization()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Broadcast","*")
self:AddTransition("*","CheckQueue","*")
self:AddTransition("*","Report","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function ATIS:_InitLocalization()
self:T(self.lid.."_InitLocalization")
self.gettext=TEXTANDSOUND:New("ATIS","en")
self.locale="en"
for locale,table in pairs(self.Messages)do
local Locale=string.lower(tostring(locale))
self:T("**** Adding locale: "..Locale)
for ID,Text in pairs(table)do
self:T(string.format('Adding ID %s',tostring(ID)))
self.gettext:AddEntry(Locale,tostring(ID),Text)
end
end
return self
end
function ATIS:SetLocale(locale)
self.locale=string.lower(locale)
return self
end
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))
return self
end
function ATIS:SetTowerFrequencies(freqs)
if type(freqs)=="table"then
else
freqs={freqs}
end
self.towerfrequency=freqs
return self
end
function ATIS:SetTransmitOnlyWithPlayers(Switch)
self.TransmitOnlyWithPlayers=Switch
if self.msrsQ then
self.msrsQ:SetTransmitOnlyWithPlayers(Switch)
end
return self
end
function ATIS:SetActiveRunway(runway)
self.activerunway=tostring(runway)
local prefer=nil
if string.find(string.lower(runway),"l")then
prefer=true
elseif string.find(string.lower(runway),"r")then
prefer=false
end
self.airbase:SetActiveRunway(runway,prefer)
return self
end
function ATIS:SetActiveRunwayLanding(runway,preferleft)
self.airbase:SetActiveRunwayLanding(runway,preferleft)
return self
end
function ATIS:SetActiveRunwayTakeoff(runway,preferleft)
self.airbase:SetActiveRunwayTakeoff(runway,preferleft)
return self
end
function ATIS:SetRunwayLength()
self.rwylength=true
return self
end
function ATIS:SetRunwayLength()
self.rwylength=true
return self
end
function ATIS:SetElevation()
self.elevation=true
return self
end
function ATIS:SetRadioPower(power)
self.power=power or 100
return self
end
function ATIS:SetMapMarks(switch)
if switch==nil or switch==true then
self.usemarker=true
else
self.usemarker=false
end
return self
end
function ATIS:GetSRSText()
return self.SRSText
end
function ATIS:SetRunwayHeadingsMagnetic(headings)
if type(headings)=="table"then
else
headings={headings}
end
for _,heading in pairs(headings)do
if type(heading)=="number"then
heading=string.format("%02d",heading)
end
self:T(self.lid..string.format("Adding user specified magnetic runway heading %s",heading))
table.insert(self.runwaymag,heading)
local h=self:GetRunwayWithoutLR(heading)
local head2=tonumber(h)-18
if head2<0 then
head2=head2+36
end
head2=string.format("%02d",head2)
local left=self:GetRunwayLR(heading)
if left==true then
head2=head2.."L"
elseif left==false then
head2=head2.."R"
end
self:T(self.lid..string.format("Adding user specified magnetic runway heading %s (inverse)",head2))
table.insert(self.runwaymag,head2)
end
return self
end
function ATIS:SetSubtitleDuration(duration)
self.subduration=tonumber(duration or 10)
return self
end
function ATIS:SetMetricUnits()
self.metric=true
return self
end
function ATIS:SetImperialUnits()
self.metric=false
return self
end
function ATIS:SetPressureMillimetersMercury()
self.PmmHg=true
return self
end
function ATIS:SetTemperatureFahrenheit()
self.TDegF=true
return self
end
function ATIS:SetRelativeHumidity(Humidity)
self.relHumidity=Humidity or 50
return self
end
function ATIS:SetAltimeterQNH(switch)
if switch==true or switch==nil then
self.altimeterQNH=true
else
self.altimeterQNH=false
end
return self
end
function ATIS:SetReportmBar(switch)
if switch==true or switch==nil then
self.ReportmBar=true
else
self.ReportmBar=false
end
return self
end
function ATIS:SetAdditionalInformation(text)
self.AdditionalInformation=text
return self
end
function ATIS:ReportQNHOnly()
self.qnhonly=true
return self
end
function ATIS:SetMagneticDeclination(magvar)
self.magvar=magvar or UTILS.GetMagneticDeclination()
return self
end
function ATIS:SetRunwayCorrectionMagnetic2True(correction)
self.runwaym2t=correction or ATIS.RunwayM2T[UTILS.GetDCSMap()]
return self
end
function ATIS:SetReportWindTrue()
self.windtrue=true
return self
end
function ATIS:SetZuluTimeDifference(delta)
self.zuludiff=delta
return self
end
function ATIS:ReportZuluTimeOnly()
self.zulutimeonly=true
return self
end
function ATIS:AddILS(frequency,runway)
local ils={}
ils.frequency=tonumber(frequency)
ils.runway=runway and tostring(runway)or nil
table.insert(self.ils,ils)
return self
end
function ATIS:SetVOR(frequency)
self.vor=frequency
return self
end
function ATIS:AddNDBouter(frequency,runway)
local ndb={}
ndb.frequency=tonumber(frequency)
ndb.runway=runway and tostring(runway)or nil
table.insert(self.ndbouter,ndb)
return self
end
function ATIS:AddNDBinner(frequency,runway)
local ndb={}
ndb.frequency=tonumber(frequency)
ndb.runway=runway and tostring(runway)or nil
table.insert(self.ndbinner,ndb)
return self
end
function ATIS:SetTACAN(channel)
self.tacan=channel
return self
end
function ATIS:SetRSBN(channel)
self.rsbn=channel
return self
end
function ATIS:AddPRMG(channel,runway)
local ndb={}
ndb.frequency=tonumber(channel)
ndb.runway=runway and tostring(runway)or nil
table.insert(self.prmg,ndb)
return self
end
function ATIS:MarkRunways(markall)
local airbases=AIRBASE.GetAllAirbases()
for _,_airbase in pairs(airbases)do
local airbase=_airbase
if(not markall and airbase:GetName()==self.airbasename)or markall==true then
airbase:GetRunwayData(self.runwaym2t,true)
end
end
end
function ATIS:SetSRS(PathToSRS,Gender,Culture,Voice,Port,GoogleKey)
self.useSRS=true
local path=PathToSRS or MSRS.path
local gender=Gender or MSRS.gender
local culture=Culture or MSRS.culture
local voice=Voice or MSRS.voice
local port=Port or MSRS.port or 5002
self.msrs=MSRS:New(path,self.frequency,self.modulation)
self.msrs:SetGender(gender)
self.msrs:SetCulture(culture)
self.msrs:SetPort(port)
self.msrs:SetCoalition(self:GetCoalition())
self.msrs:SetLabel("ATIS")
if GoogleKey then
self.msrs:SetProviderOptionsGoogle(GoogleKey,GoogleKey)
self.msrs:SetProvider(MSRS.Provider.GOOGLE)
end
if(not GoogleKey)and self.msrs:GetProvider()==MSRS.Provider.GOOGLE then
voice=Voice or MSRS.poptions.gcloud.voice
end
self.msrs:SetVoice(voice)
self.msrs:SetCoordinate(self.airbase:GetCoordinate())
self.msrsQ=MSRSQUEUE:New("ATIS")
self.msrsQ:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers)
if self.dTQueueCheck<=10 then
self:SetQueueUpdateTime(90)
end
return self
end
function ATIS:SetSRSProvider(Provider)
self:T(self.lid.."SetSRSProvider")
if self.msrs then
self.msrs:SetProvider(Provider)
else
MESSAGE:New(self.lid.."Set up SRS first before trying to change the provider!",30,"ATIS"):ToAll():ToLog()
end
return self
end
function ATIS:SetQueueUpdateTime(TimeInterval)
self.dTQueueCheck=TimeInterval or 5
end
function ATIS:GetCoalition()
local coal=self.airbase and self.airbase:GetCoalition()or nil
return coal
end
function ATIS:onafterStart(From,Event,To)
self:T({From,Event,To})
self:T("Airbase category is "..self.airbase:GetAirbaseCategory())
if self.airbase:GetAirbaseCategory()==Airbase.Category.SHIP then
self:E(self.lid..string.format("ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT SHIPS.",self.airbasename))
return
end
if self.airbase:GetAirbaseCategory()==Airbase.Category.HELIPAD then
self:E(self.lid..string.format("EXPERIMENTAL: Starting ATIS for Helipad %s! SRS must be ON",self.airbasename))
self.ATISforFARPs=true
self.useSRS=true
end
if type(self.frequency)=="table"then
local frequency=table.concat(self.frequency,"/")
local modulation=self.modulation
if type(self.modulation)=="table"then
modulation=table.concat(self.modulation,"/")
end
self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %s MHz Modulation=%s",ATIS.version,self.airbasename,frequency,modulation))
else
self:I(self.lid..string.format("Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d",ATIS.version,self.airbasename,self.frequency,self.modulation))
end
if not self.useSRS then
self.radioqueue=RADIOQUEUE:New(self.frequency,self.modulation,string.format("ATIS %s",self.airbasename))
self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate())
self.radioqueue:SetSenderUnitName(self.relayunitname)
self.radioqueue:SetRadioPower(self.power)
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)
self:__Status(-2)
self:__CheckQueue(-3)
end
function ATIS:onafterStatus(From,Event,To)
self:T({From,Event,To})
local fsmstate=self:GetState()
local relayunitstatus="N/A"
if self.relayunitname then
local ru=UNIT:FindByName(self.relayunitname)
if ru then
relayunitstatus=tostring(ru:IsAlive())
end
end
local text=""
if type(self.frequency)=="table"then
local frequency=table.concat(self.frequency,"/")
local modulation=self.modulation
if type(self.modulation)=="table"then
modulation=table.concat(self.modulation,"/")
end
text=string.format("State %s: Freq=%s MHz %s",fsmstate,frequency,modulation)
else
text=string.format("State %s: Freq=%.3f MHz %s",fsmstate,self.frequency,UTILS.GetModulationName(self.modulation))
end
if self.useSRS then
text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s",tostring(self.msrs.path),tostring(self.msrs.port),tostring(self.msrs.gender),tostring(self.msrs.culture),tostring(self.msrs.voice))
else
text=text..string.format(", Relay unit=%s (alive=%s)",tostring(self.relayunitname),relayunitstatus)
end
self:T(self.lid..text)
if not self:Is("Stopped")then
self:__Status(60)
end
end
function ATIS:onafterCheckQueue(From,Event,To)
self:T({From,Event,To})
if not self:Is("Stopped")then
if self.useSRS then
self:Broadcast()
else
if#self.radioqueue.queue==0 then
self:T(self.lid..string.format("Radio queue empty. Repeating message."))
self:Broadcast()
else
self:T2(self.lid..string.format("Radio queue %d transmissions queued.",#self.radioqueue.queue))
end
end
self:__CheckQueue(math.abs(self.dTQueueCheck))
end
end
function ATIS:onafterBroadcast(From,Event,To)
self:T({From,Event,To})
local coord=self.airbase:GetCoordinate()
local height=coord:GetLandHeight()
local qfe=coord:GetPressure(height)
local qnh=coord:GetPressure(0)
if self.altimeterQNH then
local L=-0.0065
local R=8.31446
local g=9.80665
local M=0.0289644
local T0=coord:GetTemperature(0)+273.15
local TS=288.15
local q=qnh*100
local P=q*(1+L*height/T0)^(-g*M/(R*L))
local Q=P/(1+L*height/TS)^(-g*M/(R*L))
local A=(T0/L)*((P/q)^(((-R*L)/(g*M)))-1)
self:T2(self.lid..string.format("height=%.1f, A=%.1f, T0=%.1f, QFE=%.1f, QNH=%.1f, P=%.1f, Q=%.1f hPa = %.2f",height,A,T0-273.15,qfe,qnh,P/100,Q/100,UTILS.hPa2inHg(Q/100)))
qnh=Q/100
end
local mBarqnh=qnh
local mBarqfe=qfe
if self.PmmHg then
qfe=UTILS.hPa2mmHg(qfe)
qnh=UTILS.hPa2mmHg(qnh)
else
if not self.metric then
qfe=UTILS.hPa2inHg(qfe)
qnh=UTILS.hPa2inHg(qnh)
end
end
local QFE=UTILS.Split(string.format("%.2f",qfe),".")
local QNH=UTILS.Split(string.format("%.2f",qnh),".")
if self.PmmHg then
QFE=UTILS.Split(string.format("%.1f",qfe),".")
QNH=UTILS.Split(string.format("%.1f",qnh),".")
else
if self.metric then
QFE=UTILS.Split(string.format("%.1f",qfe),".")
QNH=UTILS.Split(string.format("%.1f",qnh),".")
end
end
local windFrom,windSpeed=coord:GetWind(height+10)
local magvar=self.magvar
if self.windtrue then
magvar=0
end
windFrom=windFrom-magvar
if windFrom<0 then
windFrom=windFrom+360
end
local WINDFROM=string.format("%03d",windFrom)
local WINDSPEED=string.format("%d",UTILS.MpsToKnots(windSpeed))
if WINDFROM=="000"then
WINDFROM="360"
end
if self.metric then
WINDSPEED=string.format("%d",windSpeed)
end
local runwayLanding,rwyLandingLeft
local runwayTakeoff,rwyTakeoffLeft
if self.airbase:GetAirbaseCategory()==Airbase.Category.HELIPAD then
runwayLanding,rwyLandingLeft="PAD 01",false
runwayTakeoff,rwyTakeoffLeft="PAD 02",false
else
runwayLanding,rwyLandingLeft=self:GetActiveRunway()
runwayTakeoff,rwyTakeoffLeft=self:GetActiveRunway(true)
end
local time=timer.getAbsTime()
if self.zuludiff then
time=time-self.zuludiff*60*60
else
time=time-UTILS.GMTToLocalTimeDifference()*60*60
end
if time<0 then
time=24*60*60+time
end
local clock=UTILS.SecondsToClock(time)
local zulu=UTILS.Split(clock,":")
local ZULU=string.format("%s%s",zulu[1],zulu[2])
local hours=self.gettext:GetEntry("TIME",self.locale)
if self.useSRS then
ZULU=string.format("%s %s",hours,zulu[1])
end
local NATO=ATIS.Alphabet[tonumber(zulu[1])+1]
self:T3(string.format("clock=%s",tostring(clock)))
self:T3(string.format("zulu1=%s",tostring(zulu[1])))
self:T3(string.format("zulu2=%s",tostring(zulu[2])))
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,":")
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,":")
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
temperature=UTILS.CelsiusToFahrenheit(temperature)
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,dustdens,static=self:GetMissionWeather()
local dust=false
local fog=false
if dustdens then
if UTILS.FeetToMeters(1500)>height+25 then
dust=true
visibility=math.min(visibility,dustdens)
end
else
local fvis=world.weather.getFogVisibilityDistance()
local fheight=world.weather.getFogThickness()
if fvis>0 and fheight>height+25 then
fog=true
visibility=math.min(visibility,fvis)
end
end
local VISIBILITY=""
if self.metric then
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(visibility))
if reportedviz>10 then
reportedviz=10
end
VISIBILITY=string.format("%d",reportedviz)
end
local cloudbase=clouds.base
local cloudceil=clouds.base+clouds.thickness
local clouddens=clouds.density
local cloudspreset=clouds.preset or"Nothing"
local precepitation=0
if cloudspreset:find("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
elseif cloudspreset:find("Preset12")then
clouddens=4
elseif cloudspreset:find("Preset13")then
clouddens=7
elseif cloudspreset:find("Preset14")then
clouddens=7
elseif cloudspreset:find("Preset15")then
clouddens=7
elseif cloudspreset:find("Preset16")then
clouddens=7
elseif cloudspreset:find("Preset17")then
clouddens=7
elseif cloudspreset:find("Preset18")then
clouddens=7
elseif cloudspreset:find("Preset19")then
clouddens=7
elseif cloudspreset:find("Preset20")then
clouddens=7
elseif cloudspreset:find("Preset21")then
clouddens=9
elseif cloudspreset:find("Preset22")then
clouddens=9
elseif cloudspreset:find("Preset23")then
clouddens=9
elseif cloudspreset:find("Preset24")then
clouddens=9
elseif cloudspreset:find("Preset25")then
clouddens=9
elseif cloudspreset:find("Preset26")then
clouddens=9
elseif cloudspreset:find("Preset27")then
clouddens=9
elseif cloudspreset:find("Preset1")then
clouddens=1
elseif cloudspreset:find("Preset2")then
clouddens=1
elseif cloudspreset:find("Preset3")then
clouddens=4
elseif cloudspreset:find("Preset4")then
clouddens=4
elseif cloudspreset:find("Preset5")then
clouddens=4
elseif cloudspreset:find("Preset6")then
clouddens=4
elseif cloudspreset:find("Preset7")then
clouddens=4
elseif cloudspreset:find("Preset8")then
clouddens=4
elseif cloudspreset:find("Preset9")then
clouddens=4
else
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))
if self.metric then
CLOUDBASE=string.format("%d",cloudbase)
CLOUDCEIL=string.format("%d",cloudceil)
end
local CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase))
local CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil))
if self.metric then
CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase)
CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil)
end
local CloudCover={}
CloudCover=self.Sound.CloudsNotAvailable
local CLOUDSsub=self.gettext:GetEntry("NOCLOUDINFO",self.locale)
if static then
if clouddens>=9 then
CloudCover=self.Sound.CloudsOvercast
CLOUDSsub=self.gettext:GetEntry("OVERCAST",self.locale)
elseif clouddens>=7 then
CloudCover=self.Sound.CloudsBroken
CLOUDSsub=self.gettext:GetEntry("BROKEN",self.locale)
elseif clouddens>=4 then
CloudCover=self.Sound.CloudsScattered
CLOUDSsub=self.gettext:GetEntry("SCATTERED",self.locale)
elseif clouddens>=1 then
CloudCover=self.Sound.CloudsFew
CLOUDSsub=self.gettext:GetEntry("FEWCLOUDS",self.locale)
else
CLOUDBASE=nil
CLOUDCEIL=nil
CloudCover=self.Sound.CloudsNo
CLOUDSsub=self.gettext:GetEntry("NOCLOUDS",self.locale)
end
end
local subtitle=""
subtitle=string.format("%s",self.airbasename)
if(not self.ATISforFARPs)and self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil
and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil
and self.airbasename:find("Field")==nil
then
subtitle=subtitle.." "..self.gettext:GetEntry("AIRPORT",self.locale)
end
if not self.useSRS then
self.radioqueue:NewTransmission(string.format("%s.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(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(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 and NorthPolar==false then
self:Transmission(self.Sound.SunriseAt,0.5,subtitle)
self.radioqueue:Number2Transmission(SUNRISE,nil,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 and NorthPolar==false then
self:Transmission(self.Sound.SunsetAt,0.5,subtitle)
self.radioqueue:Number2Transmission(SUNSET,nil,0.5)
self:Transmission(self.Sound.TimeLocal,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.useSRS then
WINDFROM=string.gsub(WINDFROM,".","%1 ")
end
if self.metric then
local windfrom=self.gettext:GetEntry("WINDFROMMS",self.locale)
subtitle=string.format(windfrom,WINDFROM,WINDSPEED)
else
local windfrom=self.gettext:GetEntry("WINDFROMKNOTS",self.locale)
subtitle=string.format(windfrom,WINDFROM,WINDSPEED)
end
if turbulence>0 then
subtitle=subtitle..", "..self.gettext:GetEntry("GUSTING",self.locale)
end
local _WIND=subtitle
if not self.useSRS then
self:Transmission(self.Sound.WindFrom,1.0,subtitle)
self.radioqueue:Number2Transmission(WINDFROM)
self:Transmission(self.Sound.At,0.2)
self.radioqueue:Number2Transmission(WINDSPEED)
if self.metric then
self:Transmission(self.Sound.MetersPerSecond,0.2)
else
self:Transmission(self.Sound.Knots,0.2)
end
if turbulence>0 then
self:Transmission(self.Sound.Gusting,0.2)
end
end
alltext=alltext..";\n"..subtitle
if self.metric then
local visi=self.gettext:GetEntry("VISIKM",self.locale)
subtitle=string.format(visi,VISIBILITY)
else
local visi=self.gettext:GetEntry("VISISM",self.locale)
subtitle=string.format(visi,VISIBILITY)
end
if not self.useSRS then
self:Transmission(self.Sound.Visibilty,1.0,subtitle)
self.radioqueue:Number2Transmission(VISIBILITY)
if self.metric then
self:Transmission(self.Sound.Kilometers,0.2)
else
self:Transmission(self.Sound.StatuteMiles,0.2)
end
end
alltext=alltext..";\n"..subtitle
subtitle=""
local wp=false
local wpsub=""
if precepitation==1 then
wp=true
wpsub=wpsub.." "..self.gettext:GetEntry("RAIN",self.locale)
elseif precepitation==2 then
if wp then
wpsub=wpsub..","
end
wpsub=wpsub.." "..self.gettext:GetEntry("TSTORM",self.locale)
wp=true
elseif precepitation==3 then
wpsub=wpsub.." "..self.gettext:GetEntry("SNOW",self.locale)
wp=true
elseif precepitation==4 then
wpsub=wpsub.." "..self.gettext:GetEntry("SSTROM",self.locale)
wp=true
end
if fog then
if wp then
wpsub=wpsub..","
end
wpsub=wpsub.." "..self.gettext:GetEntry("FOG",self.locale)
wp=true
end
if dust then
if wp then
wpsub=wpsub..","
end
wpsub=wpsub.." "..self.gettext:GetEntry("DUST",self.locale)
wp=true
end
if wp then
local phenos=self.gettext:GetEntry("PHENOMENA",self.locale)
subtitle=string.format("%s: %s",phenos,wpsub)
if not self.useSRS then
self:Transmission(self.Sound.WeatherPhenomena,1.0,subtitle)
if precepitation==1 then
self:Transmission(self.Sound.Rain,0.5)
elseif precepitation==2 then
self:Transmission(self.Sound.ThunderStorm,0.5)
elseif precepitation==3 then
self:Transmission(self.Sound.Snow,0.5)
elseif precepitation==4 then
self:Transmission(self.Sound.SnowStorm,0.5)
end
if fog then
self:Transmission(self.Sound.Fog,0.5)
end
if dust then
self:Transmission(self.Sound.Dust,0.5)
end
end
alltext=alltext..";\n"..subtitle
end
if not self.useSRS then
self:Transmission(CloudCover,1.0,CLOUDSsub)
end
if CLOUDBASE and static then
local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100)
local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100)
if self.metric then
local cloudbase=self.gettext:GetEntry("CLOUDBASEM",self.locale)
subtitle=string.format(cloudbase,cbase,cceil)
else
local cloudbase=self.gettext:GetEntry("CLOUDBASEFT",self.locale)
subtitle=string.format(cloudbase,cbase,cceil)
end
if not self.useSRS then
self:Transmission(self.Sound.CloudBase,1.0,subtitle)
if tonumber(CLOUDBASE1000)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE1000)
self:Transmission(self.Sound.Thousand,0.1)
end
if tonumber(CLOUDBASE0100)>0 then
self.radioqueue:Number2Transmission(CLOUDBASE0100)
self:Transmission(self.Sound.Hundred,0.1)
end
self:Transmission(self.Sound.CloudCeiling,0.5)
if tonumber(CLOUDCEIL1000)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL1000)
self:Transmission(self.Sound.Thousand,0.1)
end
if tonumber(CLOUDCEIL0100)>0 then
self.radioqueue:Number2Transmission(CLOUDCEIL0100)
self:Transmission(self.Sound.Hundred,0.1)
end
if self.metric then
self:Transmission(self.Sound.Meters,0.1)
else
self:Transmission(self.Sound.Feet,0.1)
end
end
end
alltext=alltext..";\n"..subtitle
subtitle=""
local temptext=self.gettext:GetEntry("TEMPERATURE",self.locale)
if self.TDegF then
if temperature<0 then
subtitle=string.format("%s -%s °F",temptext,TEMPERATURE)
else
subtitle=string.format("%s %s °F",temptext,TEMPERATURE)
end
else
if temperature<0 then
subtitle=string.format("%s -%s °C",temptext,TEMPERATURE)
else
subtitle=string.format("%s %s °C",temptext,TEMPERATURE)
end
end
local _TEMPERATURE=subtitle
if not self.useSRS then
self:Transmission(self.Sound.Temperature,1.0,subtitle)
if temperature<0 then
self:Transmission(self.Sound.Minus,0.2)
end
self.radioqueue:Number2Transmission(TEMPERATURE)
if self.TDegF then
self:Transmission(self.Sound.DegreesFahrenheit,0.2)
else
self:Transmission(self.Sound.DegreesCelsius,0.2)
end
end
alltext=alltext..";\n"..subtitle
local dewtext=self.gettext:GetEntry("DEWPOINT",self.locale)
if self.TDegF then
if dewpoint<0 then
subtitle=string.format("%s -%s °F",dewtext,DEWPOINT)
else
subtitle=string.format("%s %s °F",dewtext,DEWPOINT)
end
else
if dewpoint<0 then
subtitle=string.format("%s -%s °C",dewtext,DEWPOINT)
else
subtitle=string.format("%s %s °C",dewtext,DEWPOINT)
end
end
local _DEWPOINT=subtitle
if not self.useSRS then
self:Transmission(self.Sound.DewPoint,1.0,subtitle)
if dewpoint<0 then
self:Transmission(self.Sound.Minus,0.2)
end
self.radioqueue:Number2Transmission(DEWPOINT)
if self.TDegF then
self:Transmission(self.Sound.DegreesFahrenheit,0.2)
else
self:Transmission(self.Sound.DegreesCelsius,0.2)
end
end
alltext=alltext..";\n"..subtitle
local altim=self.gettext:GetEntry("ALTIMETER",self.locale)
if self.PmmHg then
if self.qnhonly then
subtitle=string.format("%s %s.%s mmHg",altim,QNH[1],QNH[2])
else
subtitle=string.format("%s: QNH %s.%s, QFE %s.%s mmHg",altim,QNH[1],QNH[2],QFE[1],QFE[2])
end
else
if self.metric then
if self.qnhonly then
subtitle=string.format("%s %s.%s hPa",altim,QNH[1],QNH[2])
else
subtitle=string.format("%s: QNH %s.%s, QFE %s.%s hPa",altim,QNH[1],QNH[2],QFE[1],QFE[2])
end
else
if self.qnhonly then
subtitle=string.format("%s %s.%s inHg",altim,QNH[1],QNH[2])
else
subtitle=string.format("%s: QNH %s.%s, QFE %s.%s inHg",altim,QNH[1],QNH[2],QFE[1],QFE[2])
end
end
end
if self.ReportmBar and not self.metric then
if self.qnhonly then
subtitle=string.format("%s;\n%s %d hPa",subtitle,altim,mBarqnh)
else
subtitle=string.format("%s;\n%s: QNH %d, QFE %d hPa",subtitle,altim,mBarqnh,mBarqfe)
end
end
local _ALTIMETER=subtitle
if not self.useSRS then
self:Transmission(self.Sound.Altimeter,1.0,subtitle)
if not self.qnhonly then
self:Transmission(self.Sound.QNH,0.5)
end
self.radioqueue:Number2Transmission(QNH[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then
self:Transmission(self.Sound.Decimal,0.2)
end
self.radioqueue:Number2Transmission(QNH[2])
if not self.qnhonly then
self:Transmission(self.Sound.QFE,0.75)
self.radioqueue:Number2Transmission(QFE[1])
if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then
self:Transmission(self.Sound.Decimal,0.2)
end
self.radioqueue:Number2Transmission(QFE[2])
end
if self.PmmHg then
self:Transmission(self.Sound.MillimetersOfMercury,0.1)
else
if self.metric then
self:Transmission(self.Sound.HectoPascal,0.1)
else
self:Transmission(self.Sound.InchesOfMercury,0.1)
end
end
end
alltext=alltext..";\n"..subtitle
local _RUNACT
if not self.ATISforFARPs then
local subtitle=""
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
subtitle=subtitle.." "..self.gettext:GetEntry("LEFT",self.locale)
elseif rwyLandingLeft==false then
subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale)
end
alltext=alltext..";\n"..subtitle
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)
subtitle=string.format("%s %s",actrun,runwayTakeoff)
if rwyTakeoffLeft==true then
subtitle=subtitle.." "..self.gettext:GetEntry("LEFT",self.locale)
elseif rwyTakeoffLeft==false then
subtitle=subtitle.." "..self.gettext:GetEntry("RIGHT",self.locale)
end
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
alltext=alltext..";\n"..subtitle
if self.rwylength then
local runact=self.airbase:GetActiveRunway(self.runwaym2t)
local length=runact.length
if not self.metric then
length=UTILS.MetersToFeet(length)
end
local L1000,L0100=self:_GetThousandsAndHundreds(length)
local rwyl=self.gettext:GetEntry("RWYLENGTH",self.locale)
local meters=self.gettext:GetEntry("METERS",self.locale)
local feet=self.gettext:GetEntry("FEET",self.locale)
local subtitle=string.format("%s %d",rwyl,length)
if self.metric then
subtitle=subtitle.." "..meters
else
subtitle=subtitle.." "..feet
end
if not self.useSRS then
self:Transmission(self.Sound.RunwayLength,1.0,subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(self.Sound.Thousand,0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(self.Sound.Hundred,0.1)
end
if self.metric then
self:Transmission(self.Sound.Meters,0.1)
else
self:Transmission(self.Sound.Feet,0.1)
end
end
alltext=alltext..";\n"..subtitle
end
end
if self.elevation then
local elev=self.gettext:GetEntry("ELEVATION",self.locale)
local meters=self.gettext:GetEntry("METERS",self.locale)
local feet=self.gettext:GetEntry("FEET",self.locale)
local elevation=self.airbase:GetHeight()
if not self.metric then
elevation=UTILS.MetersToFeet(elevation)
end
local L1000,L0100=self:_GetThousandsAndHundreds(elevation)
local subtitle=string.format("%s %d",elev,elevation)
if self.metric then
subtitle=subtitle.." "..meters
else
subtitle=subtitle.." "..feet
end
if not self.useSRS then
self:Transmission(self.Sound.Elevation,1.0,subtitle)
if tonumber(L1000)>0 then
self.radioqueue:Number2Transmission(L1000)
self:Transmission(self.Sound.Thousand,0.1)
end
if tonumber(L0100)>0 then
self.radioqueue:Number2Transmission(L0100)
self:Transmission(self.Sound.Hundred,0.1)
end
if self.metric then
self:Transmission(self.Sound.Meters,0.1)
else
self:Transmission(self.Sound.Feet,0.1)
end
end
alltext=alltext..";\n"..subtitle
end
if self.towerfrequency then
local freqs=""
for i,freq in pairs(self.towerfrequency)do
freqs=freqs..string.format("%.3f MHz",freq)
if i<#self.towerfrequency then
freqs=freqs..", "
end
end
local twrfrq=self.gettext:GetEntry("TOWERFREQ",self.locale)
subtitle=string.format("%s %s",twrfrq,freqs)
if not self.useSRS then
self:Transmission(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(self.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(self.Sound.MegaHertz,0.2)
end
end
alltext=alltext..";\n"..subtitle
end
local ils=self:GetNavPoint(self.ils,runwayLanding,rwyLandingLeft)
if ils then
local ilstxt=self.gettext:GetEntry("ILSFREQ",self.locale)
subtitle=string.format("%s %.2f MHz",ilstxt,ils.frequency)
if not self.useSRS then
self:Transmission(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(self.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(self.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
local ndb=self:GetNavPoint(self.ndbouter,runwayLanding,rwyLandingLeft)
if ndb then
local ndbtxt=self.gettext:GetEntry("OUTERNDB",self.locale)
subtitle=string.format("%s %.2f MHz",ndbtxt,ndb.frequency)
if not self.useSRS then
self:Transmission(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(self.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(self.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
local ndb=self:GetNavPoint(self.ndbinner,runwayLanding,rwyLandingLeft)
if ndb then
local ndbtxt=self.gettext:GetEntry("INNERNDB",self.locale)
subtitle=string.format("%s %.2f MHz",ndbtxt,ndb.frequency)
if not self.useSRS then
self:Transmission(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(self.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(self.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.vor then
local vortxt=self.gettext:GetEntry("VORFREQ",self.locale)
local vorttstxt=self.gettext:GetEntry("VORFREQTTS",self.locale)
subtitle=string.format("%s %.2f MHz",vortxt,self.vor)
if self.useSRS then
subtitle=string.format("%s %.2f MHz",vorttstxt,self.vor)
end
if not self.useSRS then
self:Transmission(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(self.Sound.Decimal,0.2)
self.radioqueue:Number2Transmission(f[2])
end
self:Transmission(self.Sound.MegaHertz,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.tacan then
local tactxt=self.gettext:GetEntry("TACANCH",self.locale)
subtitle=string.format(tactxt,self.tacan)
if not self.useSRS then
self:Transmission(self.Sound.TACANChannel,1.0,subtitle)
self.radioqueue:Number2Transmission(tostring(self.tacan),nil,0.2)
self.radioqueue:NewTransmission("Xray.ogg",0.75,self.soundpathNato,nil,0.2)
end
alltext=alltext..";\n"..subtitle
end
if self.rsbn then
local rsbntxt=self.gettext:GetEntry("RSBNCH",self.locale)
subtitle=string.format("%s %d",rsbntxt,self.rsbn)
if not self.useSRS then
self:Transmission(self.Sound.RSBNChannel,1.0,subtitle)
self.radioqueue:Number2Transmission(tostring(self.rsbn),nil,0.2)
end
alltext=alltext..";\n"..subtitle
end
local ndb=self:GetNavPoint(self.prmg,runwayLanding,rwyLandingLeft)
if ndb then
local prmtxt=self.gettext:GetEntry("PRMGCH",self.locale)
subtitle=string.format("%s %d",prmtxt,ndb.frequency)
if not self.useSRS then
self:Transmission(self.Sound.PRMGChannel,1.0,subtitle)
self.radioqueue:Number2Transmission(tostring(ndb.frequency),nil,0.5)
end
alltext=alltext..";\n"..subtitle
end
if self.useSRS and self.AdditionalInformation then
alltext=alltext..";\n"..self.AdditionalInformation
end
local advtxt=self.gettext:GetEntry("ADVISE",self.locale)
subtitle=string.format("%s %s",advtxt,NATO)
if not self.useSRS then
self:Transmission(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)
if self.usemarker then
self:UpdateMarker(_INFORMATION,_RUNACT,_WIND,_ALTIMETER,_TEMPERATURE)
end
end
function ATIS:onafterReport(From,Event,To,Text)
self:T({From,Event,To})
self:T(self.lid..string.format("Report:\n%s",Text))
if self.useSRS and self.msrs then
local text=string.gsub(Text,"[\r\n]","")
local statute=self.gettext:GetEntry("STATUTE",self.locale)
local degc=self.gettext:GetEntry("DEGREES",self.locale)
local degf=self.gettext:GetEntry("FAHRENHEIT",self.locale)
local inhg=self.gettext:GetEntry("INCHHG",self.locale)
local mmhg=self.gettext:GetEntry("MMHG",self.locale)
local hpa=self.gettext:GetEntry("HECTO",self.locale)
local emes=self.gettext:GetEntry("METERSPER",self.locale)
local tacan=self.gettext:GetEntry("TACAN",self.locale)
local farp=self.gettext:GetEntry("FARP",self.locale)
local text=string.gsub(text,"SM",statute)
text=string.gsub(text,"°C",degc)
text=string.gsub(text,"°F",degf)
text=string.gsub(text,"inHg",inhg)
text=string.gsub(text,"mmHg",mmhg)
text=string.gsub(text,"hPa",hpa)
text=string.gsub(text,"m/s",emes)
text=string.gsub(text,"TACAN",tacan)
text=string.gsub(text,"FARP",farp)
local delimiter=self.gettext:GetEntry("DELIMITER",self.locale)
if string.lower(self.locale)~="en"then
text=string.gsub(text,"(%d+)(%.)(%d+)","%1 "..delimiter.." %3")
end
local text=string.gsub(text,";"," . ")
self:T("SRS TTS: "..text)
local duration=MSRS.getSpeechTime(text,0.95)
self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2)
self.SRSText=text
end
end
function ATIS:OnEventBaseCaptured(EventData)
if EventData and EventData.Place then
local airbase=EventData.Place
if EventData.PlaceName==self.airbasename then
local NewCoalitionAirbase=airbase:GetCoalition()
if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then
self.msrs:SetCoalition(NewCoalitionAirbase)
end
end
end
end
function ATIS:UpdateMarker(information,runact,wind,altimeter,temperature)
if self.markerid then
self.airbase:GetCoordinate():RemoveMark(self.markerid)
end
local text=""
if type(self.frequency)=="table"then
local frequency=table.concat(self.frequency,"/")
local modulation=self.modulation
if type(modulation)=="table"then
modulation=table.concat(self.modulation,"/")
end
text=string.format("ATIS on %s %s, %s:\n",tostring(frequency),tostring(modulation),tostring(information))
else
text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information))
end
text=text..string.format("%s\n",tostring(runact))
text=text..string.format("%s\n",tostring(wind))
text=text..string.format("%s\n",tostring(altimeter))
text=text..string.format("%s",tostring(temperature))
self.markerid=self.airbase:GetCoordinate():MarkToAll(text,true)
return self.markerid
end
function ATIS:GetActiveRunway(Takeoff)
local runway=nil
if Takeoff then
runway=self.airbase:GetActiveRunwayTakeoff()
else
runway=self.airbase:GetActiveRunwayLanding()
end
if runway then
return runway.name,runway.isLeft
else
return nil,nil
end
end
function ATIS:GetMagneticRunway(windfrom)
local diffmin=nil
local runway=nil
for _,heading in pairs(self.runwaymag)do
local hdg=self:GetRunwayWithoutLR(heading)
local diff=UTILS.HdgDiff(windfrom,tonumber(hdg)*10)
if diffmin==nil or diff<diffmin then
diffmin=diff
runway=hdg
end
end
return runway
end
function ATIS:GetNavPoint(navpoints,runway,left)
for _,_nav in pairs(navpoints or{})do
local nav=_nav
if nav.runway==nil then
return nav
else
local navy=tonumber(self:GetRunwayWithoutLR(nav.runway))*10
local rwyy=tonumber(self:GetRunwayWithoutLR(runway))*10
local navL=self:GetRunwayLR(nav.runway)
local hdgD=UTILS.HdgDiff(navy,rwyy)
if hdgD<=15 then
if navL==nil or(navL==true and left==true)or(navL==false and left==false)then
return nav
end
end
end
end
return nil
end
function ATIS:GetRunwayWithoutLR(runway)
local rwywo=runway:gsub("%D+","")
return rwywo
end
function ATIS:GetRunwayLR(runway)
local rwyL=runway:lower():find("l")
local rwyR=runway:lower():find("r")
if rwyL then
return true
elseif rwyR then
return false
else
return nil
end
end
function ATIS:Transmission(sound,interval,subtitle,path)
self.radioqueue:NewTransmission(sound.filename,sound.duration,path or self.soundpath,nil,interval,subtitle,self.subduration)
end
function ATIS:SoundCheck()
for _,_sound in pairs(ATIS.Sound)do
local sound=_sound
local subtitle=string.format("Playing sound file %s, duration %.2f sec",sound.filename,sound.duration)
self:Transmission(sound,nil,subtitle)
MESSAGE:New(subtitle,5,"ATIS"):ToAll()
end
end
function ATIS:GetMissionWeather()
local weather=env.mission.weather
local clouds=weather.clouds
local static=weather.atmosphere_type==0
local visibility=weather.visibility.distance
local turbulence=weather.groundTurbulence
local dust=nil
if weather.enable_dust==true then
dust=weather.dust_density
end
self:T("FF weather:")
self:T({clouds=clouds})
self:T({visibility=visibility})
self:T({turbulence=turbulence})
self:T({dust=dust})
self:T({static=static})
return clouds,visibility,turbulence,dust,static
end
function ATIS:_GetThousandsAndHundreds(n)
local N=UTILS.Round(n/1000,1)
local S=UTILS.Split(string.format("%.1f",N),".")
local t=S[1]
local h=S[2]
return t,h
end
do
CTLD_CARGO={
ClassName="CTLD_CARGO",
ID=0,
Name="none",
Templates={},
CargoType="none",
HasBeenMoved=false,
LoadDirectly=false,
CratesNeeded=0,
Positionable=nil,
HasBeenDropped=false,
PerCrateMass=0,
Stock=nil,
Stock0=nil,
Mark=nil,
DontShowInMenu=false,
Location=nil,
}
CTLD_CARGO.Enum={
VEHICLE="Vehicle",
TROOPS="Troops",
FOB="FOB",
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())
self:T({ID,Name,Templates,Sorte,HasBeenMoved,LoadDirectly,CratesNeeded,Positionable,Dropped})
self.ID=ID or math.random(100000,1000000)
self.Name=Name or"none"
self.Templates=Templates or{}
self.CargoType=Sorte or"type"
self.HasBeenMoved=HasBeenMoved or false
self.LoadDirectly=LoadDirectly or false
self.CratesNeeded=CratesNeeded or 0
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"
self.StaticShape=nil
self.TypeNames=nil
self.StaticCategory="Cargos"
if type(Location)=="string"then
Location=ZONE:New(Location)
end
self.Location=Location
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
function CTLD_CARGO:GetID()
return self.ID
end
function CTLD_CARGO:GetSubCat()
return self.Subcategory
end
function CTLD_CARGO:GetMass()
return self.PerCrateMass
end
function CTLD_CARGO:GetName()
return self.Name
end
function CTLD_CARGO:GetTemplates()
return self.Templates
end
function CTLD_CARGO:HasMoved()
return self.HasBeenMoved
end
function CTLD_CARGO:WasDropped()
return self.HasBeenDropped
end
function CTLD_CARGO:CanLoadDirectly()
return self.LoadDirectly
end
function CTLD_CARGO:GetCratesNeeded()
return self.CratesNeeded
end
function CTLD_CARGO:GetType()
return self.CargoType
end
function CTLD_CARGO:GetPositionable()
return self.Positionable
end
function CTLD_CARGO:SetHasMoved(moved)
self.HasBeenMoved=moved or false
end
function CTLD_CARGO:Isloaded()
if self.HasBeenMoved and not self:WasDropped()then
return true
else
return false
end
end
function CTLD_CARGO:SetWasDropped(dropped)
self.HasBeenDropped=dropped or false
end
function CTLD_CARGO:GetStock()
if self.Stock then
return self.Stock
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
self.Stock=self.Stock+number
end
return self
end
function CTLD_CARGO:RemoveStock(Number)
if self.Stock then
local number=Number or 1
self.Stock=self.Stock-number
if self.Stock<0 then self.Stock=0 end
end
return self
end
function CTLD_CARGO:SetStock(Number)
self.Stock=Number
return self
end
function CTLD_CARGO:IsRepair()
if self.CargoType=="Repair"then
return true
else
return false
end
end
function CTLD_CARGO:IsStatic()
if self.CargoType=="Static"then
return true
else
return false
end
end
function CTLD_CARGO:AddMark(Mark)
self.Mark=Mark
return self
end
function CTLD_CARGO:GetMark(Mark)
return self.Mark
end
function CTLD_CARGO:WipeMark()
self.Mark=nil
return self
end
function CTLD_CARGO:GetNetMass()
return self.CratesNeeded*self.PerCrateMass
end
end
do
CTLD_ENGINEERING={
ClassName="CTLD_ENGINEERING",
lid="",
Name="none",
Group=nil,
Unit=nil,
HeliGroup=nil,
HeliUnit=nil,
State="",
}
CTLD_ENGINEERING.Version="0.0.3"
function CTLD_ENGINEERING:New(Name,GroupName,HeliGroup,HeliUnit)
local self=BASE:Inherit(self,BASE:New())
self.Name=Name or"Engineer Squad"
self.Group=GROUP:FindByName(GroupName)
self.Unit=self.Group:GetUnit(1)
self.HeliGroup=HeliGroup
self.HeliUnit=HeliUnit
self.currwpt=nil
self.lid=string.format("%s (%s) | ",self.Name,self.Version)
self.State="Stopped"
self.marktimer=300
self:Start()
local parent=self:GetParent(self)
return self
end
function CTLD_ENGINEERING:SetStatus(State)
self.State=State
return self
end
function CTLD_ENGINEERING:GetStatus()
return self.State
end
function CTLD_ENGINEERING:IsStatus(State)
return self.State==State
end
function CTLD_ENGINEERING:IsNotStatus(State)
return self.State~=State
end
function CTLD_ENGINEERING:Start()
self:T(self.lid.."Start")
self:SetStatus("Running")
return self
end
function CTLD_ENGINEERING:Stop()
self:T(self.lid.."Stop")
self:SetStatus("Stopped")
return self
end
function CTLD_ENGINEERING:Build()
self:T(self.lid.."Build")
self:SetStatus("Building")
return self
end
function CTLD_ENGINEERING:Done()
self:T(self.lid.."Done")
local grp=self.Group
grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond")
self:SetStatus("Running")
return self
end
function CTLD_ENGINEERING:Search(crates,number)
self:T(self.lid.."Search")
self:SetStatus("Searching")
local dist=self.distance
local group=self.Group
local ctable={}
local ind=0
if number>0 then
for _,_cargo in pairs(crates)do
local cgotype=_cargo:GetType()
if _cargo:WasDropped()and cgotype~=CTLD_CARGO.Enum.STATIC then
local ok=false
local chalk=_cargo:GetMark()
if chalk==nil then
ok=true
else
local tag=chalk.tag or"none"
local timestamp=chalk.timestamp or 0
local gone=timer.getAbsTime()-timestamp
if gone>=self.marktimer then
ok=true
_cargo:WipeMark()
end
end
if ok then
local chalk={}
chalk.tag="Engineers"
chalk.timestamp=timer.getAbsTime()
_cargo:AddMark(chalk)
ind=ind+1
table.insert(ctable,ind,_cargo)
end
end
end
end
if ind>0 then
local crate=ctable[1]
local static=crate:GetPositionable()
local crate_pos=static:GetCoordinate()
local gpos=group:GetCoordinate()
local distance=self:_GetDistance(gpos,crate_pos)
self:T(string.format("%s Distance to crate: %d",self.lid,distance))
if distance>30 and distance~=-1 and self:IsStatus("Searching")then
group:RouteGroundTo(crate_pos,15,"Line abreast",1)
self.currwpt=crate_pos
self:Move()
elseif distance<=30 and distance~=-1 then
self:Arrive()
end
else
self:T(self.lid.."No crates in reach!")
end
return self
end
function CTLD_ENGINEERING:Move()
self:T(self.lid.."Move")
self:SetStatus("Moving")
local group=self.Group
local tgtpos=self.currwpt
local gpos=group:GetCoordinate()
local distance=self:_GetDistance(gpos,tgtpos)
self:T(string.format("%s Distance remaining: %d",self.lid,distance))
if distance<=30 and distance~=-1 then
self:Arrive()
end
return self
end
function CTLD_ENGINEERING:Arrive()
self:T(self.lid.."Arrive")
self:SetStatus("Arrived")
self.currwpt=nil
local Grp=self.Group
Grp:RouteStop()
return self
end
function CTLD_ENGINEERING:_GetDistance(_point1,_point2)
self:T(self.lid.." _GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
end
do
CTLD={
ClassName="CTLD",
verbose=0,
lid="",
coalition=1,
coalitiontxt="blue",
PilotGroups={},
CtldUnits={},
FreeVHFFrequencies={},
FreeUHFFrequencies={},
FreeFMFrequencies={},
CargoCounter=0,
Cargo_Troops={},
Cargo_Crates={},
Loaded_Cargo={},
Spawned_Crates={},
Spawned_Cargo={},
CrateDistance=35,
PackDistance=35,
debug=false,
wpZones={},
dropOffZones={},
pickupZones={},
DynamicCargo={},
ChinookTroopCircleRadius=5,
TroopUnloadDistGround=5,
TroopUnloadDistGroundHerc=25,
TroopUnloadDistGroundHook=15,
TroopUnloadDistHoverHook=5,
TroopUnloadDistHover=1.5,
UserSetGroup=nil,
LoadedGroupsTable={},
keeploadtable=true,
allowCATransport=false,
}
CTLD.RadioModulation={
AM=0,
FM=1,
}
CTLD.CargoZoneType={
LOAD="load",
DROP="drop",
MOVE="move",
SHIP="ship",
BEACON="beacon",
}
CTLD.UnitTypeCapabilities={
["SA342Mistral"]={type="SA342Mistral",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400},
["SA342L"]={type="SA342L",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400},
["SA342M"]={type="SA342M",crates=false,troops=true,cratelimit=0,trooplimit=4,length=12,cargoweightlimit=400},
["SA342Minigun"]={type="SA342Minigun",crates=false,troops=true,cratelimit=0,trooplimit=2,length=12,cargoweightlimit=400},
["UH-1H"]={type="UH-1H",crates=true,troops=true,cratelimit=1,trooplimit=8,length=15,cargoweightlimit=700},
["Mi-8MTV2"]={type="Mi-8MTV2",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000},
["Mi-8MT"]={type="Mi-8MT",crates=true,troops=true,cratelimit=2,trooplimit=12,length=15,cargoweightlimit=3000},
["Ka-50"]={type="Ka-50",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0},
["Ka-50_3"]={type="Ka-50_3",crates=false,troops=false,cratelimit=0,trooplimit=0,length=15,cargoweightlimit=0},
["Mi-24P"]={type="Mi-24P",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700},
["Mi-24V"]={type="Mi-24V",crates=true,troops=true,cratelimit=2,trooplimit=8,length=18,cargoweightlimit=700},
["Hercules"]={type="Hercules",crates=true,troops=true,cratelimit=7,trooplimit=64,length=25,cargoweightlimit=19000},
["UH-60L"]={type="UH-60L",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500},
["MH-60R"]={type="MH-60R",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500},
["SH-60B"]={type="SH-60B",crates=true,troops=true,cratelimit=2,trooplimit=20,length=16,cargoweightlimit=3500},
["AH-64D_BLK_II"]={type="AH-64D_BLK_II",crates=false,troops=true,cratelimit=0,trooplimit=2,length=17,cargoweightlimit=200},
["Bronco-OV-10A"]={type="Bronco-OV-10A",crates=false,troops=true,cratelimit=0,trooplimit=5,length=13,cargoweightlimit=1450},
["OH-6A"]={type="OH-6A",crates=false,troops=true,cratelimit=0,trooplimit=4,length=7,cargoweightlimit=550},
["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.FixedWingTypes={
["Hercules"]="Hercules",
["Bronco"]="Bronco",
["Mosquito"]="Mosquito",
}
CTLD.version="1.3.34"
function CTLD:New(Coalition,Prefixes,Alias)
local self=BASE:Inherit(self,FSM:New())
BASE:T({Coalition,Prefixes,Alias})
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in CTLD!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="UNHCR"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="Red CTLD"
elseif self.coalition==coalition.side.BLUE then
self.alias="Blue CTLD"
end
end
end
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","TroopsPickedUp","*")
self:AddTransition("*","TroopsExtracted","*")
self:AddTransition("*","CratesPickedUp","*")
self:AddTransition("*","TroopsDeployed","*")
self:AddTransition("*","TroopsRTB","*")
self:AddTransition("*","CratesDropped","*")
self:AddTransition("*","CratesBuild","*")
self:AddTransition("*","CratesRepaired","*")
self:AddTransition("*","CratesBuildStarted","*")
self:AddTransition("*","CratesRepairStarted","*")
self:AddTransition("*","HelicopterLost","*")
self:AddTransition("*","Load","*")
self:AddTransition("*","Loaded","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
self.PilotGroups={}
self.CtldUnits={}
self.FreeVHFFrequencies={}
self.FreeUHFFrequencies={}
self.FreeFMFrequencies={}
self.UsedVHFFrequencies={}
self.UsedUHFFrequencies={}
self.UsedFMFrequencies={}
self.RadioSound="beacon.ogg"
self.RadioSoundFC3="beacon.ogg"
self.RadioPath="l10n/DEFAULT/"
self.pickupZones={}
self.dropOffZones={}
self.wpZones={}
self.shipZones={}
self.droppedBeacons={}
self.droppedbeaconref={}
self.droppedbeacontimeout=600
self.useprecisecoordloads=true
self.Cargo_Crates={}
self.Cargo_Troops={}
self.Cargo_Statics={}
self.Loaded_Cargo={}
self.Spawned_Crates={}
self.Spawned_Cargo={}
self.MenusDone={}
self.DroppedTroops={}
self.DroppedCrates={}
self.CargoCounter=0
self.CrateCounter=0
self.TroopCounter=0
self.Engineers=0
self.EngineersInField={}
self.EngineerSearch=2000
self.nobuildmenu=false
self.CrateDistance=35
self.PackDistance=35
self.ExtractFactor=3.33
self.prefixes=Prefixes or{"Cargoheli"}
self.useprefix=true
self.maximumHoverHeight=15
self.minimumHoverHeight=4
self.forcehoverload=true
self.hoverautoloading=true
self.dropcratesanywhere=false
self.dropAsCargoCrate=false
self.smokedistance=2000
self.movetroopstowpzone=true
self.movetroopsdistance=5000
self.troopdropzoneradius=100
self.enableHercules=false
self.enableFixedWing=false
self.FixedMinAngels=165
self.FixedMaxAngels=2000
self.FixedMaxSpeed=77
self.suppressmessages=false
self.repairtime=300
self.buildtime=300
self.placeCratesAhead=false
self.cratecountry=country.id.GERMANY
self.pilotmustopendoors=false
if self.coalition==coalition.side.RED then
self.cratecountry=country.id.RUSSIA
end
self.enableLoadSave=false
self.filepath=nil
self.saveinterval=600
self.eventoninject=true
self.keeploadtable=true
self.LoadedGroupsTable={}
self.usesubcats=false
self.subcats={}
self.subcatsTroop={}
self.showstockinmenuitems=false
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.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()
return self
end
function CTLD:_GetUnitCapabilities(Unit)
self:T(self.lid.." _GetUnitCapabilities")
local _unit=Unit
local unittype=_unit:GetTypeName()
local capabilities=self.UnitTypeCapabilities[unittype]
if not capabilities or capabilities=={}then
capabilities={}
capabilities.troops=false
capabilities.crates=false
capabilities.cratelimit=0
capabilities.trooplimit=0
capabilities.type="generic"
capabilities.length=20
capabilities.cargoweightlimit=0
end
return capabilities
end
function CTLD:AllowCATransport(OnOff,ClientSet)
self.allowCATransport=OnOff
self.CATransportSet=ClientSet
return self
end
function CTLD:_GenerateUHFrequencies()
self:T(self.lid.." _GenerateUHFrequencies")
self.FreeUHFFrequencies={}
self.FreeUHFFrequencies=UTILS.GenerateUHFrequencies(243,320)
return self
end
function CTLD:_GenerateFMFrequencies()
self:T(self.lid.." _GenerateFMrequencies")
self.FreeFMFrequencies={}
self.FreeFMFrequencies=UTILS.GenerateFMFrequencies()
return self
end
function CTLD:_GenerateVHFrequencies()
self:T(self.lid.." _GenerateVHFrequencies")
self.FreeVHFFrequencies={}
self.UsedVHFFrequencies={}
self.FreeVHFFrequencies=UTILS.GenerateVHFrequencies()
return self
end
function CTLD:SetTroopDropZoneRadius(Radius)
self:T(self.lid.." SetTroopDropZoneRadius")
local tradius=Radius or 100
if tradius<25 then tradius=25 end
self.troopdropzoneradius=tradius
return self
end
function CTLD:AddPlayerTask(PlayerTask)
self:T(self.lid.." AddPlayerTask")
if not self.PlayerTaskQueue then
self.PlayerTaskQueue=FIFO:New()
end
self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr)
return self
end
function CTLD:_EventHandler(EventData)
self:T(string.format("%s Event = %d",self.lid,EventData.id))
local event=EventData
if event.id==EVENTS.PlayerEnterAircraft or event.id==EVENTS.PlayerEnterUnit then
local _coalition=event.IniCoalition
if _coalition~=self.coalition then
return
end
local unitname=event.IniUnitName or"none"
self.MenusDone[unitname]=nil
local _unit=event.IniUnit
local _group=event.IniGroup
if _unit:IsHelicopter()or _group:IsHelicopter()then
local unitname=event.IniUnitName or"none"
self.Loaded_Cargo[unitname]=nil
self:_RefreshF10Menus()
end
if self: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.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)
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
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)
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})
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:_SendMessage(Text,Time,Clearscreen,Group)
self:T(self.lid.." _SendMessage")
if not self.suppressmessages then
local m=MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group)
end
return self
end
function CTLD:_FindTroopsCargoObject(Name)
self:T(self.lid.." _FindTroopsCargoObject")
local cargo=nil
for _,_cargo in pairs(self.Cargo_Troops)do
local cargo=_cargo
if cargo.Name==Name then
return cargo
end
end
return nil
end
function CTLD:_FindCratesCargoObject(Name)
self:T(self.lid.." _FindCratesCargoObject")
local cargo=nil
for _,_cargo in pairs(self.Cargo_Crates)do
local cargo=_cargo
if cargo.Name==Name then
return cargo
end
end
return nil
end
function CTLD: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"
if Unit and Unit:IsAlive()then
local cargo=self:_FindTroopsCargoObject(name)
local group=Unit:GetGroup()
if cargo then
self:_LoadTroops(group,Unit,cargo,true)
else
self:E(self.lid.." Troops preload - Cargo Object "..name.." not found!")
end
end
return self
end
function CTLD:_PreloadCrates(Group,Unit,Cargo,NumberOfCrates)
local group=Group
local unit=Unit
local unitname=unit:GetName()
local unittype=unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local cancrates=capabilities.crates
local cratelimit=capabilities.cratelimit
if not cancrates then
self:_SendMessage("Sorry this chopper cannot carry crates!",10,false,Group)
return self
else
local numberonboard=0
local massonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Cratesloaded or 0
massonboard=self:_GetUnitCargoMass(Unit)
else
loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
end
local crate=Cargo
local numbercrates=NumberOfCrates or crate:GetCratesNeeded()
for i=1,numbercrates do
loaded.Cratesloaded=loaded.Cratesloaded+1
crate:SetHasMoved(true)
crate:SetWasDropped(false)
table.insert(loaded.Cargo,crate)
crate.Positionable=nil
self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,false,Group)
self.Loaded_Cargo[unitname]=loaded
self:_UpdateUnitCargoMass(Unit)
end
end
return self
end
function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates)
self:T(self.lid.." PreloadCrates")
local name=Cratesname or"Unknown"
if Unit and Unit:IsAlive()then
local cargo=self:_FindCratesCargoObject(name)
local group=Unit:GetGroup()
if cargo then
self:_PreloadCrates(group,Unit,cargo,NumberOfCrates)
else
self:E(self.lid.." Crates preload - Cargo Object "..name.." not found!")
end
end
return self
end
function CTLD:_LoadTroops(Group,Unit,Cargotype,Inject)
self:T(self.lid.." _LoadTroops")
local instock=Cargotype:GetStock()
local cgoname=Cargotype:GetName()
local cgotype=Cargotype:GetType()
local cgonetmass=Cargotype:GetNetMass()
local maxloadable=self:_GetMaxLoadableMass(Unit)
if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 and not Inject then
self:_SendMessage(string.format("Sorry, all %s are gone!",cgoname),10,false,Group)
return self
end
local grounded=not self:IsUnitInAir(Unit)
local hoverload=self:CanHoverLoad(Unit)
local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
if not Inject then
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group)
if not self.debug then return self end
elseif not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!",10,false,Group)
if not self.debug then return self end
elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
self:_SendMessage("You need to open the door(s) to load troops!",10,false,Group)
if not self.debug then return self end
end
end
local group=Group
local unit=Unit
local unitname=unit:GetName()
local cargotype=Cargotype
local cratename=cargotype:GetName()
local unittype=unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local cantroops=capabilities.troops
local trooplimit=capabilities.trooplimit
local troopsize=cargotype:GetCratesNeeded()
local numberonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Troopsloaded or 0
else
loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
end
if troopsize+numberonboard>trooplimit then
self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group)
return
elseif maxloadable<cgonetmass then
self:_SendMessage("Sorry, that\'s too heavy to load!",10,false,Group)
return
else
self.CargoCounter=self.CargoCounter+1
local loadcargotype=CTLD_CARGO:New(self.CargoCounter,Cargotype.Name,Cargotype.Templates,cgotype,true,true,Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass)
self:T({cargotype=loadcargotype})
loaded.Troopsloaded=loaded.Troopsloaded+troopsize
table.insert(loaded.Cargo,loadcargotype)
self.Loaded_Cargo[unitname]=loaded
self:_SendMessage(string.format("%s boarded!",cgoname),10,false,Group)
self:_RefreshDropTroopsMenu(Group,Unit)
self:__TroopsPickedUp(1,Group,Unit,Cargotype)
self:_UpdateUnitCargoMass(Unit)
Cargotype:RemoveStock()
end
return self
end
function CTLD:_FindRepairNearby(Group,Unit,Repairtype)
self:T(self.lid.." _FindRepairNearby")
local unitcoord=Unit:GetCoordinate()
local nearestGroup=nil
local nearestGroupIndex=-1
local nearestDistance=10000
for k,v in pairs(self.DroppedTroops)do
local distance=self:_GetDistance(v:GetCoordinate(),unitcoord)
local unit=v:GetUnit(1)
local desc=unit:GetDesc()or nil
if distance<nearestDistance and distance~=-1 and not desc.attributes.Infantry then
nearestGroup=v
nearestGroupIndex=k
nearestDistance=distance
end
end
if nearestGroup==nil or nearestDistance>self.EngineerSearch then
self:_SendMessage("No unit close enough to repair!",10,false,Group)
return nil,nil
end
local groupname=nearestGroup:GetName()
local function matchstring(String,Table)
local match=false
String=string.gsub(String,"-"," ")
if type(Table)=="table"then
for _,_name in pairs(Table)do
_name=string.gsub(_name,"-"," ")
if string.find(String,_name)then
match=true
break
end
end
else
if type(String)=="string"then
Table=string.gsub(Table,"-"," ")
if string.find(String,Table)then match=true end
end
end
return match
end
local Cargotype=nil
for k,v in pairs(self.Cargo_Crates)do
if matchstring(groupname,v.Templates)and matchstring(groupname,Repairtype)then
Cargotype=v
break
end
end
if Cargotype==nil then
return nil,nil
else
return nearestGroup,Cargotype
end
end
function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering)
self:T(self.lid.." _RepairObjectFromCrates")
local build=Build
local Repairtype=build.Template
local NearestGroup,CargoType=self:_FindRepairNearby(Group,Unit,Repairtype)
if NearestGroup~=nil then
if self.repairtime<2 then self.repairtime=30 end
if not Engineering then
self:_SendMessage(string.format("Repair started using %s taking %d secs",build.Name,self.repairtime),10,false,Group)
end
local name=CargoType:GetName()
local required=CargoType:GetCratesNeeded()
local template=CargoType:GetTemplates()
local ctype=CargoType:GetType()
local object={}
object.Name=CargoType:GetName()
object.Required=required
object.Found=required
object.Template=template
object.CanBuild=true
object.Type=ctype
self:_CleanUpCrates(Crates,Build,Number)
local desttimer=TIMER:New(function()NearestGroup:Destroy(false)end,self)
desttimer:Start(self.repairtime-1)
local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate())
buildtimer:Start(self.repairtime)
self:__CratesRepairStarted(1,Group,Unit)
else
if not Engineering then
self:_SendMessage("Can't repair this unit with "..build.Name,10,false,Group)
else
self:T("Can't repair this unit with "..build.Name)
end
end
return self
end
function CTLD:_ExtractTroops(Group,Unit)
self:T(self.lid.." _ExtractTroops")
local grounded=not self:IsUnitInAir(Unit)
local hoverload=self:CanHoverLoad(Unit)
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
end
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
self:_SendMessage("You need to open the door(s) to extract troops!",10,false,Group)
if not self.debug then return self end
end
local unit=Unit
local unitname=unit:GetName()
local unittype=unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local cantroops=capabilities.troops
local trooplimit=capabilities.trooplimit
local unitcoord=unit:GetCoordinate()
local nearestGroup=nil
local nearestGroupIndex=-1
local nearestDistance=10000000
local maxdistance=0
local nearestList={}
local distancekeys={}
local extractdistance=self.CrateDistance*self.ExtractFactor
for k,v in pairs(self.DroppedTroops)do
local distance=self:_GetDistance(v:GetCoordinate(),unitcoord)
local TNow=timer.getTime()
local vtime=v.ExtractTime or TNow-310
if distance<=extractdistance and distance~=-1 and(TNow-vtime>300)then
nearestGroup=v
nearestGroupIndex=k
nearestDistance=distance
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
end
if nearestGroup==nil or nearestDistance>extractdistance then
self:_SendMessage("No units close enough to extract!",10,false,Group)
return self
end
table.sort(distancekeys)
local secondarygroups={}
for i=1,#distancekeys do
local nearestGroup=nearestList[distancekeys[i]]
local groupType=string.match(nearestGroup:GetName(),"(.+)-(.+)$")
local Cargotype=nil
for k,v in pairs(self.Cargo_Troops)do
local comparison=""
if type(v.Templates)=="string"then comparison=v.Templates else comparison=v.Templates[1]end
if comparison==groupType then
Cargotype=v
break
end
end
if Cargotype==nil then
self:_SendMessage("Can't onboard "..groupType,10,false,Group)
else
local troopsize=Cargotype:GetCratesNeeded()
local numberonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Troopsloaded or 0
else
loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
end
if troopsize+numberonboard>trooplimit then
self:_SendMessage("Sorry, we\'re crammed already!",10,false,Group)
nearestGroup.ExtractTime=0
else
self.CargoCounter=self.CargoCounter+1
nearestGroup.ExtractTime=timer.getTime()
local loadcargotype=CTLD_CARGO:New(self.CargoCounter,Cargotype.Name,Cargotype.Templates,Cargotype.CargoType,true,true,Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass)
self:T({cargotype=loadcargotype})
local running=math.floor(nearestDistance/4)+20
loaded.Troopsloaded=loaded.Troopsloaded+troopsize
table.insert(loaded.Cargo,loadcargotype)
self.Loaded_Cargo[unitname]=loaded
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)
local groupname=nearestGroup:GetName()
self:__TroopsExtracted(running,Group,Unit,nearestGroup,groupname)
local coord=Unit:GetCoordinate()or Group:GetCoordinate()
local Point
if coord then
local heading=unit:GetHeading()or 0
local Angle=math.floor((heading+160)%360)
Point=coord:Translate(8,Angle):GetVec2()
if Point then
nearestGroup:RouteToVec2(Point,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
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
local groupname=string.match(_group:GetName(),"(.+)-(.+)$")
if _name==groupname then
_group:Destroy(false,15)
end
end
end
end
end
self:CleanDroppedTroops()
return self
end
function CTLD:_GetCrates(Group,Unit,Cargo,number,drop,pack)
self:T(self.lid.." _GetCrates")
if not drop and not pack then
local cgoname=Cargo:GetName()
local instock=Cargo:GetStock()
if type(instock)=="number"and tonumber(instock)<=0 and tonumber(instock)~=-1 then
self:_SendMessage(string.format("Sorry, we ran out of %s",cgoname),10,false,Group)
return self
end
end
local inzone=false
local drop=drop or false
local ship=nil
local width=20
local distance=nil
local zone=nil
if not drop and not pack then
inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone,ship,zone,distance,width=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
elseif drop and not pack then
if self.dropcratesanywhere then
inzone=true
else
inzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP)
end
elseif pack and not drop then
inzone=true
end
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group)
if not self.debug then return self end
end
local location=Cargo:GetLocation()
if location then
local unitcoord=Unit:GetCoordinate()or Group:GetCoordinate()
if unitcoord then
if not location:IsCoordinateInZone(unitcoord)then
self:_SendMessage("The requested cargo is not available in this zone!",10,false,Group)
if not self.debug then return self end
end
end
end
local capabilities=self:_GetUnitCapabilities(Unit)
local canloadcratesno=capabilities.cratelimit
local loaddist=self.CrateDistance or 35
local nearcrates,numbernearby=self:_FindCratesNearby(Group,Unit,loaddist,true,true)
if numbernearby>=canloadcratesno and not drop then
self:_SendMessage("There are enough crates nearby already! Take care of those first!",10,false,Group)
return self
end
local IsHerc=self:IsFixedWing(Unit)
local IsHook=self:IsHook(Unit)
local IsTruck=Unit:IsGround()
local cargotype=Cargo
local number=number or cargotype:GetCratesNeeded()
local cratesneeded=cargotype:GetCratesNeeded()
local cratename=cargotype:GetName()
local cratetemplate="Container"
local cgotype=cargotype:GetType()
local cgomass=cargotype:GetMass()
local isstatic=false
if cgotype==CTLD_CARGO.Enum.STATIC then
cratetemplate=cargotype:GetTemplates()
isstatic=true
end
local position=Unit:GetCoordinate()
local heading=Unit:GetHeading()+1
local height=Unit:GetHeight()
local droppedcargo={}
local cratedistance=0
local rheading=0
local angleOffNose=0
local addon=0
if IsHerc 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 or drop==true then
cratedistance=(i-1)*2.5+capabilities.length
if cratedistance>self.CrateDistance then cratedistance=self.CrateDistance end
rheading=UTILS.RandomGaussian(0,30,-90,90,100)
rheading=math.fmod((heading+rheading),360)
cratecoord=position:Translate(cratedistance,rheading)
else
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
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
end
if type(ship)=="string"then
self:T("Spawning on ship "..ship)
local Ship=UNIT:FindByName(ship)
local shipcoord=Ship:GetCoordinate()
local unitcoord=Unit:GetCoordinate()
local dist=shipcoord:Get2DDistance(unitcoord)
dist=dist-(20+math.random(1,10))
local width=width/2
local Offy=math.random(-width,width)
local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry)
:InitCargoMass(cgomass)
:InitCargo(self.enableslingload)
:InitLinkToUnit(Ship,dist,Offy,0)
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
local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry)
:InitCoordinate(cratecoord)
:InitCargoMass(cgomass)
:InitCargo(self.enableslingload)
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()
local subcat=cargotype.Subcategory
self.CargoCounter=self.CargoCounter+1
local realcargo=nil
if drop then
realcargo=CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat)
local map=cargotype:GetStaticResourceMap()
realcargo:SetStaticResourceMap(map)
local CCat,CType,CShape=cargotype:GetStaticTypeAndShape()
realcargo:SetStaticTypeAndShape(CCat,CType,CShape)
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
end
local CCat,CType,CShape=cargotype:GetStaticTypeAndShape()
realcargo:SetStaticTypeAndShape(CCat,CType,CShape)
table.insert(self.Spawned_Cargo,realcargo)
end
if not(drop or pack)then
Cargo:RemoveStock()
end
local text=string.format("Crates for %s have been positioned near you!",cratename)
if drop then
text=string.format("Crates for %s have been dropped!",cratename)
self:__CratesDropped(1,Group,Unit,droppedcargo)
else
self:_SendMessage(text,10,false,Group)
end
self:_RefreshLoadCratesMenu(Group,Unit)
return self
end
function CTLD:InjectStatics(Zone,Cargo,RandomCoord,FromLoad)
self:T(self.lid.." InjectStatics")
local cratecoord=Zone:GetCoordinate()
if RandomCoord then
cratecoord=Zone:GetRandomCoordinate(5,20)
end
local surface=cratecoord:GetSurfaceType()
if surface==land.SurfaceType.WATER then
return self
end
local cargotype=Cargo
local cratesneeded=cargotype:GetCratesNeeded()
local cratetemplate="Container"
local cratename=cargotype:GetName()
local cgotype=cargotype:GetType()
local cgomass=cargotype:GetMass()
local 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 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
local spawnstatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry)
:InitCargoMass(cgomass)
:InitCargo(self.enableslingload)
:InitCoordinate(cratecoord)
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()
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,true)
return self
end
function CTLD:_ListCratesNearby(_group,_unit)
self:T(self.lid.." _ListCratesNearby")
local finddist=self.CrateDistance or 35
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
local entry=_entry
local name=entry:GetName()
local dropped=entry:WasDropped()
if dropped then
text:Add(string.format("Dropped crate for %s, %dkg",name,entry.PerCrateMass))
else
text:Add(string.format("Crate for %s, %dkg",name,entry.PerCrateMass))
end
end
if text:GetCount()==1 then
text:Add("        N O N E")
end
text:Add("------------------------------------------------------------")
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:_RemoveCratesNearby(_group,_unit)
self:T(self.lid.." _RemoveCratesNearby")
local finddist=self.CrateDistance or 35
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()or"none"
text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass))
if entry:GetPositionable()then
entry:GetPositionable():Destroy(false)
end
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)
self:_CleanupTrackedCrates(removedIDs)
self:_RefreshLoadCratesMenu(_group,_unit)
else
self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group)
end
return self
end
function CTLD:_GetDistance(_point1,_point2)
self:T(self.lid.." _GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
function CTLD:_FindCratesNearby(_group,_unit,_dist,_ignoreweight,ignoretype)
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 maxloadable=2000
local IsHook=self:IsHook(_unit)
if not _ignoreweight then
maxloadable=self:_GetMaxLoadableMass(_unit)
end
self:T(self.lid.." Max loadable mass: "..maxloadable)
for _,_cargoobject in pairs(existingcrates)do
local cargo=_cargoobject
local static=cargo:GetPositionable()
local 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)
self:T(self.lid..string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable))
if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true then
index=index+1
table.insert(found,staticid,cargo)
maxloadable=maxloadable-weight
end
end
end
return found,index,LoadedbyGC,indexg
end
function CTLD:_LoadCratesNearby(Group,Unit)
self:T(self.lid.." _LoadCratesNearby")
local group=Group
local unit=Unit
local unitname=unit:GetName()
local unittype=unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local cancrates=capabilities.crates
local cratelimit=capabilities.cratelimit
local grounded=not self:IsUnitInAir(Unit)
local canhoverload=self:CanHoverLoad(Unit)
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group)
if not self.debug then return self end
end
if not cancrates then
self:_SendMessage("Sorry this chopper cannot carry crates!",10,false,Group)
elseif self.forcehoverload and not canhoverload then
self:_SendMessage("Hover over the crates to pick them up!",10,false,Group)
elseif not grounded and not canhoverload then
self:_SendMessage("Land or hover over the crates to pick them up!",10,false,Group)
else
local numberonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Cratesloaded or 0
else
loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
end
local finddist=self.CrateDistance or 35
local nearcrates,number=self:_FindCratesNearby(Group,Unit,finddist,false,false)
self:T(self.lid.." Crates found: "..number)
if number==0 and self.hoverautoloading then
return self
elseif number==0 then
self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!",10,false,Group)
return self
elseif numberonboard==cratelimit then
self:_SendMessage("Sorry, we are fully loaded!",10,false,Group)
return self
else
local capacity=cratelimit-numberonboard
local crateidsloaded={}
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
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.Cratesloaded<cratelimit and loadedHere<totalFound do
loadedHere=loadedHere+1
local crate=crateList[loadedHere]
if crate and crate.Positionable then
loaded.Cratesloaded=loaded.Cratesloaded+1
crate:SetHasMoved(true)
crate:SetWasDropped(false)
table.insert(loaded.Cargo,crate)
table.insert(crateidsloaded,crate:GetID())
crate:GetPositionable():Destroy(false)
crate.Positionable=nil
else
loadedHere=loadedHere-1
break
end
end
capacity=cratelimit-loaded.Cratesloaded
if loadedHere>0 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)
end
end
return self
end
function CTLD:_CleanupTrackedCrates(crateIdsToRemove)
local existingcrates=self.Spawned_Cargo
local newexcrates={}
for _,_crate in pairs(existingcrates)do
local excrate=_crate
local ID=excrate:GetID()
local keep=true
for _,_ID in pairs(crateIdsToRemove)do
if ID==_ID then
keep=false
end
end
local static=_crate:GetPositionable()
if not static or not static:IsAlive()then
keep=false
end
if keep then
table.insert(newexcrates,_crate)
end
end
self.Spawned_Cargo=nil
self.Spawned_Cargo=newexcrates
return self
end
function CTLD:_GetUnitCargoMass(Unit)
self:T(self.lid.." _GetUnitCargoMass")
if not Unit then return 0 end
local unitname=Unit:GetName()
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local loadedmass=0
if self.Loaded_Cargo[unitname]then
local cargotable=loadedcargo.Cargo or{}
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
loadedmass=loadedmass+(cargo.PerCrateMass*cargo:GetCratesNeeded())
end
if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and 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
end
function CTLD:_GetMaxLoadableMass(Unit)
self:T(self.lid.." _GetMaxLoadableMass")
if not Unit then return 0 end
local loadable=0
local loadedmass=self:_GetUnitCargoMass(Unit)
local capabilities=self:_GetUnitCapabilities(Unit)
local maxmass=capabilities.cargoweightlimit or 2000
loadable=maxmass-loadedmass
return loadable
end
function CTLD:_UpdateUnitCargoMass(Unit)
self:T(self.lid.." _UpdateUnitCargoMass")
local calculatedMass=self:_GetUnitCargoMass(Unit)
Unit:SetUnitInternalCargo(calculatedMass)
return self
end
function CTLD:_ListCargo(Group,Unit)
self:T(self.lid.." _ListCargo")
local unitname=Unit:GetName()
local unittype=Unit:GetTypeName()
local capabilities=self:_GetUnitCapabilities(Unit)
local trooplimit=capabilities.trooplimit
local cratelimit=capabilities.cratelimit
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local loadedmass=self:_GetUnitCargoMass(Unit)
local maxloadable=self:_GetMaxLoadableMass(Unit)
local finddist=self.CrateDistance or 35
if self.Loaded_Cargo[unitname]then
local no_troops=loadedcargo.Troopsloaded or 0
local no_crates=loadedcargo.Cratesloaded or 0
local cargotable=loadedcargo.Cargo or{}
local report=REPORT:New("Transport Checkout Sheet")
report:Add("------------------------------------------------------------")
report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit))
report:Add("------------------------------------------------------------")
report:Add("        -- TROOPS --")
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and(not cargo:WasDropped()or self.allowcratepickupagain)then
report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded()))
end
end
if report:GetCount()==4 then
report:Add("        N O N E")
end
report:Add("------------------------------------------------------------")
report:Add("       -- CRATES --")
local cratecount=0
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 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
report:Add("------------------------------------------------------------")
report:Add("Total Mass: "..loadedmass.." kg. Loadable: "..maxloadable.." kg.")
local text=report:Text()
self:_SendMessage(text,30,true,Group)
else
self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable),10,false,Group)
end
return self
end
function CTLD:_ListInventory(Group,Unit)
self:T(self.lid.." _ListInventory")
local unitname=Unit:GetName()
local unittype=Unit:GetTypeName()
local cgotypes=self.Cargo_Crates
local trptypes=self.Cargo_Troops
local stctypes=self.Cargo_Statics
local function countcargo(cgotable)
local counter=0
for _,_cgo in pairs(cgotable)do
counter=counter+1
end
return counter
end
local crateno=countcargo(cgotypes)
local troopno=countcargo(trptypes)
local staticno=countcargo(stctypes)
if(crateno>0 or troopno>0 or staticno>0)then
local report=REPORT:New("Inventory Sheet")
report:Add("------------------------------------------------------------")
report:Add(string.format("Troops: %d, Cratetypes: %d",troopno,crateno+staticno))
report:Add("------------------------------------------------------------")
report:Add("        -- TROOPS --")
for _,_cargo in pairs(trptypes)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
local stockn=cargo:GetStock()
local stock="none"
if stockn==-1 then
stock="unlimited"
elseif stockn>0 then
stock=tostring(stockn)
end
report:Add(string.format("Unit: %s | Soldiers: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock))
end
end
if report:GetCount()==4 then
report:Add("        N O N E")
end
report:Add("------------------------------------------------------------")
report:Add("       -- CRATES --")
local cratecount=0
for _,_cargo in pairs(cgotypes)do
local cargo=_cargo
local type=cargo:GetType()
if(type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
local stockn=cargo:GetStock()
local stock="none"
if stockn==-1 then
stock="unlimited"
elseif stockn>0 then
stock=tostring(stockn)
end
report:Add(string.format("Type: %s | Crates per Set: %d | Stock: %s",cargo:GetName(),cargo:GetCratesNeeded(),stock))
cratecount=cratecount+1
end
end
for _,_cargo in pairs(stctypes)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.STATIC)and not cargo:WasDropped()then
local stockn=cargo:GetStock()
local stock="none"
if stockn==-1 then
stock="unlimited"
elseif stockn>0 then
stock=tostring(stockn)
end
report:Add(string.format("Type: %s | Stock: %s",cargo:GetName(),stock))
cratecount=cratecount+1
end
end
if cratecount==0 then
report:Add("        N O N E")
end
local text=report:Text()
self:_SendMessage(text,30,true,Group)
else
self:_SendMessage(string.format("Nothing in stock!"),10,false,Group)
end
return self
end
function CTLD: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
end
end
function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
local Positions={}
local template=_DATABASE:GetGroupTemplate(Template)
local numbertroops=#template.units
local slightshift=math.abs(math.random(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)
local pos1=post:GetVec2()
local p1t={
x=pos1.x,
y=pos1.y,
heading=phead,
}
table.insert(Positions,p1t)
end
return Positions
end
function CTLD:_UnloadTroops(Group,Unit)
self:T(self.lid.." _UnloadTroops")
local droppingatbase=false
local canunload=true
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then
self:_SendMessage("You need to open the door(s) to unload troops!",10,false,Group)
if not self.debug then return self end
end
local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
if inzone then
droppingatbase=true
end
local hoverunload=self:IsCorrectHover(Unit)
local IsHerc=self: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
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local cargotable=loadedcargo.Cargo
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and not cargo:WasDropped()then
local name=cargo:GetName()or"none"
local temptable=cargo:GetTemplates()or{}
local position=Group:GetCoordinate()
local zoneradius=self.troopdropzoneradius or 100
local factor=1
if IsHerc then
factor=cargo:GetCratesNeeded()or 1
zoneradius=Unit:GetVelocityMPS()or 100
end
local zone=ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor)
local randomcoord=zone:GetRandomCoordinate(10,30*factor)
local heading=Group:GetHeading()or 0
if hoverunload or grounded then
randomcoord=Group:GetCoordinate()
local Angle=(heading+270)%360
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*2)
local Positions=self:_GetUnitPositions(randomcoord,rad,heading,_template)
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:InitSetUnitAbsolutePositions(Positions)
:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end)
:SpawnFromVec2(randomcoord:GetVec2())
self:__TroopsDeployed(1,Group,Unit,self.DroppedTroops[self.TroopCounter],type)
end
cargo:SetWasDropped(true)
if type==CTLD_CARGO.Enum.ENGINEERS then
self.Engineers=self.Engineers+1
local grpname=self.DroppedTroops[self.TroopCounter]:GetName()
self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname)
self:_SendMessage(string.format("Dropped Engineers %s into action!",name),10,false,Group)
else
self:_SendMessage(string.format("Dropped Troops %s into action!",name),10,false,Group)
end
end
end
else
self:_SendMessage("Troops have returned to base!",10,false,Group)
self:__TroopsRTB(1,Group,Unit,zonename,zone)
end
local loaded={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
local loadedcargo=self.Loaded_Cargo[unitname]or{}
local cargotable=loadedcargo.Cargo or{}
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
local dropped=cargo:WasDropped()
if type~=CTLD_CARGO.Enum.TROOPS and type~=CTLD_CARGO.Enum.ENGINEERS and not dropped then
table.insert(loaded.Cargo,_cargo)
loaded.Cratesloaded=loaded.Cratesloaded+1
else
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)and droppingatbase then
local name=cargo:GetName()
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
local stock=_troop:GetStock()
if stock and tonumber(stock)>=0 then _troop:AddStock()end
end
end
end
end
end
self.Loaded_Cargo[unitname]=nil
self.Loaded_Cargo[unitname]=loaded
self:_RefreshDropTroopsMenu(Group,Unit)
self:_UpdateUnitCargoMass(Unit)
else
if IsHerc then
self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group)
else
self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group)
end
end
return self
end
function CTLD:_UnloadCrates(Group,Unit)
self:T(self.lid.." _UnloadCrates")
if not self.dropcratesanywhere then
local inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP)
if not inzone then
self:_SendMessage("You are not close enough to a drop zone!",10,false,Group)
if not self.debug then
return self
end
end
end
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: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
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 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={}
loaded.Troopsloaded=0
loaded.Cratesloaded=0
loaded.Cargo={}
for _,_cargo in pairs(cargotable)do
local cargo=_cargo
local type=cargo:GetType()
local size=cargo:GetCratesNeeded()
if type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS then
table.insert(loaded.Cargo,_cargo)
loaded.Troopsloaded=loaded.Troopsloaded+size
end
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)
else
if IsHerc then
self:_SendMessage("Nothing loaded or not within airdrop parameters!",10,false,Group)
else
self:_SendMessage("Nothing loaded or not hovering within parameters!",10,false,Group)
end
end
return self
end
function CTLD:_BuildCrates(Group,Unit,Engineering,MultiDrop)
self:T(self.lid.." _BuildCrates")
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)
return self
end
end
if not Engineering and self.nobuildinloadzones then
local inloadzone=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if inloadzone then
self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group)
return self
end
end
local finddist=self.CrateDistance or 35
local crates,number=self:_FindCratesNearby(Group,Unit,finddist,true,true)
local buildables={}
local foundbuilds=false
local canbuild=false
if number>0 then
for _,_crate in pairs(crates)do
local Crate=_crate
if(Crate:WasDropped()or not self.movecratesbeforebuild)and not Crate:IsRepair()and not Crate:IsStatic()then
local name=Crate:GetName()
local required=Crate:GetCratesNeeded()
local template=Crate:GetTemplates()
local ctype=Crate:GetType()
local ccoord=Crate:GetPositionable():GetCoordinate()
if not buildables[name]then
local object={}
object.Name=name
object.Required=required
object.Found=1
object.Template=template
object.CanBuild=false
object.Type=ctype
object.Coord=ccoord:GetVec2()
buildables[name]=object
foundbuilds=true
else
buildables[name].Found=buildables[name].Found+1
foundbuilds=true
end
if buildables[name].Found>=buildables[name].Required then
buildables[name].CanBuild=true
canbuild=true
end
self:T({buildables=buildables})
end
end
local report=REPORT:New("Checklist Buildable Crates")
report:Add("------------------------------------------------------------")
for _,_build in pairs(buildables)do
local build=_build
local name=build.Name
local needed=build.Required
local found=build.Found
local txtok="NO"
if build.CanBuild then
txtok="YES"
end
local text=string.format("Type: %s | Required %d | Found %d | Can Build %s",name,needed,found,txtok)
report:Add(text)
end
if not foundbuilds then
report:Add("     --- None found! ---")
if self.movecratesbeforebuild then
report:Add("*** Crates need to be moved before building!")
end
end
report:Add("------------------------------------------------------------")
local text=report:Text()
if not Engineering then
self:_SendMessage(text,30,true,Group)
else
self:T(text)
end
if canbuild then
for _,_build in pairs(buildables)do
local build=_build
if build.CanBuild then
self:_CleanUpCrates(crates,build,number)
if self.buildtime and self.buildtime>0 then
local buildtimer=TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop)
buildtimer:Start(self.buildtime)
self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group)
self:__CratesBuildStarted(1,Group,Unit)
self:_RefreshDropTroopsMenu(Group,Unit)
else
self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop)
end
end
end
end
else
if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end
end
return self
end
function CTLD:_PackCratesNearby(Group,Unit)
self:T(self.lid.." _PackCratesNearby")
local location=Group:GetCoordinate()
local nearestGroups=SET_GROUP:New():FilterCoalitions("blue"):FilterZones({ZONE_RADIUS:New("TempZone",location:GetVec2(),self.PackDistance,false)}):FilterOnce()
for _,_Group in pairs(nearestGroups.Set)do
for _,_Template in pairs(_DATABASE.Templates.Groups)do
if(string.match(_Group:GetName(),_Template.GroupName))then
for _,_entry in pairs(self.Cargo_Crates)do
if(_entry.Templates[1]==_Template.GroupName)then
_Group:Destroy()
self:_GetCrates(Group,Unit,_entry,nil,false,true)
self:_RefreshLoadCratesMenu(Group,Unit)
return true
end
end
end
end
end
self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group)
return false
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,true)
local buildables={}
local foundbuilds=false
local canbuild=false
if number>0 then
for _,_crate in pairs(crates)do
local Crate=_crate
if Crate:WasDropped()and Crate:IsRepair()and not Crate:IsStatic()then
local name=Crate:GetName()
local required=Crate:GetCratesNeeded()
local template=Crate:GetTemplates()
local ctype=Crate:GetType()
if not buildables[name]then
local object={}
object.Name=name
object.Required=required
object.Found=1
object.Template=template
object.CanBuild=false
object.Type=ctype
buildables[name]=object
foundbuilds=true
else
buildables[name].Found=buildables[name].Found+1
foundbuilds=true
end
if buildables[name].Found>=buildables[name].Required then
buildables[name].CanBuild=true
canbuild=true
end
self:T({repair=buildables})
end
end
local report=REPORT:New("Checklist Repairs")
report:Add("------------------------------------------------------------")
for _,_build in pairs(buildables)do
local build=_build
local name=build.Name
local needed=build.Required
local found=build.Found
local txtok="NO"
if build.CanBuild then
txtok="YES"
end
local text=string.format("Type: %s | Required %d | Found %d | Can Repair %s",name,needed,found,txtok)
report:Add(text)
end
if not foundbuilds then report:Add("     --- None Found ---")end
report:Add("------------------------------------------------------------")
local text=report:Text()
if not Engineering then
self:_SendMessage(text,30,true,Group)
else
self:T(text)
end
if canbuild then
for _,_build in pairs(buildables)do
local build=_build
if build.CanBuild then
self:_RepairObjectFromCrates(Group,Unit,crates,build,number,Engineering)
end
end
end
else
if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist),10,false,Group)end
end
return self
end
function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,MultiDrop)
self:T(self.lid.." _BuildObjectFromCrates")
if Group and Group:IsAlive()or(RepairLocation and not Repair)then
local name=Build.Name
local ctype=Build.Type
local canmove=false
if ctype==CTLD_CARGO.Enum.VEHICLE then canmove=true end
if ctype==CTLD_CARGO.Enum.STATIC then
return self
end
local temptable=Build.Template or{}
if type(temptable)=="string"then
temptable={temptable}
end
local zone=nil
if RepairLocation and not Repair then
zone=ZONE_RADIUS:New(string.format("Build zone-%d",math.random(1,10000)),RepairLocation:GetVec2(),100)
else
zone=ZONE_GROUP:New(string.format("Unload zone-%d",math.random(1,10000)),Group,100)
end
local randomcoord=Build.Coord or zone:GetRandomCoordinate(35):GetVec2()
if MultiDrop and(not Repair)and canmove then
local randomcoord=zone:GetRandomCoordinate(35):GetVec2()
end
if Repair then
randomcoord=RepairLocation:GetVec2()
end
for _,_template in pairs(temptable)do
self.TroopCounter=self.TroopCounter+1
local alias=string.format("%s-%d",_template,math.random(1,100000))
if canmove then
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end)
:SpawnFromVec2(randomcoord)
else
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:OnSpawnGroup(function(grp)grp.spawntime=timer.getTime()end)
:SpawnFromVec2(randomcoord)
end
if Repair then
self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
else
self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
end
end
else
self:T(self.lid.."Group KIA while building!")
end
return self
end
function CTLD:_MoveGroupToZone(Group)
self:T(self.lid.." _MoveGroupToZone")
local groupname=Group:GetName()or"none"
local groupcoord=Group:GetCoordinate()
local outcome,name,zone,distance=self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
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()
Group:SetAIOn()
Group:OptionAlarmStateAuto()
Group:OptionDisperseOnAttack(30)
Group:OptionROEOpenFirePossible()
Group:RouteToVec2(coordinate,5)
end
return self
end
function CTLD:_CleanUpCrates(Crates,Build,Number)
self:T(self.lid.." _CleanUpCrates")
local build=Build
local existingcrates=self.Spawned_Cargo
local newexcrates={}
local numberdest=Build.Required
local nametype=Build.Name
local found=0
local rounds=Number
local destIDs={}
for _,_crate in pairs(Crates)do
local nowcrate=_crate
local name=nowcrate:GetName()
local thisID=nowcrate:GetID()
if name==nametype then
table.insert(destIDs,thisID)
found=found+1
nowcrate:GetPositionable():Destroy(false)
nowcrate.Positionable=nil
nowcrate.HasBeenDropped=false
end
if found==numberdest then break end
end
self:_CleanupTrackedCrates(destIDs)
return self
end
function CTLD:_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)
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)
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:_RefreshF10Menus()
self:T(self.lid.." _RefreshF10Menus")
self.onestepmenu=self.onestepmenu or false
local PlayerSet=self.PilotGroups
local PlayerTable=PlayerSet:GetSetObjects()
local _UnitList={}
for _,groupObj in pairs(PlayerTable)do
local firstUnit=groupObj:GetFirstUnitAlive()
if firstUnit then
if firstUnit:IsPlayer()then
if firstUnit:IsHelicopter()or(self.enableFixedWing and self:IsFixedWing(firstUnit))then
local _unit=firstUnit:GetName()
_UnitList[_unit]=_unit
end
end
end
end
if self.allowCATransport and self.CATransportSet then
for _,_clientobj in pairs(self.CATransportSet.Set)do
local client=_clientobj
if client:IsGround()then
local cname=client:GetName()
self:T(self.lid.."Adding: "..cname)
_UnitList[cname]=cname
end
end
end
self.CtldUnits=_UnitList
if self.usesubcats then
for _id,_cargo in pairs(self.Cargo_Crates)do
local entry=_cargo
if not self.subcats[entry.Subcategory]then
self.subcats[entry.Subcategory]=entry.Subcategory
end
end
for _id,_cargo in pairs(self.Cargo_Statics)do
local entry=_cargo
if not self.subcats[entry.Subcategory]then
self.subcats[entry.Subcategory]=entry.Subcategory
end
end
for _id,_cargo in pairs(self.Cargo_Troops)do
local entry=_cargo
if not self.subcatsTroop[entry.Subcategory]then
self.subcatsTroop[entry.Subcategory]=entry.Subcategory
end
end
end
local menucount=0
local menus={}
for _,_unitName in pairs(self.CtldUnits)do
if(not self.MenusDone[_unitName])or(self.showstockinmenuitems==true)then
self:T(self.lid.."Menu not done yet for ".._unitName)
local _unit=UNIT:FindByName(_unitName)
if not _unit and self.allowCATransport then
_unit=CLIENT:FindByName(_unitName)
end
if _unit and _unit:IsAlive()then
local _group=_unit:GetGroup()
if _group then
self:T(self.lid.."Unit and Group exist")
local capabilities=self:_GetUnitCapabilities(_unit)
local cantroops=capabilities.troops
local cancrates=capabilities.crates
local unittype=_unit:GetTypeName()
local isHook=self:IsHook(_unit)
local nohookswitch=true
if _group.CTLDTopmenu then
_group.CTLDTopmenu:Remove()
_group.CTLDTopmenu=nil
end
local toptroops=nil
local topcrates=nil
local topmenu=MENU_GROUP:New(_group,"CTLD",nil)
_group.CTLDTopmenu=topmenu
if cantroops then
local toptroops=MENU_GROUP:New(_group,"Manage Troops",topmenu)
local troopsmenu=MENU_GROUP:New(_group,"Load troops",toptroops)
_group.MyTopTroopsMenu=toptroops
if self.usesubcats then
local subcatmenus={}
for catName,_ in pairs(self.subcatsTroop)do
subcatmenus[catName]=MENU_GROUP:New(_group,catName,troopsmenu)
end
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,subcatmenus[cargoObj.Subcategory],self._LoadTroops,self,_group,_unit,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)
end
end
end
local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh()
MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh()
MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh()
local uName=_unit:GetName()
local loadedData=self.Loaded_Cargo[uName]
if loadedData and loadedData.Cargo then
for i,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"
local needed=cargoObj:GetCratesNeeded()or 1
local cID=cargoObj:GetID()
local line=string.format("Drop: %s",name,needed,cID)
MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh()
end
end
end
end
if cancrates then
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
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 and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get only",mSet,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 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 and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get only",mSet,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)
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 and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get only",mSet,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 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 and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj)
MENU_GROUP_COMMAND:New(_group,"Get only",mSet,self._GetCrates,self,_group,_unit,cargoObj)
end
end
end
else
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
MENU_GROUP_COMMAND:New(_group,txt,subcatmenus[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 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)
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)
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 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
MENU_GROUP_COMMAND:New(_group,txt,cratesmenu,self._GetCrates,self,_group,_unit,cargoObj)
end
end
end
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
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 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,"Pack only",mPack,self._PackCratesNearby,self,_group,_unit)
MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates,self._ListCratesNearby,self,_group,_unit)
else
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
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
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)
end
end
else
self:T(self.lid.." Menus already done for this group!")
end
end
return self
end
function CTLD:_RefreshLoadCratesMenu(Group,Unit)
if not Group.MyLoadCratesMenu then return end
Group.MyLoadCratesMenu:RemoveSubMenus()
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()<b:GetID()end)
local i=1
while i<=#list do
local left=#list-i+1
local label
if left>=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)
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 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)
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)
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 loadedHere<needed and loadedData.Cratesloaded>=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
self:_RefreshLoadCratesMenu(Group,Unit)
self:_RefreshDropCratesMenu(Group,Unit)
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 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)
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()<b:GetID()end)
local i=1
while i<=#list do
local left=(#list-i+1)
if left>=needed then
local chunk={}
for n=i,i+needed-1 do
table.insert(chunk,list[n])
end
local label=string.format("%d. %s",lineIndex,cName)
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)
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=#list+1
end
lineIndex=lineIndex+1
end
end
else
local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu)
MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit)
MENU_GROUP_COMMAND:New(Group,"Drop only",mAll,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()<b:GetID()end)
local i=1
while i<=#list do
local left=(#list-i+1)
if left>=needed then
local chunk={}
for n=i,i+needed-1 do
table.insert(chunk,list[n])
end
local label=string.format("%d. %s",lineIndex,cName)
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 and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex)
MENU_GROUP_COMMAND:New(Group,"Drop only",mSet,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)
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=#list+1
end
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=true
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)
: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
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()
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)
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()<b:GetID()end)
local count=#objList
local chunkID=objList[1]:GetID()
self.TroopsIDToChunk[chunkID]=objList
local label=string.format("Drop %s (%d)",tName,count)
MENU_GROUP_COMMAND:New(theGroup,label,dropTroopsMenu,self._UnloadSingleTroopByID,self,theGroup,theUnit,chunkID)
end
end
function CTLD:_CheckTemplates(temptable)
self:T(self.lid.." _CheckTemplates")
local outcome=true
if type(temptable)~="table"then
temptable={temptable}
end
for _,_name in pairs(temptable)do
if not _DATABASE.Templates.Groups[_name]then
outcome=false
self:E(self.lid.."ERROR: Template name ".._name.." is missing!")
end
end
return outcome
end
function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,SubCategory)
self:T(self.lid.." AddTroopsCargo")
self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock})
if not self:_CheckTemplates(Templates)then
self:E(self.lid.."Troops Cargo for "..Name.." has missing template(s)!")
return self
end
self.CargoCounter=self.CargoCounter+1
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock,SubCategory)
table.insert(self.Cargo_Troops,cargo)
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)
self:T(self.lid.." AddCratesCargo")
if not self:_CheckTemplates(Templates)then
self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!")
return self
end
self.CargoCounter=self.CargoCounter+1
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location)
if UnitTypes then
cargo:AddUnitTypeName(UnitTypes)
end
cargo:SetStaticTypeAndShape("Cargos",self.basetype)
if TypeName then
cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName)
end
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)
self:T(self.lid.." AddStaticsCargo")
self.CargoCounter=self.CargoCounter+1
local type=CTLD_CARGO.Enum.STATIC
local template=STATIC:FindByName(Name,true):GetTypeName()
local unittemplate=_DATABASE:GetStaticUnitTemplate(Name)
local ResourceMap=nil
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)
cargo:SetStaticResourceMap(ResourceMap)
table.insert(self.Cargo_Statics,cargo)
if SubCategory and self.usesubcats~=true then self.usesubcats=true end
return cargo
end
function CTLD:GetStaticsCargoFromTemplate(Name,Mass)
self:T(self.lid.." GetStaticsCargoFromTemplate")
self.CargoCounter=self.CargoCounter+1
local type=CTLD_CARGO.Enum.STATIC
local template=STATIC:FindByName(Name,true):GetTypeName()
local unittemplate=_DATABASE:GetStaticUnitTemplate(Name)
local ResourceMap=nil
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,1)
cargo:SetStaticResourceMap(ResourceMap)
return cargo
end
function CTLD:AddCratesRepair(Name,Template,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName)
self:T(self.lid.." AddCratesRepair")
if not self:_CheckTemplates(Template)then
self:E(self.lid.."Repair Cargo for "..Name.." has a missing template!")
return self
end
self.CargoCounter=self.CargoCounter+1
local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location)
if UnitTypes then
cargo:AddUnitTypeName(UnitTypes)
end
cargo:SetStaticTypeAndShape("cargos",self.basetype)
if TypeName then
cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName)
end
table.insert(self.Cargo_Crates,cargo)
return self
end
function CTLD:AddZone(Zone)
self:T(self.lid.." AddZone")
local zone=Zone
if zone.type==CTLD.CargoZoneType.LOAD then
table.insert(self.pickupZones,zone)
elseif zone.type==CTLD.CargoZoneType.DROP then
table.insert(self.dropOffZones,zone)
elseif zone.type==CTLD.CargoZoneType.SHIP then
table.insert(self.shipZones,zone)
elseif zone.type==CTLD.CargoZoneType.BEACON then
table.insert(self.droppedBeacons,zone)
else
table.insert(self.wpZones,zone)
end
return self
end
function CTLD:ActivateZone(Name,ZoneType,NewState)
self:T(self.lid.." ActivateZone")
local newstate=true
if NewState~=nil then
newstate=NewState
end
local table={}
if ZoneType==CTLD.CargoZoneType.LOAD then
table=self.pickupZones
elseif ZoneType==CTLD.CargoZoneType.DROP then
table=self.dropOffZones
elseif ZoneType==CTLD.CargoZoneType.SHIP then
table=self.shipZones
elseif ZoneType==CTLD.CargoZoneType.BEACON then
table=self.droppedBeacons
else
table=self.wpZones
end
for _,_zone in pairs(table)do
local thiszone=_zone
if thiszone.name==Name then
thiszone.active=newstate
break
end
end
return self
end
function CTLD:DeactivateZone(Name,ZoneType)
self:T(self.lid.." DeactivateZone")
self:ActivateZone(Name,ZoneType,false)
return self
end
function CTLD:_GetFMBeacon(Name)
self:T(self.lid.." _GetFMBeacon")
local beacon={}
if#self.FreeFMFrequencies<=1 then
self.FreeFMFrequencies=self.UsedFMFrequencies
self.UsedFMFrequencies={}
end
local FM=table.remove(self.FreeFMFrequencies,math.random(#self.FreeFMFrequencies))
table.insert(self.UsedFMFrequencies,FM)
beacon.name=Name
beacon.frequency=FM/1000000
beacon.modulation=CTLD.RadioModulation.FM
return beacon
end
function CTLD:_GetUHFBeacon(Name)
self:T(self.lid.." _GetUHFBeacon")
local beacon={}
if#self.FreeUHFFrequencies<=1 then
self.FreeUHFFrequencies=self.UsedUHFFrequencies
self.UsedUHFFrequencies={}
end
local UHF=table.remove(self.FreeUHFFrequencies,math.random(#self.FreeUHFFrequencies))
table.insert(self.UsedUHFFrequencies,UHF)
beacon.name=Name
beacon.frequency=UHF/1000000
beacon.modulation=CTLD.RadioModulation.AM
return beacon
end
function CTLD:_GetVHFBeacon(Name)
self:T(self.lid.." _GetVHFBeacon")
local beacon={}
if#self.FreeVHFFrequencies<=3 then
self.FreeVHFFrequencies=self.UsedVHFFrequencies
self.UsedVHFFrequencies={}
end
local VHF=table.remove(self.FreeVHFFrequencies,math.random(#self.FreeVHFFrequencies))
table.insert(self.UsedVHFFrequencies,VHF)
beacon.name=Name
beacon.frequency=VHF/1000000
beacon.modulation=CTLD.RadioModulation.FM
return beacon
end
function CTLD:AddCTLDZone(Name,Type,Color,Active,HasBeacon,Shiplength,Shipwidth)
self:T(self.lid.." AddCTLDZone")
local zone=ZONE:FindByName(Name)
if not zone and Type~=CTLD.CargoZoneType.SHIP then
self:E(self.lid.."**** Zone does not exist: "..Name)
return self
end
if Type==CTLD.CargoZoneType.SHIP then
local Ship=UNIT:FindByName(Name)
if not Ship then
self:E(self.lid.."**** Ship does not exist: "..Name)
return self
end
end
local ctldzone={}
ctldzone.active=Active or false
ctldzone.color=Color or SMOKECOLOR.Red
ctldzone.name=Name or"NONE"
ctldzone.type=Type or CTLD.CargoZoneType.MOVE
ctldzone.hasbeacon=HasBeacon or false
if Type==CTLD.CargoZoneType.BEACON then
self.droppedbeaconref[ctldzone.name]=zone:GetCoordinate()
ctldzone.timestamp=timer.getTime()
end
if HasBeacon then
ctldzone.fmbeacon=self:_GetFMBeacon(Name)
ctldzone.uhfbeacon=self:_GetUHFBeacon(Name)
ctldzone.vhfbeacon=self:_GetVHFBeacon(Name)
else
ctldzone.fmbeacon=nil
ctldzone.uhfbeacon=nil
ctldzone.vhfbeacon=nil
end
if Type==CTLD.CargoZoneType.SHIP then
ctldzone.shiplength=Shiplength or 100
ctldzone.shipwidth=Shipwidth or 10
end
self:AddZone(ctldzone)
return self
end
function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon)
self:T(self.lid.." AddCTLDZoneFromAirbase")
local AFB=AIRBASE:FindByName(AirbaseName)
local name=AFB:GetZone():GetName()
self:T(self.lid.."AFB "..AirbaseName.." ZoneName "..name)
self:AddCTLDZone(name,Type,Color,Active,HasBeacon)
return self
end
function CTLD:DropBeaconNow(Unit)
self:T(self.lid.." DropBeaconNow")
local ctldzone={}
ctldzone.active=true
ctldzone.color=math.random(0,4)
ctldzone.name="Beacon "..math.random(1,10000)
ctldzone.type=CTLD.CargoZoneType.BEACON
ctldzone.hasbeacon=true
ctldzone.fmbeacon=self:_GetFMBeacon(ctldzone.name)
ctldzone.uhfbeacon=self:_GetUHFBeacon(ctldzone.name)
ctldzone.vhfbeacon=self:_GetVHFBeacon(ctldzone.name)
ctldzone.timestamp=timer.getTime()
self.droppedbeaconref[ctldzone.name]=Unit:GetCoordinate()
self:AddZone(ctldzone)
local FMbeacon=ctldzone.fmbeacon
local VHFbeacon=ctldzone.vhfbeacon
local UHFbeacon=ctldzone.uhfbeacon
local Name=ctldzone.name
local FM=FMbeacon.frequency
local VHF=VHFbeacon.frequency*1000
local UHF=UHFbeacon.frequency
local text=string.format("Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",Name,FM,VHF,UHF)
self:_SendMessage(text,15,false,Unit:GetGroup())
return self
end
function CTLD:CheckDroppedBeacons()
self:T(self.lid.." CheckDroppedBeacons")
local timeout=self.droppedbeacontimeout or 600
local livebeacontable={}
for _,_beacon in pairs(self.droppedBeacons)do
local beacon=_beacon
if not beacon.timestamp then beacon.timestamp=timer.getTime()+timeout end
local T0=beacon.timestamp
if timer.getTime()-T0>timeout then
local name=beacon.name
self.droppedbeaconref[name]=nil
_beacon=nil
else
table.insert(livebeacontable,beacon)
end
end
self.droppedBeacons=nil
self.droppedBeacons=livebeacontable
return self
end
function CTLD:_ListRadioBeacons(Group,Unit)
self:T(self.lid.." _ListRadioBeacons")
local report=REPORT:New("Active Zone Beacons")
report:Add("------------------------------------------------------------")
local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons}
for i=1,5 do
for index,cargozone in pairs(zones[i])do
local czone=cargozone
if czone.active and czone.hasbeacon then
local FMbeacon=czone.fmbeacon
local VHFbeacon=czone.vhfbeacon
local UHFbeacon=czone.uhfbeacon
local Name=czone.name
local FM=FMbeacon.frequency
local VHF=VHFbeacon.frequency*1000
local UHF=UHFbeacon.frequency
report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ",Name,FM,VHF,UHF),"|")
end
end
end
if report:GetCount()==1 then
report:Add("        N O N E")
end
report:Add("------------------------------------------------------------")
self:_SendMessage(report:Text(),30,true,Group)
return self
end
function CTLD:_AddRadioBeacon(Name,Sound,Mhz,Modulation,IsShip,IsDropped)
self:T(self.lid.." _AddRadioBeacon")
local Zone=nil
if IsShip then
Zone=UNIT:FindByName(Name)
elseif IsDropped then
Zone=self.droppedbeaconref[Name]
else
Zone=ZONE:FindByName(Name)
if not Zone then
Zone=AIRBASE:FindByName(Name):GetZone()
end
end
local Sound=Sound or"beacon.ogg"
if Zone then
if IsDropped then
local ZoneCoord=Zone
local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0}
local Frequency=Mhz*1000000
local Sound=self.RadioPath..Sound
trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000))
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
else
local ZoneCoord=Zone:GetCoordinate()
local ZoneVec3=ZoneCoord:GetVec3()or{x=0,y=0,z=0}
local Frequency=Mhz*1000000
local Sound=self.RadioPath..Sound
trigger.action.radioTransmission(Sound,ZoneVec3,Modulation,false,Frequency,1000,Name..math.random(1,10000))
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
end
else
self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name)
end
return self
end
function CTLD:SetSoundfilesFolder(FolderPath)
self:T(self.lid.." SetSoundfilesFolder")
if FolderPath then
local lastchar=string.sub(FolderPath,-1)
if lastchar~="/"then
FolderPath=FolderPath.."/"
end
end
self.RadioPath=FolderPath
self:I(self.lid..string.format("Setting sound files folder to: %s",self.RadioPath))
return self
end
function CTLD:_RefreshRadioBeacons()
self:T(self.lid.." _RefreshRadioBeacons")
local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones,[5]=self.droppedBeacons}
for i=1,5 do
local IsShip=false
if i==4 then IsShip=true end
local IsDropped=false
if i==5 then IsDropped=true end
for index,cargozone in pairs(zones[i])do
local czone=cargozone
local Sound=self.RadioSound
local Silent=self.RadioSoundFC3 or self.RadioSound
if czone.active and czone.hasbeacon then
local FMbeacon=czone.fmbeacon
local VHFbeacon=czone.vhfbeacon
local UHFbeacon=czone.uhfbeacon
local Name=czone.name
local FM=FMbeacon.frequency
local VHF=VHFbeacon.frequency
local UHF=UHFbeacon.frequency
self:_AddRadioBeacon(Name,Sound,FM,CTLD.RadioModulation.FM,IsShip,IsDropped)
self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.AM,IsShip,IsDropped)
self:_AddRadioBeacon(Name,Silent,UHF,CTLD.RadioModulation.AM,IsShip,IsDropped)
end
end
end
return self
end
function CTLD:IsUnitInZone(Unit,Zonetype)
self:T(self.lid.." IsUnitInZone")
self:T(Zonetype)
local unitname=Unit:GetName()
local zonetable={}
local outcome=false
if Zonetype==CTLD.CargoZoneType.LOAD then
zonetable=self.pickupZones
elseif Zonetype==CTLD.CargoZoneType.DROP then
zonetable=self.dropOffZones
elseif Zonetype==CTLD.CargoZoneType.SHIP then
zonetable=self.shipZones
else
zonetable=self.wpZones
end
local zonecoord=nil
local colorret=nil
local maxdist=1000000
local zoneret=nil
local zonewret=nil
local zonenameret=nil
local unitcoord=Unit:GetCoordinate()
local unitVec2=unitcoord:GetVec2()
for _,_cargozone in pairs(zonetable)do
local czone=_cargozone
local zonename=czone.name
local active=czone.active
local color=czone.color
local zone=nil
local zoneradius=100
local zonewidth=20
if Zonetype==CTLD.CargoZoneType.SHIP then
self:T("Checking Type Ship: "..zonename)
local ZoneUNIT=UNIT:FindByName(zonename)
if not ZoneUNIT then return false end
zonecoord=ZoneUNIT:GetCoordinate()
zoneradius=czone.shiplength
zonewidth=czone.shipwidth
zone=ZONE_UNIT:New(ZoneUNIT:GetName(),ZoneUNIT,zoneradius/2)
elseif ZONE:FindByName(zonename)then
zone=ZONE:FindByName(zonename)
self:T("Checking Zone: "..zonename)
zonecoord=zone:GetCoordinate()
zonewidth=zoneradius
elseif AIRBASE:FindByName(zonename)then
zone=AIRBASE:FindByName(zonename):GetZone()
self:T("Checking Zone: "..zonename)
zonecoord=zone:GetCoordinate()
zoneradius=2000
zonewidth=zoneradius
end
local distance=self:_GetDistance(zonecoord,unitcoord)
self:T("Distance Zone: "..distance)
self:T("Zone Active: "..tostring(active))
if(zone:IsVec2InZone(unitVec2)or Zonetype==CTLD.CargoZoneType.MOVE)and active==true and distance<maxdist then
outcome=true
maxdist=distance
zoneret=zone
zonenameret=zonename
zonewret=zonewidth
colorret=color
end
end
if Zonetype==CTLD.CargoZoneType.SHIP then
return outcome,zonenameret,zoneret,maxdist,zonewret
else
return outcome,zonenameret,zoneret,maxdist
end
end
function CTLD:SmokePositionNow(Unit,Flare,SmokeColor)
self:T(self.lid.." SmokePositionNow")
local Smokecolor=self.SmokeColor or SMOKECOLOR.Red
if SmokeColor then
Smokecolor=SmokeColor
end
local FlareColor=self.FlareColor or FLARECOLOR.Red
local unitcoord=Unit:GetCoordinate()
local Group=Unit:GetGroup()
if Flare then
unitcoord:Flare(FlareColor,90)
else
local height=unitcoord:GetLandHeight()+2
unitcoord.y=height
unitcoord:Smoke(Smokecolor)
end
return self
end
function CTLD:SmokeZoneNearBy(Unit,Flare)
self:T(self.lid.." SmokeZoneNearBy")
local unitcoord=Unit:GetCoordinate()
local Group=Unit:GetGroup()
local smokedistance=self.smokedistance
local smoked=false
local zones={[1]=self.pickupZones,[2]=self.wpZones,[3]=self.dropOffZones,[4]=self.shipZones}
for i=1,4 do
for index,cargozone in pairs(zones[i])do
local CZone=cargozone
local zonename=CZone.name
local zone=nil
if i==4 then
zone=UNIT:FindByName(zonename)
else
zone=ZONE:FindByName(zonename)
if not zone then
zone=AIRBASE:FindByName(zonename):GetZone()
end
end
local zonecoord=zone:GetCoordinate()
local active=CZone.active
local color=CZone.color
local distance=self:_GetDistance(zonecoord,unitcoord)
if distance<smokedistance and active then
if not Flare then
zonecoord:Smoke(color or SMOKECOLOR.White)
else
if color==SMOKECOLOR.Blue then color=FLARECOLOR.White end
zonecoord:Flare(color or FLARECOLOR.White)
end
local txt="smoking"
if Flare then txt="flaring"end
self:_SendMessage(string.format("Roger, %s zone %s!",txt,zonename),10,false,Group)
smoked=true
end
end
end
if not smoked then
local distance=UTILS.MetersToNM(self.smokedistance)
self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,false,Group)
end
return self
end
function CTLD:SetUnitCapabilities(Unittype,Cancrates,Cantroops,Cratelimit,Trooplimit,Length,Maxcargoweight)
self:T(self.lid.." UnitCapabilities")
local unittype=nil
local unit=nil
if type(Unittype)=="string"then
unittype=Unittype
elseif type(Unittype)=="table"and Unittype.ClassName and Unittype:IsInstanceOf("UNIT")then
unittype=Unittype:GetTypeName()
else
return self
end
local length=20
local maxcargo=500
local existingcaps=self.UnitTypeCapabilities[unittype]
if existingcaps then
length=existingcaps.length or 20
maxcargo=existingcaps.cargoweightlimit or 500
end
local capabilities={}
capabilities.type=unittype
capabilities.crates=Cancrates or false
capabilities.troops=Cantroops or false
capabilities.cratelimit=Cratelimit or 0
capabilities.trooplimit=Trooplimit or 0
capabilities.length=Length or length
capabilities.cargoweightlimit=Maxcargoweight or maxcargo
self.UnitTypeCapabilities[unittype]=capabilities
return self
end
function CTLD:SetOwnSetPilotGroups(Set)
self.UserSetGroup=Set
return self
end
function CTLD:UnitCapabilities(Unittype,Cancrates,Cantroops,Cratelimit,Trooplimit,Length,Maxcargoweight)
self:I(self.lid.."This function been replaced with `SetUnitCapabilities()` - pls use the new one going forward!")
self:SetUnitCapabilities(Unittype,Cancrates,Cantroops,Cratelimit,Trooplimit,Length,Maxcargoweight)
return self
end
function CTLD:IsCorrectHover(Unit)
self:T(self.lid.." IsCorrectHover")
local outcome=false
if self:IsUnitInAir(Unit)then
local uspeed=Unit:GetVelocityMPS()
local uheight=Unit:GetHeight()
local ucoord=Unit:GetCoordinate()
if not ucoord then
return false
end
local gheight=ucoord:GetLandHeight()
local aheight=uheight-gheight
local maxh=self.maximumHoverHeight
local minh=self.minimumHoverHeight
local mspeed=2
if(uspeed<=mspeed)and(aheight<=maxh)and(aheight>=minh)then
outcome=true
end
end
return outcome
end
function CTLD:IsCorrectFlightParameters(Unit)
self:T(self.lid.." IsCorrectFlightParameters")
local outcome=false
if self:IsUnitInAir(Unit)then
local uspeed=Unit:GetVelocityMPS()
local uheight=Unit:GetHeight()
local ucoord=Unit:GetCoordinate()
if not ucoord then
return false
end
local gheight=ucoord:GetLandHeight()
local aheight=uheight-gheight
local minh=self.FixedMinAngels
local maxh=self.FixedMaxAngels
local maxspeed=self.FixedMaxSpeed
local kmspeed=uspeed*3.6
local knspeed=kmspeed/1.86
self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed))
if(aheight<=maxh)and(aheight>=minh)and(uspeed<=maxspeed)then
outcome=true
end
end
return outcome
end
function CTLD:_ShowHoverParams(Group,Unit)
local inhover=self:IsCorrectHover(Unit)
local htxt="true"
if not inhover then htxt="false"end
local text=""
if _SETTINGS:IsMetric()then
text=string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s",self.minimumHoverHeight,self.maximumHoverHeight,htxt)
else
local minheight=UTILS.MetersToFeet(self.minimumHoverHeight)
local maxheight=UTILS.MetersToFeet(self.maximumHoverHeight)
text=string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s",minheight,maxheight,htxt)
end
self:_SendMessage(text,10,false,Group)
return self
end
function CTLD:_ShowFlightParams(Group,Unit)
local inhover=self:IsCorrectFlightParameters(Unit)
local htxt="true"
if not inhover then htxt="false"end
local text=""
if _SETTINGS:IsImperial()then
local minheight=UTILS.MetersToFeet(self.FixedMinAngels)
local maxheight=UTILS.MetersToFeet(self.FixedMaxAngels)
text=string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s",minheight,maxheight,htxt)
else
local minheight=self.FixedMinAngels
local maxheight=self.FixedMaxAngels
text=string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s",minheight,maxheight,htxt)
end
self:_SendMessage(text,10,false,Group)
return self
end
function CTLD:CanHoverLoad(Unit)
self:T(self.lid.." CanHoverLoad")
if self:IsFixedWing(Unit)then return false end
local outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)and self:IsCorrectHover(Unit)
if not outcome then
outcome=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
return outcome
end
function CTLD:IsUnitInAir(Unit)
local minheight=self.minimumHoverHeight
if self.enableFixedWing and self:IsFixedWing(Unit)then
minheight=5.1
end
local uheight=Unit:GetHeight()
local ucoord=Unit:GetCoordinate()
if not ucoord then
return false
end
local gheight=ucoord:GetLandHeight()
local aheight=uheight-gheight
if aheight>=minheight then
return true
else
return false
end
end
function CTLD:AutoHoverLoad(Unit)
self:T(self.lid.." AutoHoverLoad")
local unittype=Unit:GetTypeName()
local unitname=Unit:GetName()
local Group=Unit:GetGroup()
local capabilities=self:_GetUnitCapabilities(Unit)
local cancrates=capabilities.crates
local cratelimit=capabilities.cratelimit
if cancrates then
local numberonboard=0
local loaded={}
if self.Loaded_Cargo[unitname]then
loaded=self.Loaded_Cargo[unitname]
numberonboard=loaded.Cratesloaded or 0
end
local load=cratelimit-numberonboard
local canload=self:CanHoverLoad(Unit)
if canload and load>0 then
self:_LoadCratesNearby(Group,Unit)
end
end
return self
end
function CTLD:CheckAutoHoverload()
if self.hoverautoloading then
for _,_pilot in pairs(self.CtldUnits)do
local Unit=UNIT:FindByName(_pilot)
if self:CanHoverLoad(Unit)then self:AutoHoverLoad(Unit)end
end
end
return self
end
function CTLD:CleanDroppedTroops()
local troops=self.DroppedTroops
local newtable={}
for _index,_group in pairs(troops)do
self:T({_group.ClassName})
if _group and _group.ClassName=="GROUP"then
if _group:IsAlive()then
newtable[_index]=_group
end
end
end
self.DroppedTroops=newtable
local engineers=self.EngineersInField
local engtable={}
for _index,_group in pairs(engineers)do
self:T({_group.ClassName})
if _group and _group:IsNotStatus("Stopped")then
engtable[_index]=_group
end
end
self.EngineersInField=engtable
return self
end
function CTLD:_CountStockPlusInHeloPlusAliveGroups(Restock,Threshold)
local Troopstable={}
for _id,_cargo in pairs(self.Cargo_Crates)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 _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
self:E(self.lid.."Group without Cargo Generic: ".._group:GetName())
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.Name
local gcargo=self:_FindCratesCargoObject(gname)or self:_FindTroopsCargoObject(gname)
self:T("Looking at "..gname.." in the helo - type = "..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)
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 leftover<Troopstable[genname].Stock then
Troopstable[genname].Stock=leftover
end
Troopstable[genname].Sum=Troopstable[genname].Stock+Troopstable[genname].Infield+Troopstable[genname].Inhelo+Troopstable[genname].CratesInfield
end
end
end
end
end
if Restock==true then
local threshold=Threshold or 75
for _name,_data in pairs(Troopstable)do
if _data.StockR and _data.StockR<threshold then
if _data.GenericCargo then
_data.GenericCargo:SetStock(_data.Stock0)
end
end
end
end
return Troopstable
end
function CTLD:AddStockTroops(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:AddStock(number)
break
end
end
return self
end
function CTLD:AddStockCrates(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Crates
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:AddStock(number)
break
end
end
return self
end
function CTLD:AddStockStatics(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Statics
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:AddStock(number)
break
end
end
return self
end
function CTLD:SetStockCrates(Name,Number)
local name=Name or"none"
local number=Number
local gentroops=self.Cargo_Crates
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:SetStock(number)
break
end
end
return self
end
function CTLD:SetStockTroops(Name,Number)
local name=Name or"none"
local number=Number
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:SetStock(number)
break
end
end
return self
end
function CTLD:SetStockStatics(Name,Number)
local name=Name or"none"
local number=Number
local gentroops=self.Cargo_Statics
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:SetStock(number)
break
end
end
return self
end
function CTLD:GetStockCrates()
local Stock={}
local gentroops=self.Cargo_Crates
for _id,_troop in pairs(gentroops)do
Stock[_troop.Name]=_troop.Stock or-1
end
return Stock
end
function CTLD:GetStockTroops()
local Stock={}
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
Stock[_troop.Name]=_troop.Stock or-1
end
return Stock
end
function CTLD:GetLoadedCargo(Unit)
local Troops=0
local Crates=0
local Cargo={}
if Unit and Unit:IsAlive()then
local name=Unit:GetName()
if self.Loaded_Cargo[name]then
Troops=self.Loaded_Cargo[name].Troopsloaded or 0
Crates=self.Loaded_Cargo[name].Cratesloaded or 0
Cargo=self.Loaded_Cargo[name].Cargo or{}
end
end
return Troops,Crates,Cargo
end
function CTLD:GetStockStatics()
local Stock={}
local gentroops=self.Cargo_Statics
for _id,_troop in pairs(gentroops)do
Stock[_troop.Name]=_troop.Stock or-1
end
return Stock
end
function CTLD:RemoveStockTroops(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Troops
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:RemoveStock(number)
end
end
return self
end
function CTLD:RemoveStockCrates(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Crates
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:RemoveStock(number)
end
end
return self
end
function CTLD:RemoveStockStatics(Name,Number)
local name=Name or"none"
local number=Number or 1
local gentroops=self.Cargo_Statics
for _id,_troop in pairs(gentroops)do
if _troop.Name==name then
_troop:RemoveStock(number)
end
end
return self
end
function CTLD:GetGenericCargoObjectFromGroupName(GroupName)
local Cargotype=nil
local template=GroupName
if string.find(template,"#")then
template=string.gsub(GroupName,"#(%d+)$","")
end
template=string.gsub(template,"-(%d+)$","")
for k,v in pairs(self.Cargo_Troops)do
local comparison=""
if type(v.Templates)=="string"then comparison=v.Templates else comparison=v.Templates[1]end
if comparison==template then
Cargotype=v
break
end
end
if not Cargotype then
for k,v in pairs(self.Cargo_Crates)do
local comparison=""
if type(v.Templates)=="string"then comparison=v.Templates else comparison=v.Templates[1]end
if comparison==template and v.CargoType~=CTLD_CARGO.Enum.REPAIR then
Cargotype=v
break
end
end
end
return Cargotype
end
function CTLD:_CheckEngineers()
self:T(self.lid.." CheckEngineers")
local engtable=self.EngineersInField
for _ind,_engineers in pairs(engtable)do
local engineers=_engineers
local wrenches=engineers.Group
self:T(_engineers.lid.._engineers:GetStatus())
if wrenches and wrenches:IsAlive()then
if engineers:IsStatus("Running")or engineers:IsStatus("Searching")then
local crates,number=self:_FindCratesNearby(wrenches,nil,self.EngineerSearch,true,true)
engineers:Search(crates,number)
elseif engineers:IsStatus("Moving")then
engineers:Move()
elseif engineers:IsStatus("Arrived")then
engineers:Build()
local unit=wrenches:GetUnit(1)
self:_BuildCrates(wrenches,unit,true)
self:_RepairCrates(wrenches,unit,true)
engineers:Done()
end
else
engineers:Stop()
end
end
return self
end
function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp)
self:T(self.lid.." InjectTroops")
local cargo=Cargo
local function IsTroopsMatch(cargo)
local match=false
local cgotbl=self.Cargo_Troops
local name=cargo:GetName()
local CargoObject
local CargoName
for _,_cgo in pairs(cgotbl)do
local cname=_cgo:GetName()
if name==cname then
match=true
CargoObject=_cgo
CargoName=cname
break
end
end
return match,CargoObject,CargoName
end
local function Cruncher(group,typename,anzahl)
local units=group:GetUnits()
local reduced=0
for _,_unit in pairs(units)do
local typo=_unit:GetTypeName()
if typename==typo then
_unit:Destroy(false)
reduced=reduced+1
if reduced==anzahl then break end
end
end
end
local function PostSpawn(args)
local group=args[1]
local structure=args[2]
if structure then
local loadedstructure={}
local strcset=UTILS.Split(structure,";")
for _,_data in pairs(strcset)do
local datasplit=UTILS.Split(_data,"==")
loadedstructure[datasplit[1]]=tonumber(datasplit[2])
end
local originalstructure=UTILS.GetCountPerTypeName(group)
for _name,_number in pairs(originalstructure)do
local loadednumber=0
if loadedstructure[_name]then
loadednumber=loadedstructure[_name]
end
local reduce=false
if loadednumber<_number then reduce=true end
if reduce then
Cruncher(group,_name,_number-loadednumber)
end
end
end
end
local match,CargoObject,CargoName=IsTroopsMatch(cargo)
if not match then
self.CargoCounter=self.CargoCounter+1
cargo.ID=self.CargoCounter
table.insert(self.Cargo_Troops,cargo)
end
if match and CargoObject then
local stock=CargoObject:GetStock()
if stock~=-1 and stock~=nil and stock==0 then
self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.")
return
else
CargoObject:RemoveStock(1)
end
end
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.TROOPS or type==CTLD_CARGO.Enum.ENGINEERS)then
local name=cargo:GetName()or"none"
local temptable=cargo:GetTemplates()or{}
local factor=1.5
local zone=Zone
local randomcoord=zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2()
if PreciseLocation then
randomcoord=zone:GetCoordinate():GetVec2()
end
local randompositions=not PreciseLocation
for _,_template in pairs(temptable)do
self.TroopCounter=self.TroopCounter+1
local alias=string.format("%s-%d",_template,math.random(1,100000))
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(randompositions,20,2)
:InitDelayOff()
:OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp)
:SpawnFromVec2(randomcoord)
if self.movetroopstowpzone and type~=CTLD_CARGO.Enum.ENGINEERS then
self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter])
end
end
cargo:SetWasDropped(true)
if type==CTLD_CARGO.Enum.ENGINEERS then
self.Engineers=self.Engineers+1
local grpname=self.DroppedTroops[self.TroopCounter]:GetName()
self.EngineersInField[self.Engineers]=CTLD_ENGINEERING:New(name,grpname)
end
if Structure then
BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure})
end
if self.keeploadtable and TimeStamp~=nil then
self:T2("Inserting: "..cargo.CargoType)
local cargotype=type
table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter],TimeStamp=TimeStamp,CargoType=cargotype,CargoName=name})
end
if self.eventoninject then
self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter],type)
end
end
return self
end
function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp)
self:T(self.lid.." InjectVehicles")
local cargo=Cargo
local function IsVehicMatch(cargo)
local match=false
local cgotbl=self.Cargo_Crates
local name=cargo:GetName()
local CargoObject
local CargoName
for _,_cgo in pairs(cgotbl)do
local cname=_cgo:GetName()
if name==cname then
match=true
CargoObject=_cgo
CargoName=cname
break
end
end
return match,CargoObject,CargoName
end
local function Cruncher(group,typename,anzahl)
local units=group:GetUnits()
local reduced=0
for _,_unit in pairs(units)do
local typo=_unit:GetTypeName()
if typename==typo then
_unit:Destroy(false)
reduced=reduced+1
if reduced==anzahl then break end
end
end
end
local function PostSpawn(args)
local group=args[1]
local structure=args[2]
if structure then
local loadedstructure={}
local strcset=UTILS.Split(structure,";")
for _,_data in pairs(strcset)do
local datasplit=UTILS.Split(_data,"==")
loadedstructure[datasplit[1]]=tonumber(datasplit[2])
end
local originalstructure=UTILS.GetCountPerTypeName(group)
for _name,_number in pairs(originalstructure)do
local loadednumber=0
if loadedstructure[_name]then
loadednumber=loadedstructure[_name]
end
local reduce=false
if loadednumber<_number then reduce=true end
if reduce then
Cruncher(group,_name,_number-loadednumber)
end
end
end
end
local match,CargoObject,CargoName=IsVehicMatch(cargo)
if not match then
self.CargoCounter=self.CargoCounter+1
cargo.ID=self.CargoCounter
table.insert(self.Cargo_Crates,cargo)
end
if match and CargoObject then
local stock=CargoObject:GetStock()
if stock~=-1 and stock~=nil and stock==0 then
self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.")
return
else
CargoObject:RemoveStock(1)
end
end
local type=cargo:GetType()
if(type==CTLD_CARGO.Enum.VEHICLE or type==CTLD_CARGO.Enum.FOB)then
local name=cargo:GetName()or"none"
local temptable=cargo:GetTemplates()or{}
local factor=1.5
local zone=Zone
local randomcoord=zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2()
if PreciseLocation then
randomcoord=zone:GetCoordinate():GetVec2()
end
cargo:SetWasDropped(true)
local canmove=false
if type==CTLD_CARGO.Enum.VEHICLE then canmove=true end
for _,_template in pairs(temptable)do
self.TroopCounter=self.TroopCounter+1
local alias=string.format("%s-%d",_template,math.random(1,100000))
if canmove then
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(true,20,2)
:InitDelayOff()
:OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp)
:SpawnFromVec2(randomcoord)
else
self.DroppedTroops[self.TroopCounter]=SPAWN:NewWithAlias(_template,alias)
:InitDelayOff()
:OnSpawnGroup(function(grp,TimeStamp)grp.spawntime=TimeStamp or timer.getTime()end,TimeStamp)
:SpawnFromVec2(randomcoord)
end
if Structure then
BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure})
end
if self.keeploadtable and TimeStamp~=nil then
self:T2("Inserting: "..cargo.CargoType)
local cargotype=type
table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter],TimeStamp=TimeStamp,CargoType=cargotype,CargoName=name})
end
if self.eventoninject then
self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter])
end
end
end
return self
end
function CTLD:onafterStart(From,Event,To)
self:T({From,Event,To})
self:I(self.lid.."Started ("..self.version..")")
if self.enableHercules then self.enableFixedWing=true end
if self.UserSetGroup then
self.PilotGroups=self.UserSetGroup
elseif self.useprefix or self.enableFixedWing then
local prefix=self.prefixes
if self.enableFixedWing then
self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart()
else
self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart()
end
else
self.PilotGroups=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart()
end
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
self:HandleEvent(EVENTS.NewDynamicCargo,self._EventHandler)
self:HandleEvent(EVENTS.DynamicCargoLoaded,self._EventHandler)
self:HandleEvent(EVENTS.DynamicCargoUnloaded,self._EventHandler)
self:HandleEvent(EVENTS.DynamicCargoRemoved,self._EventHandler)
self:HandleEvent(EVENTS.Land,self._EventHandler)
self:HandleEvent(EVENTS.Takeoff,self._EventHandler)
self:__Status(-5)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CTLD:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
self:CleanDroppedTroops()
self:_RefreshF10Menus()
self:CheckDroppedBeacons()
self:_RefreshRadioBeacons()
self:CheckAutoHoverload()
self:_CheckEngineers()
return self
end
function CTLD:onafterStatus(From,Event,To)
self:T({From,Event,To})
local pilots=0
for _,_pilot in pairs(self.CtldUnits)do
pilots=pilots+1
end
local boxes=0
for _,_pilot in pairs(self.Spawned_Cargo)do
boxes=boxes+1
end
local cc=self.CargoCounter
local tc=self.TroopCounter
if self.debug or self.verbose>0 then
local text=string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d",self.lid,pilots,boxes,cc,tc)
local m=MESSAGE:New(text,10,"CTLD"):ToAll()
if self.verbose>0 then
self:I(self.lid.."Cargo and Troops in Stock:")
for _,_troop in pairs(self.Cargo_Crates)do
local name=_troop:GetName()
local stock=_troop:GetStock()
self:I(string.format("-- %s \t\t\t %d",name,stock))
end
for _,_troop in pairs(self.Cargo_Statics)do
local name=_troop:GetName()
local stock=_troop:GetStock()
self:I(string.format("-- %s \t\t\t %d",name,stock))
end
for _,_troop in pairs(self.Cargo_Troops)do
local name=_troop:GetName()
local stock=_troop:GetStock()
self:I(string.format("-- %s \t\t %d",name,stock))
end
end
end
self:__Status(-30)
return self
end
function CTLD:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.UnitLost)
self:UnHandleEvent(EVENTS.Shot)
return self
end
function CTLD:onbeforeTroopsPickedUp(From,Event,To,Group,Unit,Cargo)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeCratesPickedUp(From,Event,To,Group,Unit,Cargo)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeTroopsExtracted(From,Event,To,Group,Unit,Troops,Groupname)
self:T({From,Event,To})
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
local targetzone=task.Target:GetObject()
local okaygroup=string.find(Groupname,task:GetProperty("ExtractName"),1,true)
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and okaygroup then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
end
)
end
return self
end
function CTLD:onbeforeTroopsDeployed(From,Event,To,Group,Unit,Troops)
self:T({From,Event,To})
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
local dropcoord=Troops:GetCoordinate()or COORDINATE:New(0,0,0)
local dropvec2=dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
local targetzone=task.Target:GetObject()
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
end
)
end
return self
end
function CTLD:onafterTroopsDeployed(From,Event,To,Group,Unit,Troops,Type)
self:T({From,Event,To})
if self.movetroopstowpzone and Type~=CTLD_CARGO.Enum.ENGINEERS then
self:_MoveGroupToZone(Troops)
end
return self
end
function CTLD:onbeforeCratesDropped(From,Event,To,Group,Unit,Cargotable)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeCratesBuild(From,Event,To,Group,Unit,Vehicle)
self:T({From,Event,To})
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
local dropcoord=Vehicle:GetCoordinate()or COORDINATE:New(0,0,0)
local dropvec2=dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
local targetzone=task.Target:GetObject()
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2)then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
end
)
end
return self
end
function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle)
self:T({From,Event,To})
if self.movetroopstowpzone then
self:_MoveGroupToZone(Vehicle)
end
return self
end
function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject)
self:T({From,Event,To})
return self
end
function CTLD:onbeforeSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
function CTLD:onafterSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
if lfs then
path=self.filepath or lfs.writedir()
end
filename=filename or self.filename
if path~=nil then
filename=path.."\\"..filename
end
local grouptable=self.DroppedTroops
local cgovehic=self.Cargo_Crates
local cgotable=self.Cargo_Troops
local stcstable=self.Spawned_Cargo
local statics=nil
local statics={}
self:T(self.lid.."Building Statics Table for Saving")
for _,_cargo in pairs(stcstable)do
local cargo=_cargo
local object=cargo:GetPositionable()
if object and object:IsAlive()and(cargo:WasDropped()or not cargo:HasMoved())then
statics[#statics+1]=cargo
end
end
local function FindCargoType(name,table)
local match=false
local cargo=nil
name=string.gsub(name,"-"," ")
for _ind,_cargo in pairs(table)do
local thiscargo=_cargo
local template=thiscargo:GetTemplates()
if type(template)=="string"then
template={template}
end
for _,_name in pairs(template)do
_name=string.gsub(_name,"-"," ")
if string.find(name,_name)and _cargo:GetType()~=CTLD_CARGO.Enum.REPAIR then
match=true
cargo=thiscargo
end
end
if match then break end
end
return match,cargo
end
local data="Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape,SpawnTime\n"
local n=0
for _,_grp in pairs(grouptable)do
local group=_grp
if group and group:IsAlive()then
local name=group:GetName()
local template=name
if string.find(template,"#")then
template=string.gsub(name,"#(%d+)$","")
end
local template=string.gsub(name,"-(%d+)$","")
local match,cargo=FindCargoType(template,cgotable)
if not match then
match,cargo=FindCargoType(template,cgovehic)
end
if match then
n=n+1
local cargo=cargo
local cgoname=cargo.Name
local cgotemp=cargo.Templates
local cgotype=cargo.CargoType
local cgoneed=cargo.CratesNeeded
local cgomass=cargo.PerCrateMass
local scat,stype,sshape=cargo:GetStaticTypeAndShape()
local structure=UTILS.GetCountPerTypeName(group)
local strucdata=""
for typen,anzahl in pairs(structure)do
strucdata=strucdata..typen.."=="..anzahl..";"
end
local spawntime=group.spawntime or timer.getTime()+n
if type(cgotemp)=="table"then
local templates="{"
for _,_tmpl in pairs(cgotemp)do
templates=templates.._tmpl..";"
end
templates=templates.."}"
cgotemp=templates
end
local location=group:GetVec3()
local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s,%f\n"
,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or"none",spawntime)
data=data..txt
end
end
end
for _,_cgo in pairs(statics)do
local object=_cgo
local cgoname=object.Name
local cgotemp=object.Templates
if type(cgotemp)=="table"then
local templates="{"
for _,_tmpl in pairs(cgotemp)do
templates=templates.._tmpl..";"
end
templates=templates.."}"
cgotemp=templates
end
local cgotype=object.CargoType
local cgoneed=object.CratesNeeded
local cgomass=object.PerCrateMass
local crateobj=object.Positionable
local location=crateobj:GetVec3()
local scat,stype,sshape=object:GetStaticTypeAndShape()
local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,'none',%s,%s,%s\n"
,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,scat,stype,sshape or"none")
data=data..txt
end
_savefile(filename,data)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CTLD:onbeforeLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
filename=filename or self.filename
path=path or self.filepath
if not io then
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename))
return false
end
end
function CTLD:onafterLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
filename=filename or self.filename
path=path or self.filepath
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading CTLD state from file %s",filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local file=assert(io.open(filename,"rb"))
local loadeddata={}
for line in file:lines()do
loadeddata[#loadeddata+1]=line
end
file:close()
table.remove(loadeddata,1)
local n=0
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local groupname=dataset[1]
local vec2={}
vec2.x=tonumber(dataset[2])
vec2.y=tonumber(dataset[4])
local cargoname=dataset[5]
local cargotemplates=dataset[6]
local cargotype=dataset[7]
local size=tonumber(dataset[8])
local mass=tonumber(dataset[9])
local StaticCategory=dataset[11]
local StaticType=dataset[12]
local StaticShape=dataset[13]
n=n+1
local timestamp=tonumber(dataset[14])or(timer.getTime()+n)
self:T2("TimeStamp = "..timestamp)
if type(groupname)=="string"and groupname~="STATIC"then
cargotemplates=string.gsub(cargotemplates,"{","")
cargotemplates=string.gsub(cargotemplates,"}","")
cargotemplates=UTILS.Split(cargotemplates,";")
local structure=nil
if dataset[10]and dataset[10]~="none"then
structure=dataset[10]
structure=string.gsub(structure,",","")
end
local dropzone=ZONE_RADIUS:New("DropZone",vec2,20)
if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then
local injectvehicle=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape)
self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp)
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
local dropzone=ZONE_RADIUS:New("DropZone",vec2,20)
local injectstatic=nil
if cargotype==CTLD_CARGO.Enum.VEHICLE or cargotype==CTLD_CARGO.Enum.FOB then
cargotemplates=string.gsub(cargotemplates,"{","")
cargotemplates=string.gsub(cargotemplates,"}","")
cargotemplates=UTILS.Split(cargotemplates,";")
injectstatic=CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
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)
end
if injectstatic then
self:InjectStatics(dropzone,injectstatic,false,true)
end
end
end
if self.keeploadtable then
self:__Loaded(1,self.LoadedGroupsTable)
end
return self
end
end
do
CTLD_HERCULES={
ClassName="CTLD_HERCULES",
lid="",
Name="",
Version="0.0.3",
}
CTLD_HERCULES.Types={
["ATGM M1045 HMMWV TOW Air [7183lb]"]={['name']="M1045 HMMWV TOW",['container']=true},
["ATGM M1045 HMMWV TOW Skid [7073lb]"]={['name']="M1045 HMMWV TOW",['container']=false},
["APC M1043 HMMWV Armament Air [7023lb]"]={['name']="M1043 HMMWV Armament",['container']=true},
["APC M1043 HMMWV Armament Skid [6912lb]"]={['name']="M1043 HMMWV Armament",['container']=false},
["SAM Avenger M1097 Air [7200lb]"]={['name']="M1097 Avenger",['container']=true},
["SAM Avenger M1097 Skid [7090lb]"]={['name']="M1097 Avenger",['container']=false},
["APC Cobra Air [10912lb]"]={['name']="Cobra",['container']=true},
["APC Cobra Skid [10802lb]"]={['name']="Cobra",['container']=false},
["APC M113 Air [21624lb]"]={['name']="M-113",['container']=true},
["APC M113 Skid [21494lb]"]={['name']="M-113",['container']=false},
["Tanker M978 HEMTT [34000lb]"]={['name']="M978 HEMTT Tanker",['container']=false},
["HEMTT TFFT [34400lb]"]={['name']="HEMTT TFFT",['container']=false},
["SPG M1128 Stryker MGS [33036lb]"]={['name']="M1128 Stryker MGS",['container']=false},
["AAA Vulcan M163 Air [21666lb]"]={['name']="Vulcan",['container']=true},
["AAA Vulcan M163 Skid [21577lb]"]={['name']="Vulcan",['container']=false},
["APC M1126 Stryker ICV [29542lb]"]={['name']="M1126 Stryker ICV",['container']=false},
["ATGM M1134 Stryker [30337lb]"]={['name']="M1134 Stryker ATGM",['container']=false},
["APC LAV-25 Air [22520lb]"]={['name']="LAV-25",['container']=true},
["APC LAV-25 Skid [22514lb]"]={['name']="LAV-25",['container']=false},
["M1025 HMMWV Air [6160lb]"]={['name']="Hummer",['container']=true},
["M1025 HMMWV Skid [6050lb]"]={['name']="Hummer",['container']=false},
["IFV M2A2 Bradley [34720lb]"]={['name']="M-2 Bradley",['container']=false},
["IFV MCV-80 [34720lb]"]={['name']="MCV-80",['container']=false},
["IFV BMP-1 [23232lb]"]={['name']="BMP-1",['container']=false},
["IFV BMP-2 [25168lb]"]={['name']="BMP-2",['container']=false},
["IFV BMP-3 [32912lb]"]={['name']="BMP-3",['container']=false},
["ARV BRDM-2 Air [12320lb]"]={['name']="BRDM-2",['container']=true},
["ARV BRDM-2 Skid [12210lb]"]={['name']="BRDM-2",['container']=false},
["APC BTR-80 Air [23936lb]"]={['name']="BTR-80",['container']=true},
["APC BTR-80 Skid [23826lb]"]={['name']="BTR-80",['container']=false},
["APC BTR-82A Air [24998lb]"]={['name']="BTR-82A",['container']=true},
["APC BTR-82A Skid [24888lb]"]={['name']="BTR-82A",['container']=false},
["SAM ROLAND ADS [34720lb]"]={['name']="Roland Radar",['container']=false},
["SAM ROLAND LN [34720b]"]={['name']="Roland ADS",['container']=false},
["SAM SA-13 STRELA [21624lb]"]={['name']="Strela-10M3",['container']=false},
["AAA ZSU-23-4 Shilka [32912lb]"]={['name']="ZSU-23-4 Shilka",['container']=false},
["SAM SA-19 Tunguska 2S6 [34720lb]"]={['name']="2S6 Tunguska",['container']=false},
["Transport UAZ-469 Air [3747lb]"]={['name']="UAZ-469",['container']=true},
["Transport UAZ-469 Skid [3630lb]"]={['name']="UAZ-469",['container']=false},
["AAA GEPARD [34720lb]"]={['name']="Gepard",['container']=false},
["SAM CHAPARRAL Air [21624lb]"]={['name']="M48 Chaparral",['container']=true},
["SAM CHAPARRAL Skid [21516lb]"]={['name']="M48 Chaparral",['container']=false},
["SAM LINEBACKER [34720lb]"]={['name']="M6 Linebacker",['container']=false},
["Transport URAL-375 [14815lb]"]={['name']="Ural-375",['container']=false},
["Transport M818 [16000lb]"]={['name']="M 818",['container']=false},
["IFV MARDER [34720lb]"]={['name']="Marder",['container']=false},
["Transport Tigr Air [15900lb]"]={['name']="Tigr_233036",['container']=true},
["Transport Tigr Skid [15730lb]"]={['name']="Tigr_233036",['container']=false},
["IFV TPZ FUCH [33440lb]"]={['name']="TPZ",['container']=false},
["IFV BMD-1 Air [18040lb]"]={['name']="BMD-1",['container']=true},
["IFV BMD-1 Skid [17930lb]"]={['name']="BMD-1",['container']=false},
["IFV BTR-D Air [18040lb]"]={['name']="BTR_D",['container']=true},
["IFV BTR-D Skid [17930lb]"]={['name']="BTR_D",['container']=false},
["EWR SBORKA Air [21624lb]"]={['name']="Dog Ear radar",['container']=true},
["EWR SBORKA Skid [21624lb]"]={['name']="Dog Ear radar",['container']=false},
["ART 2S9 NONA Air [19140lb]"]={['name']="SAU 2-C9",['container']=true},
["ART 2S9 NONA Skid [19030lb]"]={['name']="SAU 2-C9",['container']=false},
["ART GVOZDIKA [34720lb]"]={['name']="SAU Gvozdika",['container']=false},
["APC MTLB Air [26400lb]"]={['name']="MTLB",['container']=true},
["APC MTLB Skid [26290lb]"]={['name']="MTLB",['container']=false},
}
function CTLD_HERCULES:New(Coalition,Alias,CtldObject)
local self=BASE:Inherit(self,FSM:New())
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in CTLD!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="UNHCR"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="Red CTLD Hercules"
elseif self.coalition==coalition.side.BLUE then
self.alias="Blue CTLD Hercules"
end
end
end
self.lid=string.format("%s (%s) | ",self.alias,self.coalitiontxt)
self.infantrytemplate="Infantry"
self.CTLD=CtldObject
self.verbose=true
self.j=0
self.carrierGroups={}
self.Cargo={}
self.ParatrooperCount={}
self.ObjectTracker={}
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:HandleEvent(EVENTS.Shot,self._HandleShot)
self:I(self.lid.."Started")
self:CheckTemplates()
return self
end
function CTLD_HERCULES:CheckTemplates()
self:T(self.lid..'CheckTemplates')
self.Types["Paratroopers 10"]={
name=self.infantrytemplate,
container=false,
available=false,
}
local missing={}
local nomissing=0
local found={}
local nofound=0
for _index,_tab in pairs(self.Types)do
local outcometxt="MISSING"
if _DATABASE.Templates.Groups[_tab.name]then
outcometxt="OK"
self.Types[_index].available=true
found[_tab.name]=true
else
self.Types[_index].available=false
missing[_tab.name]=true
end
if self.verbose then
self:I(string.format(self.lid.."Checking template for %s (%s) ... %s",_index,_tab.name,outcometxt))
end
end
for _,_name in pairs(found)do
nofound=nofound+1
end
for _,_name in pairs(missing)do
nomissing=nomissing+1
end
self:I(string.format(self.lid.."Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing))
return self
end
function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country,GroupSpacing)
self:T(self.lid..'Soldier_SpawnGroup')
self:T(Cargo_Drop_Position)
local InjectTroopsType=CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80)
local position=Cargo_Drop_Position:GetVec2()
local dropzone=ZONE_RADIUS:New("Infantry "..math.random(1,10000),position,100)
self.CTLD:InjectTroops(dropzone,InjectTroopsType)
return self
end
function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,Cargo_Country)
self:T(self.lid.."Cargo_SpawnGroup")
self:T(Cargo_Type_name)
if Cargo_Type_name~='Container red 1'then
local InjectVehicleType=CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000)
local position=Cargo_Drop_Position:GetVec2()
local dropzone=ZONE_RADIUS:New("Vehicle "..math.random(1,10000),position,100)
self.CTLD:InjectVehicles(dropzone,InjectVehicleType)
end
return self
end
function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position,Cargo_Type_name,CargoHeading,dead,Cargo_Country)
self:T(self.lid.."Cargo_SpawnStatic")
self:T("Static "..Cargo_Type_name.." Dead "..tostring(dead))
local position=Cargo_Drop_Position:GetVec2()
local Zone=ZONE_RADIUS:New("Cargo Static "..math.random(1,10000),position,100)
if not dead then
local injectstatic=CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1)
self.CTLD:InjectStatics(Zone,injectstatic,true,true)
end
return self
end
function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name,_pos)
local theCargo=self.CTLD:_FindCratesCargoObject(_name)
if theCargo then
self.CTLD.CrateCounter=self.CTLD.CrateCounter+1
local CCat,CType,CShape=theCargo:GetStaticTypeAndShape()
local basetype=CType or self.CTLD.basetype or"container_cargo"
CCat=CCat or"Cargos"
local theStatic=SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry)
:InitCargoMass(theCargo.PerCrateMass)
:InitCargo(self.CTLD.enableslingload)
:InitCoordinate(_pos)
if CShape then
theStatic:InitShape(CShape)
end
theStatic:Spawn(270,_name.."-Container-"..math.random(1,100000))
self.CTLD.Spawned_Crates[self.CTLD.CrateCounter]=theStatic
local newCargo=CTLD_CARGO:New(self.CTLD.CargoCounter,theCargo.Name,theCargo.Templates,theCargo.CargoType,true,false,theCargo.CratesNeeded,self.CTLD.Spawned_Crates[self.CTLD.CrateCounter],true,theCargo.PerCrateMass,nil,theCargo.Subcategory)
local map=theCargo:GetStaticResourceMap()
newCargo:SetStaticResourceMap(map)
table.insert(self.CTLD.Spawned_Cargo,newCargo)
newCargo:SetWasDropped(true)
newCargo:SetHasMoved(true)
end
return self
end
function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction,Cargo_Content_position,Cargo_Type_name,Cargo_over_water,Container_Enclosed,ParatrooperGroupSpawn,offload_cargo,all_cargo_survive_to_the_ground,all_cargo_gets_destroyed,destroy_cargo_dropped_without_parachute,Cargo_Country)
self:T(self.lid..'Cargo_SpawnObjects')
local CargoHeading=self.CargoHeading
if offload_cargo==true or ParatrooperGroupSpawn==true then
if ParatrooperGroupSpawn==true then
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,10)
else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country)
end
else
if all_cargo_gets_destroyed==true or Cargo_over_water==true then
else
if all_cargo_survive_to_the_ground==true then
if ParatrooperGroupSpawn==true then
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country)
else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country)
end
if Container_Enclosed==true then
if ParatrooperGroupSpawn==false then
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country)
end
end
end
if destroy_cargo_dropped_without_parachute==true then
if Container_Enclosed==true then
if ParatrooperGroupSpawn==true then
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country,0)
else
if self.CTLD.dropAsCargoCrate then
self:Cargo_SpawnDroppedAsCargo(Cargo_Type_name,Cargo_Content_position)
else
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,Cargo_Country)
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,"Hercules_Container_Parachute_Static",CargoHeading,false,Cargo_Country)
end
end
else
self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position,Cargo_Type_name,CargoHeading,true,Cargo_Country)
end
end
end
end
return self
end
function CTLD_HERCULES:Calculate_Object_Height_AGL(group)
self:T(self.lid.."Calculate_Object_Height_AGL")
if group.ClassName and group.ClassName=="GROUP"then
local gcoord=group:GetCoordinate()
local height=group:GetHeight()
local lheight=gcoord:GetLandHeight()
self:T(self.lid.."Height "..height-lheight)
return height-lheight
else
if group:isExist()then
local dcsposition=group:getPosition().p
local dcsvec2={x=dcsposition.x,y=dcsposition.z}
local height=math.floor(group:getPosition().p.y-land.getHeight(dcsvec2))
self.ObjectTracker[group.id_]=dcsposition
self:T(self.lid.."Height "..height)
return height
else
return 0
end
end
end
function CTLD_HERCULES:Check_SurfaceType(object)
self:T(self.lid.."Check_SurfaceType")
if object:isExist()then
return land.getSurfaceType({x=object:getPosition().p.x,y=object:getPosition().p.z})
else
return 1
end
end
function CTLD_HERCULES:Cargo_Track(cargo,initiator)
self:T(self.lid.."Cargo_Track")
local Cargo_Drop_initiator=initiator
if cargo.Cargo_Contents~=nil then
if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents)<10 then
if self:Check_SurfaceType(cargo.Cargo_Contents)==2 or self:Check_SurfaceType(cargo.Cargo_Contents)==3 then
cargo.Cargo_over_water=true
end
local dcsvec3=self.ObjectTracker[cargo.Cargo_Contents.id_]or initiator:GetVec3()
self:T("SPAWNPOSITION: ")
self:T({dcsvec3})
local Vec2={
x=dcsvec3.x,
y=dcsvec3.z,
}
local vec3=COORDINATE:NewFromVec2(Vec2)
self.ObjectTracker[cargo.Cargo_Contents.id_]=nil
self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction,vec3,cargo.Cargo_Type_name,cargo.Cargo_over_water,cargo.Container_Enclosed,cargo.ParatrooperGroupSpawn,cargo.offload_cargo,cargo.all_cargo_survive_to_the_ground,cargo.all_cargo_gets_destroyed,cargo.destroy_cargo_dropped_without_parachute,cargo.Cargo_Country)
if cargo.Cargo_Contents:isExist()then
cargo.Cargo_Contents:destroy()
end
cargo.scheduleFunctionID:Stop()
cargo={}
end
end
return self
end
function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point)
self:T(self.lid.."Calculate_Cargo_Drop_initiator_NorthCorrection")
if not point.z then
point.z=point.y
point.y=0
end
local lat,lon=coord.LOtoLL(point)
local north_posit=coord.LLtoLO(lat+1,lon)
return math.atan2(north_posit.z-point.z,north_posit.x-point.x)
end
function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator)
self:T(self.lid.."Calculate_Cargo_Drop_initiator_Heading")
local Heading=Cargo_Drop_initiator:GetHeading()
Heading=Heading+self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3())
if Heading<0 then
Heading=Heading+(2*math.pi)
end
return Heading+0.06
end
function CTLD_HERCULES:Cargo_Initialize(Initiator,Cargo_Contents,Cargo_Type_name,Container_Enclosed,SoldierGroup,ParatrooperGroupSpawnInit)
self:T(self.lid.."Cargo_Initialize")
local Cargo_Drop_initiator=Initiator:GetName()
if Cargo_Drop_initiator~=nil then
if ParatrooperGroupSpawnInit==true then
self:T("Paratrooper Drop")
if not self.ParatrooperCount[Cargo_Drop_initiator]then
self.ParatrooperCount[Cargo_Drop_initiator]=1
else
self.ParatrooperCount[Cargo_Drop_initiator]=self.ParatrooperCount[Cargo_Drop_initiator]+1
end
local Paratroopers=self.ParatrooperCount[Cargo_Drop_initiator]
self:T("Paratrooper Drop Number "..self.ParatrooperCount[Cargo_Drop_initiator])
local SpawnParas=false
if math.fmod(Paratroopers,10)==0 then
SpawnParas=true
end
self.j=self.j+1
self.Cargo[self.j]={}
self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator)
self.Cargo[self.j].Cargo_Contents=Cargo_Contents
self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name
self.Cargo[self.j].Container_Enclosed=Container_Enclosed
self.Cargo[self.j].ParatrooperGroupSpawn=SpawnParas
self.Cargo[self.j].Cargo_Country=Initiator:GetCountry()
if self:Calculate_Object_Height_AGL(Initiator)<5.0 then
self.Cargo[self.j].offload_cargo=true
elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then
self.Cargo[self.j].all_cargo_survive_to_the_ground=true
elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then
self.Cargo[self.j].all_cargo_gets_destroyed=true
else
self.Cargo[self.j].all_cargo_gets_destroyed=false
end
local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator)
self.Cargo[self.j].scheduleFunctionID=timer
timer:Start(1,1,600)
else
self.j=self.j+1
self.Cargo[self.j]={}
self.Cargo[self.j].Cargo_Drop_Direction=self:Calculate_Cargo_Drop_initiator_Heading(Initiator)
self.Cargo[self.j].Cargo_Contents=Cargo_Contents
self.Cargo[self.j].Cargo_Type_name=Cargo_Type_name
self.Cargo[self.j].Container_Enclosed=Container_Enclosed
self.Cargo[self.j].ParatrooperGroupSpawn=false
self.Cargo[self.j].Cargo_Country=Initiator:GetCountry()
if self:Calculate_Object_Height_AGL(Initiator)<5.0 then
self.Cargo[self.j].offload_cargo=true
elseif self:Calculate_Object_Height_AGL(Initiator)<10.0 then
self.Cargo[self.j].all_cargo_survive_to_the_ground=true
elseif self:Calculate_Object_Height_AGL(Initiator)<100.0 then
self.Cargo[self.j].all_cargo_gets_destroyed=true
else
self.Cargo[self.j].destroy_cargo_dropped_without_parachute=true
end
local timer=TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator)
self.Cargo[self.j].scheduleFunctionID=timer
timer:Start(1,1,600)
end
end
return self
end
function CTLD_HERCULES:SetType(key,cargoType,cargoNum)
self:T(self.lid.."SetType")
self.carrierGroups[key]['cargoType']=cargoType
self.carrierGroups[key]['cargoNum']=cargoNum
return self
end
function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event)
self:T(self.lid.."Shot Event ID:"..Cargo_Drop_Event.id)
if Cargo_Drop_Event.id==EVENTS.Shot then
local GT_Name=""
local SoldierGroup=false
local ParatrooperGroupSpawnInit=false
local GT_DisplayName=Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15,-1)
self:T(string.format("%sCargo_Drop_Event: %s",self.lid,Weapon.getDesc(Cargo_Drop_Event.weapon).typeName))
if(GT_DisplayName=="Squad 30 x Soldier [7950lb]")then
self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,"Soldier M4 GRG",false,true,true)
end
if self.Types[GT_DisplayName]then
local GT_Name=self.Types[GT_DisplayName]['name']
local Cargo_Container_Enclosed=self.Types[GT_DisplayName]['container']
self:Cargo_Initialize(Cargo_Drop_Event.IniGroup,Cargo_Drop_Event.weapon,GT_Name,Cargo_Container_Enclosed)
end
end
return self
end
function CTLD_HERCULES:_HandleBirth(event)
self:T(self.lid.."Birth Event ID:"..event.id)
return self
end
end
CSAR={
ClassName="CSAR",
verbose=0,
lid="",
coalition=1,
coalitiontxt="blue",
FreeVHFFrequencies={},
UsedVHFFrequencies={},
takenOff={},
csarUnits={},
downedPilots={},
landedStatus={},
addedTo={},
woundedGroups={},
inTransitGroups={},
smokeMarkers={},
heliVisibleMessage={},
heliCloseMessage={},
max_units=6,
hoverStatus={},
pilotDisabled={},
pilotLives={},
useprefix=true,
csarPrefix={},
template=nil,
mash={},
smokecolor=4,
rescues=0,
rescuedpilots=0,
limitmaxdownedpilots=true,
maxdownedpilots=10,
allheligroupset=nil,
topmenuname="CSAR",
ADFRadioPwr=1000,
PilotWeight=80,
CreateRadioBeacons=true,
UserSetGroup=nil,
AllowIRStrobe=false,
IRStrobeRuntime=300,
}
CSAR.AircraftType={}
CSAR.AircraftType["SA342Mistral"]=2
CSAR.AircraftType["SA342Minigun"]=2
CSAR.AircraftType["SA342L"]=4
CSAR.AircraftType["SA342M"]=4
CSAR.AircraftType["UH-1H"]=8
CSAR.AircraftType["Mi-8MTV2"]=12
CSAR.AircraftType["Mi-8MT"]=12
CSAR.AircraftType["Mi-24P"]=8
CSAR.AircraftType["Mi-24V"]=8
CSAR.AircraftType["Bell-47"]=2
CSAR.AircraftType["UH-60L"]=10
CSAR.AircraftType["AH-64D_BLK_II"]=2
CSAR.AircraftType["Bronco-OV-10A"]=2
CSAR.AircraftType["MH-60R"]=10
CSAR.AircraftType["OH-6A"]=2
CSAR.AircraftType["OH58D"]=2
CSAR.AircraftType["CH-47Fbl1"]=31
CSAR.version="1.0.31"
function CSAR:New(Coalition,Template,Alias)
local self=BASE:Inherit(self,FSM:New())
BASE:T({Coalition,Template,Alias})
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in CSAR!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
if Alias then
self.alias=tostring(Alias)
else
self.alias="Red Cross"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="IFRC"
elseif self.coalition==coalition.side.BLUE then
self.alias="CSAR"
end
end
end
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","PilotDown","*")
self:AddTransition("*","Approach","*")
self:AddTransition("*","Landed","*")
self:AddTransition("*","Boarded","*")
self:AddTransition("*","Returning","*")
self:AddTransition("*","Rescued","*")
self:AddTransition("*","KIA","*")
self:AddTransition("*","Load","*")
self:AddTransition("*","Save","*")
self:AddTransition("*","Stop","Stopped")
self.addedTo={}
self.allheligroupset={}
self.csarUnits={}
self.FreeVHFFrequencies={}
self.heliVisibleMessage={}
self.heliCloseMessage={}
self.hoverStatus={}
self.inTransitGroups={}
self.landedStatus={}
self.lastCrash={}
self.takenOff={}
self.smokeMarkers={}
self.UsedVHFFrequencies={}
self.woundedGroups={}
self.downedPilots={}
self.downedpilotcounter=1
self.rescues=0
self.rescuedpilots=0
self.csarOncrash=false
self.allowDownedPilotCAcontrol=false
self.enableForAI=false
self.smokecolor=4
self.coordtype=2
self.immortalcrew=true
self.invisiblecrew=false
self.messageTime=15
self.pilotRuntoExtractPoint=true
self.loadDistance=75
self.extractDistance=500
self.loadtimemax=135
self.radioSound="beacon.ogg"
self.beaconRefresher=29
self.allowFARPRescue=true
self.FARPRescueDistance=1000
self.max_units=6
self.useprefix=true
self.csarPrefix={"helicargo","MEDEVAC"}
self.template=Template or"generic"
self.mashprefix={"MASH"}
self.autosmoke=false
self.autosmokedistance=2000
self.limitmaxdownedpilots=true
self.maxdownedpilots=25
self:_GenerateVHFrequencies()
self.approachdist_far=5000
self.approachdist_near=3000
self.pilotmustopendoors=false
self.suppressmessages=false
self.rescuehoverheight=20
self.rescuehoverdistance=10
self.countryblue=country.id.USA
self.countryred=country.id.RUSSIA
self.countryneutral=country.id.UN_PEACEKEEPERS
self.csarUsePara=false
self.wetfeettemplate=nil
self.usewetfeet=false
self.allowbronco=false
self.ADFRadioPwr=1000
self.PilotWeight=80
self.UserSetGroup=nil
self.useSRS=false
self.SRSPath="E:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRSchannel=300
self.SRSModulation=radio.modulation.AM
self.SRSport=5002
self.SRSCulture="en-GB"
self.SRSVoice=MSRS.Voices.Google.Standard.en_GB_Standard_B
self.SRSGPathToCredentials=nil
self.SRSVolume=1.0
self.SRSGender="male"
self.CSARVoice=MSRS.Voices.Google.Standard.en_US_Standard_A
self.CSARVoiceMS=MSRS.Voices.Microsoft.Hedda
self.coordinate=nil
local AliaS=string.gsub(self.alias," ","_")
self.filename=string.format("CSAR_%s_Persist.csv",AliaS)
self.enableLoadSave=false
self.filepath=nil
self.saveinterval=600
return self
end
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet,BeaconName)
self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername})
local DownedPilot={}
DownedPilot.desc=Description or""
DownedPilot.frequency=Frequency or 0
DownedPilot.index=self.downedpilotcounter
DownedPilot.name=Groupname or Playername or""
DownedPilot.originalUnit=OriginalUnit or""
DownedPilot.player=Playername or""
DownedPilot.side=Side or 0
DownedPilot.typename=Typename or""
DownedPilot.group=Group
DownedPilot.timestamp=0
DownedPilot.alive=true
DownedPilot.wetfeet=Wetfeet or false
DownedPilot.BeaconName=BeaconName
local PilotTable=self.downedPilots
local counter=self.downedpilotcounter
PilotTable[counter]={}
PilotTable[counter]=DownedPilot
self:T({Table=PilotTable})
self.downedPilots=PilotTable
self.downedpilotcounter=self.downedpilotcounter+1
return self
end
function CSAR:_PilotsOnboard(_heliName)
self:T(self.lid.." _PilotsOnboard")
local count=0
if self.inTransitGroups[_heliName]then
for _,_group in pairs(self.inTransitGroups[_heliName])do
count=count+1
end
end
return count
end
function CSAR:_DoubleEjection(_unitname)
if self.lastCrash[_unitname]then
local _time=self.lastCrash[_unitname]
if timer.getTime()-_time<10 then
self:E(self.lid.."Caught double ejection!")
return true
end
end
self.lastCrash[_unitname]=timer.getTime()
return false
end
function CSAR:AddPlayerTask(PlayerTask)
self:T(self.lid.." AddPlayerTask")
if not self.PlayerTaskQueue then
self.PlayerTaskQueue=FIFO:New()
end
self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr)
return self
end
function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
self:T({country,point,frequency,tostring(wetfeet)})
local freq=frequency or 1000
local freq=freq/1000
for i=1,10 do
math.random(i,10000)
end
if point:IsSurfaceTypeWater()or wetfeet then
point.y=0
end
local template=self.template
if self.usewetfeet and wetfeet then
template=self.wetfeettemplate
end
local alias=string.format("Pilot %.2fkHz-%d",freq,math.random(1,99))
local coalition=self.coalition
local pilotcacontrol=self.allowDownedPilotCAcontrol
local _spawnedGroup=SPAWN
:NewWithAlias(template,alias)
:InitCoalition(coalition)
:InitCountry(country)
:InitDelayOff()
:SpawnFromCoordinate(point)
return _spawnedGroup,alias
end
function CSAR:_AddSpecialOptions(group)
self:T(self.lid.." _AddSpecialOptions")
self:T({group})
local immortalcrew=self.immortalcrew
local invisiblecrew=self.invisiblecrew
if immortalcrew then
local _setImmortal={
id='SetImmortal',
params={
value=true
}
}
group:SetCommand(_setImmortal)
end
if invisiblecrew then
local _setInvisible={
id='SetInvisible',
params={
value=true
}
}
group:SetCommand(_setInvisible)
end
group:OptionAlarmStateGreen()
group:OptionROEHoldFire()
return self
end
function CSAR:_AddCsar(_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description,forcedesc)
self:T(self.lid.." _AddCsar")
self:T({_coalition,_country,_point,_typeName,_unitName,_playerName,_freq,noMessage,_description})
local template=self.template
local wetfeet=false
local surface=_point:GetSurfaceType()
if surface==land.SurfaceType.WATER then
wetfeet=true
end
if not _freq then
_freq=self:_GenerateADFFrequency()
if not _freq then _freq=333000 end
end
local _spawnedGroup,_alias=self:_SpawnPilotInField(_country,_point,_freq,wetfeet)
local _typeName=_typeName or"Pilot"
if not noMessage then
if _freq~=0 then
self:_DisplayToAllSAR("MAYDAY MAYDAY! ".._typeName.." is down. ",self.coalition,self.messageTime)
else
self:_DisplayToAllSAR("Troops In Contact. ".._typeName.." requests CASEVAC. ",self.coalition,self.messageTime)
end
end
local BeaconName
if _playerName then
BeaconName=_playerName..math.random(1,10000)
elseif _unitName then
BeaconName=_unitName..math.random(1,10000)
else
BeaconName="Ghost-1-1"..math.random(1,10000)
end
if(_freq and _freq~=0)then
self:_AddBeaconToGroup(_spawnedGroup,_freq,BeaconName)
end
self:_AddSpecialOptions(_spawnedGroup)
local _text=_description
if not forcedesc then
if _playerName~=nil then
if _freq~=0 then
_text="Pilot ".._playerName
else
_text="TIC - ".._playerName
end
elseif _unitName~=nil then
if _freq~=0 then
_text="AI Pilot of ".._unitName
else
_text="TIC - ".._unitName
end
end
end
self:T({_spawnedGroup,_alias})
local _GroupName=_spawnedGroup:GetName()or _alias
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet,BeaconName)
self:_InitSARForPilot(_spawnedGroup,_unitName,_freq,noMessage,_playerName)
return _spawnedGroup,_alias
end
function CSAR:_SpawnCsarAtZone(_zone,_coalition,_description,_randomPoint,_nomessage,unitname,typename,forcedesc)
self:T(self.lid.." _SpawnCsarAtZone")
local freq=self:_GenerateADFFrequency()
local _triggerZone=nil
if type(_zone)=="string"then
_triggerZone=ZONE:New(_zone)
elseif type(_zone)=="table"and _zone.ClassName then
if string.find(_zone.ClassName,"ZONE",1)then
_triggerZone=_zone
end
end
if _triggerZone==nil then
self:E(self.lid.."ERROR: Can\'t find zone called ".._zone,10)
return
end
local _description=_description or"PoW"
local unitname=unitname or"Old Rusty"
local typename=typename or"Phantom II"
local pos={}
if _randomPoint then
local _pos=_triggerZone:GetRandomPointVec3()
pos=COORDINATE:NewFromVec3(_pos)
else
pos=_triggerZone:GetCoordinate()
end
local _country=0
if _coalition==coalition.side.BLUE then
_country=self.countryblue
elseif _coalition==coalition.side.RED then
_country=self.countryred
else
_country=self.countryneutral
end
self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,freq,_nomessage,_description,forcedesc)
return self
end
function CSAR:SpawnCSARAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc)
self:_SpawnCsarAtZone(Zone,Coalition,Description,RandomPoint,Nomessage,Unitname,Typename,Forcedesc)
return self
end
function CSAR:_SpawnCASEVAC(_Point,_coalition,_description,_nomessage,unitname,typename,forcedesc)
self:T(self.lid.." _SpawnCASEVAC")
local _description=_description or"CASEVAC"
local unitname=unitname or"CASEVAC"
local typename=typename or"Ground Commander"
local pos={}
pos=_Point
local _country=0
if _coalition==coalition.side.BLUE then
_country=self.countryblue
elseif _coalition==coalition.side.RED then
_country=self.countryred
else
_country=self.countryneutral
end
self:_AddCsar(_coalition,_country,pos,typename,unitname,_description,0,_nomessage,_description,forcedesc)
return self
end
function CSAR:SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc)
self:_SpawnCASEVAC(Point,Coalition,Description,Nomessage,Unitname,Typename,Forcedesc)
return self
end
function CSAR:_EventHandler(EventData)
self:T(self.lid.." _EventHandler")
self:T({Event=EventData.id})
local _event=EventData
if self.enableForAI==false and _event.IniPlayerName==nil then
return self
end
if _event==nil or _event.initiator==nil then
return self
elseif _event.id==EVENTS.Takeoff then
self:T(self.lid.." Event unit - Takeoff")
local _coalition=_event.IniCoalition
if _coalition~=self.coalition then
return self
end
if _event.IniGroupName then
self.takenOff[_event.IniUnitName]=true
end
return self
elseif _event.id==EVENTS.PlayerEnterAircraft or _event.id==EVENTS.PlayerEnterUnit then
self:T(self.lid.." Event unit - Player Enter")
local _coalition=_event.IniCoalition
self:T("Coalition = "..UTILS.GetCoalitionName(_coalition))
if _coalition~=self.coalition then
return self
end
if _event.IniPlayerName then
self.takenOff[_event.IniPlayerName]=nil
end
self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true)))
if _event.IniUnit:InAir(true)then
self.takenOff[_event.IniPlayerName]=true
end
local _unit=_event.IniUnit
local _group=_event.IniGroup
local function IsBronco(Group)
local grp=Group
local typename=grp:GetTypeName()
self:T(typename)
if typename=="Bronco-OV-10A"then return true end
return false
end
if _unit:IsHelicopter()or _group:IsHelicopter()or IsBronco(_group)then
self:_AddMedevacMenuItem()
end
return self
elseif(_event.id==EVENTS.PilotDead and self.csarOncrash==false)then
self:T(self.lid.." Event unit - Pilot Dead")
local _unit=_event.IniUnit
local _unitname=_event.IniUnitName
local _group=_event.IniGroup
if _unit==nil then
return self
end
local _coalition=_event.IniCoalition
if _coalition~=self.coalition then
return self
end
if self.takenOff[_event.IniUnitName]==true or _group:IsAirborne()then
if self:_DoubleEjection(_unitname)then
return self
end
else
self:T(self.lid.." Pilot has not taken off, ignore")
end
return self
elseif _event.id==EVENTS.PilotDead or _event.id==EVENTS.Ejection then
if _event.id==EVENTS.PilotDead and self.csarOncrash==false then
return self
end
self:T(self.lid.." Event unit - Pilot Ejected")
local _unit=_event.IniUnit
local _unitname=_event.IniUnitName
local _group=_event.IniGroup
self:T({_unit.UnitName,_unitname,_group.GroupName})
if _unit==nil then
self:T("Unit NIL!")
return self
end
local _coalition=_group:GetCoalition()
if _coalition~=self.coalition then
self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition))
return self
end
self:T("Airborne: "..tostring(_group:IsAirborne()))
self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName]))
if not self.takenOff[_event.IniUnitName]and not _group:IsAirborne()then
self:T(self.lid.." Pilot has not taken off, ignore")
end
if self:_DoubleEjection(_unitname)then
self:T("Double Ejection!")
return self
end
if self.limitmaxdownedpilots and self:_ReachedPilotLimit()then
self:T("Maxed Downed Pilot!")
return self
end
local wetfeet=false
local initdcscoord=nil
local initcoord=nil
if _event.id==EVENTS.Ejection then
initdcscoord=_event.TgtDCSUnit:getPoint()
initcoord=COORDINATE:NewFromVec3(initdcscoord)
self:T({initdcscoord})
else
initdcscoord=_event.IniDCSUnit:getPoint()
initcoord=COORDINATE:NewFromVec3(initdcscoord)
self:T({initdcscoord})
end
local surface=initcoord:GetSurfaceType()
if surface==land.SurfaceType.WATER then
self:T("Wet feet!")
wetfeet=true
end
if self.csarUsePara==false or(self.csarUsePara and wetfeet)then
local _freq=self:_GenerateADFFrequency()
self:_AddCsar(_coalition,_unit:GetCountry(),initcoord,_unit:GetTypeName(),_unit:GetName(),_event.IniPlayerName,_freq,false,"none")
return self
end
elseif _event.id==EVENTS.Land then
self:T(self.lid.." Landing")
if _event.IniUnitName then
self.takenOff[_event.IniUnitName]=nil
end
if self.allowFARPRescue then
local _unit=_event.IniUnit
if _unit==nil then
self:T(self.lid.." Unit nil on landing")
return self
end
local _coalition=_event.IniGroup:GetCoalition()
if _coalition~=self.coalition then
self:T(self.lid.." Wrong coalition")
return self
end
self.takenOff[_event.IniUnitName]=nil
local _place=_event.Place
if _place==nil then
self:T(self.lid.." Landing Place Nil")
return self
end
if self.inTransitGroups[_event.IniUnitName]==nil then
return self
end
if _place:GetCoalition()==self.coalition or _place:GetCoalition()==coalition.side.NEUTRAL then
self:__Landed(2,_event.IniUnitName,_place)
self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true)
else
self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition()))
end
end
return self
end
if(_event.id==EVENTS.LandingAfterEjection and self.csarUsePara==true)then
self:T("LANDING_AFTER_EJECTION")
local _LandingPos=COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _unitname="Aircraft"
local _typename="Ejected Pilot"
local _country=_event.initiator:getCountry()
local _coalition=coalition.getCountryCoalition(_country)
self:T("Country = ".._country.." Coalition = ".._coalition)
if _coalition==self.coalition then
local _freq=self:_GenerateADFFrequency()
self:I({coalition=_coalition,country=_country,coord=_LandingPos,name=_unitname,player=_event.IniPlayerName,freq=_freq})
self:_AddCsar(_coalition,_country,_LandingPos,nil,_unitname,_event.IniPlayerName,_freq,false,"none")
Unit.destroy(_event.initiator)
end
end
return self
end
function CSAR:_InitSARForPilot(_downedGroup,_GroupName,_freq,_nomessage,_playername)
self:T(self.lid.." _InitSARForPilot")
local _leader=_downedGroup:GetUnit(1)
local _groupName=_GroupName
local _freqk=_freq/1000
local _coordinatesText=self:_GetPositionOfWounded(_downedGroup)
local _leadername=_leader:GetName()
if not _nomessage then
if _freq~=0 then
local _text=string.format("%s requests SAR at %s, beacon at %.2f KHz",_groupName,_coordinatesText,_freqk)
if self.coordtype~=2 then
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
local coordtext=UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
local _text=string.format("%s requests SAR at %s, beacon at %.2f kilo hertz",_groupName,coordtext,_freqk)
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
end
else
local _text=string.format("Pickup Zone at %s.",_coordinatesText)
if self.coordtype~=2 then
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
local coordtext=UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
local _text=string.format("Pickup Zone at %s.",coordtext)
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
end
end
end
for _,_heliName in pairs(self.csarUnits)do
self:_CheckWoundedGroupStatus(_heliName,_groupName)
end
self:__PilotDown(2,_downedGroup,_freqk,_groupName,_coordinatesText,_playername)
return self
end
function CSAR:_CheckNameInDownedPilots(name)
local PilotTable=self.downedPilots
local found=false
local table=nil
for _,_pilot in pairs(PilotTable)do
if _pilot.name==name and _pilot.alive==true then
found=true
table=_pilot
break
end
end
return found,table
end
function CSAR:_RemoveNameFromDownedPilots(name,force)
local PilotTable=self.downedPilots
local found=false
for _index,_pilot in pairs(PilotTable)do
if _pilot.name==name then
self.downedPilots[_index].alive=false
end
end
return found
end
function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
if not ShortCallsign or ShortCallsign==false then
self.ShortCallsign=false
else
self.ShortCallsign=true
end
self.Keepnumber=Keepnumber or false
self.CallsignTranslations=CallsignTranslations
return self
end
function CSAR:_GetCustomCallSign(UnitName)
local callsign=UnitName
local unit=UNIT:FindByName(UnitName)
if unit and unit:IsAlive()then
local group=unit:GetGroup()
callsign=group:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
end
return callsign
end
function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname)
self:T(self.lid.." _CheckWoundedGroupStatus")
local _heliName=heliname
local _woundedGroupName=woundedgroupname
self:T({Heli=_heliName,Downed=_woundedGroupName})
local _found,_downedpilot=self:_CheckNameInDownedPilots(_woundedGroupName)
if not _found then
self:T("...not found in list!")
return
end
local _woundedGroup=_downedpilot.group
if _woundedGroup~=nil and _woundedGroup:IsAlive()then
local _heliUnit=self:_GetSARHeli(_heliName)
local _lookupKeyHeli=_heliName.."_".._woundedGroupName
if _heliUnit==nil then
self.heliVisibleMessage[_lookupKeyHeli]=nil
self.heliCloseMessage[_lookupKeyHeli]=nil
self.landedStatus[_lookupKeyHeli]=nil
self:T("...heliunit nil!")
return
end
local _heliCoord=_heliUnit:GetCoordinate()
local _leaderCoord=_woundedGroup:GetCoordinate()
local _distance=self:_GetDistance(_heliCoord,_leaderCoord)
if(self.autosmoke==true)and(_distance<self.autosmokedistance)and(_distance~=-1)then
self:_PopSmokeForGroup(_woundedGroupName,_woundedGroup)
end
if _distance<self.approachdist_near and _distance>0 then
if self:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName)==true then
_downedpilot.timestamp=timer.getAbsTime()
self:__Approach(-5,heliname,woundedgroupname)
end
elseif _distance>=self.approachdist_near and _distance<self.approachdist_far then
if self.heliVisibleMessage[_lookupKeyHeli]==nil then
local _pilotName=_downedpilot.desc
if self.autosmoke==true then
local dist=self.autosmokedistance/1000
local disttext=string.format("%.0fkm",dist)
if _SETTINGS:IsImperial()then
local dist=UTILS.MetersToNM(self.autosmokedistance)
disttext=string.format("%.0fnm",dist)
end
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.",self:_GetCustomCallSign(_heliName),_pilotName,disttext),self.messageTime,false,true)
else
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true)
end
self.heliVisibleMessage[_lookupKeyHeli]=true
end
self.heliCloseMessage[_lookupKeyHeli]=nil
self.landedStatus[_lookupKeyHeli]=nil
_downedpilot.timestamp=timer.getAbsTime()
self:__Approach(-10,heliname,woundedgroupname)
end
else
self:T("...Downed Pilot KIA?!")
if not _downedpilot.alive then
self:_RemoveNameFromDownedPilots(_downedpilot.name,true)
end
end
return self
end
function CSAR:_PopSmokeForGroup(_woundedGroupName,_woundedLeader)
self:T(self.lid.." _PopSmokeForGroup")
local _lastSmoke=self.smokeMarkers[_woundedGroupName]
if _lastSmoke==nil or timer.getTime()>_lastSmoke then
local _smokecolor=self.smokecolor
local _smokecoord=_woundedLeader:GetCoordinate():Translate(6,math.random(1,360))
_smokecoord:Smoke(_smokecolor)
self.smokeMarkers[_woundedGroupName]=timer.getTime()+300
end
return self
end
function CSAR:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
self:T(self.lid.." _PickupUnit")
local _heliName=_heliUnit:GetName()
local _groups=self.inTransitGroups[_heliName]
local _unitsInHelicopter=self:_PilotsOnboard(_heliName)
if not _groups then
self.inTransitGroups[_heliName]={}
_groups=self.inTransitGroups[_heliName]
end
local _maxUnits=self.AircraftType[_heliUnit:GetTypeName()]
if _maxUnits==nil then
_maxUnits=self.max_units
end
if _unitsInHelicopter+1>_maxUnits then
self:_DisplayMessageToSAR(_heliUnit,string.format("%s, %s. We\'re already crammed with %d guys! Sorry!",_pilotName,self:_GetCustomCallSign(_heliName),_unitsInHelicopter,_unitsInHelicopter),self.messageTime,false,false,true)
return self
end
local found,downedgrouptable=self:_CheckNameInDownedPilots(_woundedGroupName)
local grouptable=downedgrouptable
self.inTransitGroups[_heliName][_woundedGroupName]=
{
originalUnit=grouptable.originalUnit,
woundedGroup=_woundedGroupName,
side=self.coalition,
desc=grouptable.desc,
player=grouptable.player,
}
_woundedGroup:Destroy(false)
self:_RemoveNameFromDownedPilots(_woundedGroupName,true)
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s I\'m in! Get to the MASH ASAP! ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,true,true)
self:_UpdateUnitCargoMass(_heliName)
self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc)
return self
end
function CSAR:_UpdateUnitCargoMass(_heliName)
self:T(self.lid.." _UpdateUnitCargoMass")
local calculatedMass=self:_PilotsOnboard(_heliName)*(self.PilotWeight or 80)
local Unit=UNIT:FindByName(_heliName)
if Unit then
Unit:SetUnitInternalCargo(calculatedMass)
end
return self
end
function CSAR:_OrderGroupToMoveToPoint(_leader,_destination)
self:T(self.lid.." _OrderGroupToMoveToPoint")
local group=_leader
local coordinate=_destination:GetVec2()
group:SetAIOn()
group:RouteToVec2(coordinate,5)
return self
end
function CSAR:_IsLoadingDoorOpen(unit_name)
self:T(self.lid.." _IsLoadingDoorOpen")
return UTILS.IsLoadingDoorOpen(unit_name)
end
function CSAR:_CheckCloseWoundedGroup(_distance,_heliUnit,_heliName,_woundedGroup,_woundedGroupName)
self:T(self.lid.." _CheckCloseWoundedGroup")
local _woundedLeader=_woundedGroup
local _lookupKeyHeli=_heliUnit:GetName().."_".._woundedGroupName
local _found,_pilotable=self:_CheckNameInDownedPilots(_woundedGroupName)
local _pilotName=_pilotable.desc
local _reset=true
if(_distance<500)then
self:T(self.lid.."[Pickup Debug] Helo closer than 500m: ".._lookupKeyHeli)
if self.heliCloseMessage[_lookupKeyHeli]==nil then
if self.autosmoke==true then
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land or hover at the smoke.",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true)
else
self:_DisplayMessageToSAR(_heliUnit,string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ",self:_GetCustomCallSign(_heliName),_pilotName),self.messageTime,false,true)
end
self.heliCloseMessage[_lookupKeyHeli]=true
end
self:T(self.lid.."[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli)
if not _heliUnit:InAir()then
self:T(self.lid.."[Pickup Debug] Helo landed: ".._lookupKeyHeli)
if self.pilotRuntoExtractPoint==true then
if(_distance<self.extractDistance)then
local _time=self.landedStatus[_lookupKeyHeli]
self:T(self.lid.."[Pickup Debug] Check pilot running or arrived ".._lookupKeyHeli)
if _time==nil then
self:T(self.lid.."[Pickup Debug] Pilot running not arrived yet ".._lookupKeyHeli)
self.landedStatus[_lookupKeyHeli]=math.floor((_distance-self.loadDistance)/3.6)
_time=self.landedStatus[_lookupKeyHeli]
_woundedGroup:OptionAlarmStateGreen()
self:_OrderGroupToMoveToPoint(_woundedGroup,_heliUnit:GetCoordinate())
self:_DisplayMessageToSAR(_heliUnit,"Wait till ".._pilotName.." gets in. \nETA ".._time.." more seconds.",self.messageTime,false)
else
_time=self.landedStatus[_lookupKeyHeli]-10
self.landedStatus[_lookupKeyHeli]=_time
end
self:T(self.lid.."[Pickup Debug] Pilot close enough? ".._lookupKeyHeli)
if _distance<self.loadDistance+5 or _distance<=13 then
self:T(self.lid.."[Pickup Debug] Pilot close enough - YES ".._lookupKeyHeli)
if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true)
self:T(self.lid.."[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli)
return false
else
self:T(self.lid.."[Pickup Debug] Pick up Pilot ".._lookupKeyHeli)
self.landedStatus[_lookupKeyHeli]=nil
self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
return true
end
end
end
else
self:T(self.lid.."[Pickup Debug] Helo landed, pilot NOT set to run to helo ".._lookupKeyHeli)
if(_distance<self.loadDistance)then
self:T(self.lid.."[Pickup Debug] Helo close enough, door check ".._lookupKeyHeli)
if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then
self:T(self.lid.."[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli)
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true)
return false
else
self:T(self.lid.."[Pickup Debug] Pick up Pilot ".._lookupKeyHeli)
self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
return true
end
end
end
else
self:T(self.lid.."[Pickup Debug] Helo hovering".._lookupKeyHeli)
local _unitsInHelicopter=self:_PilotsOnboard(_heliName)
local _maxUnits=self.AircraftType[_heliUnit:GetTypeName()]
if _maxUnits==nil then
_maxUnits=self.max_units
end
self:T(self.lid.."[Pickup Debug] Check capacity and close enough for winching ".._lookupKeyHeli)
if _heliUnit:InAir()and _unitsInHelicopter+1<=_maxUnits then
if _distance<self.rescuehoverdistance then
self:T(self.lid.."[Pickup Debug] Helo hovering close enough ".._lookupKeyHeli)
local leaderheight=_woundedLeader:GetHeight()
if leaderheight<0 then leaderheight=0 end
local _height=_heliUnit:GetHeight()-leaderheight
if _height<=self.rescuehoverheight then
self:T(self.lid.."[Pickup Debug] Helo hovering low enough ".._lookupKeyHeli)
local _time=self.hoverStatus[_lookupKeyHeli]
if _time==nil then
self.hoverStatus[_lookupKeyHeli]=10
_time=10
else
_time=self.hoverStatus[_lookupKeyHeli]-10
self.hoverStatus[_lookupKeyHeli]=_time
end
self:T(self.lid.."[Pickup Debug] Check hover timer ".._lookupKeyHeli)
if _time>0 then
self:T(self.lid.."[Pickup Debug] Helo hovering not long enough ".._lookupKeyHeli)
self:_DisplayMessageToSAR(_heliUnit,"Hovering above ".._pilotName..". \n\nHold hover for ".._time.." seconds to winch them up. \n\nIf the countdown stops you\'re too far away!",self.messageTime,true)
else
self:T(self.lid.."[Pickup Debug] Helo hovering long enough - door check ".._lookupKeyHeli)
if self.pilotmustopendoors and(self:_IsLoadingDoorOpen(_heliName)==false)then
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me in!",self.messageTime,true,true)
self:T(self.lid.."[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli)
return false
else
self.hoverStatus[_lookupKeyHeli]=nil
self:_PickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName)
self:T(self.lid.."[Pickup Debug] Pilot picked up ".._lookupKeyHeli)
return true
end
end
_reset=false
else
self:T(self.lid.."[Pickup Debug] Helo hovering too high ".._lookupKeyHeli)
self:_DisplayMessageToSAR(_heliUnit,"Too high to winch ".._pilotName.." \nReduce height and hover for 10 seconds!",self.messageTime,true,true)
self:T(self.lid.."[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli)
return false
end
end
end
end
end
if _reset then
self.hoverStatus[_lookupKeyHeli]=nil
end
if _distance<500 then
return true
else
return false
end
end
function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule)
self:T(self.lid.." _ScheduledSARFlight")
self:T({heliname,groupname})
local _heliUnit=self:_GetSARHeli(heliname)
local _woundedGroupName=groupname
if(_heliUnit==nil)then
self.inTransitGroups[heliname]=nil
return
end
if self.inTransitGroups[heliname]==nil or self.inTransitGroups[heliname][_woundedGroupName]==nil then
return
end
local _dist=self:_GetClosestMASH(_heliUnit)
if _dist==-1 then
self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance can not be determined!")
return
end
self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000))
if(_dist<self.FARPRescueDistance or isairport)and _heliUnit:InAir()==false then
self:T(self.lid.."[Drop off debug] Distance ok, door check")
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname)==false then
self:_DisplayMessageToSAR(_heliUnit,"Open the door to let me out!",self.messageTime,true,true)
self:T(self.lid.."[Drop off debug] Door closed, try again next loop")
else
self:T(self.lid.."[Drop off debug] Rescued!")
self:_RescuePilots(_heliUnit)
return
end
end
if not noreschedule then
self:__Returning(5,heliname,_woundedGroupName,isairport)
self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname,isairport,noreschedule)
end
return self
end
function CSAR:_RescuePilots(_heliUnit)
self:T(self.lid.." _RescuePilots")
local _heliName=_heliUnit:GetName()
local _rescuedGroups=self.inTransitGroups[_heliName]
if _rescuedGroups==nil then
return
end
local PilotsSaved=self:_PilotsOnboard(_heliName)
self.inTransitGroups[_heliName]=nil
local _txt=string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!",self:_GetCustomCallSign(_heliName),PilotsSaved)
self:_DisplayMessageToSAR(_heliUnit,_txt,self.messageTime)
self:_UpdateUnitCargoMass(_heliName)
self:__Rescued(-1,_heliUnit,_heliName,PilotsSaved)
return self
end
function CSAR:_GetSARHeli(_unitName)
self:T(self.lid.." _GetSARHeli")
local unit=UNIT:FindByName(_unitName)
if unit and unit:IsAlive()then
return unit
else
return nil
end
end
function CSAR:_DisplayMessageToSAR(_unit,_text,_time,_clear,_speak,_override)
self:T(self.lid.." _DisplayMessageToSAR")
local group=_unit:GetGroup()
local _clear=_clear or nil
local _time=_time or self.messageTime
if _override or not self.suppressmessages then
local m=MESSAGE:New(_text,_time,"CSAR",_clear):ToGroup(group)
end
if _speak and self.useSRS then
local coord=_unit:GetCoordinate()
if coord then
self.msrs:SetCoordinate(coord)
end
_text=string.gsub(_text,"km"," kilometer")
_text=string.gsub(_text,"nm"," nautical miles")
self.SRSQueue:NewTransmission(_text,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,self.SRSVoice,volume,label,coord)
end
return self
end
function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit)
self:T(self.lid.." _GetPositionOfWounded")
local _coordinate=_woundedGroup:GetCoordinate()
local _coordinatesText="None"
if _coordinate then
if self.coordtype==0 then
_coordinatesText=_coordinate:ToStringLLDDM()
elseif self.coordtype==1 then
_coordinatesText=_coordinate:ToStringLLDMS()
elseif self.coordtype==2 then
_coordinatesText=_coordinate:ToStringMGRS()
else
_coordinatesText=_coordinate:ToStringBULLS(self.coalition)
end
end
if _Unit and _Unit:GetPlayerName()then
local playername=_Unit:GetPlayerName()
if playername then
local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS
if settings then
self:T("Get Settings ok!")
if settings:IsA2G_MGRS()then
_coordinatesText=_coordinate:ToStringMGRS(settings)
elseif settings:IsA2G_LL_DMS()then
_coordinatesText=_coordinate:ToStringLLDMS(settings)
elseif settings:IsA2G_LL_DDM()then
_coordinatesText=_coordinate:ToStringLLDDM(settings)
elseif settings:IsA2G_BR()then
local startcoordinate=_Unit:GetCoordinate()
_coordinatesText=_coordinate:ToStringBR(startcoordinate,settings)
end
end
end
end
return _coordinatesText
end
function CSAR:_DisplayActiveSAR(_unitName)
self:T(self.lid.." _DisplayActiveSAR")
local _msg="Active MEDEVAC/SAR:"
local _heli=self:_GetSARHeli(_unitName)
if _heli==nil then
return
end
local _heliSide=self.coalition
local _csarList={}
local _DownedPilotTable=self.downedPilots
self:T({Table=_DownedPilotTable})
for _,_value in pairs(_DownedPilotTable)do
local _groupName=_value.name
self:T(string.format("Display Active Pilot: %s",tostring(_groupName)))
self:T({Table=_value})
local _woundedGroup=_value.group
if _woundedGroup and _value.alive then
local _coordinatesText=self:_GetPositionOfWounded(_woundedGroup,_heli)
local _helicoord=_heli:GetCoordinate()
local _woundcoord=_woundedGroup:GetCoordinate()
local _distance=self:_GetDistance(_helicoord,_woundcoord)
self:T({_distance=_distance})
local distancetext=""
local settings=_SETTINGS
if _heli:GetPlayerName()then
settings=_DATABASE:GetPlayerSettings(_heli:GetPlayerName())or _SETTINGS
end
if settings:IsImperial()then
distancetext=string.format("%.1fnm",UTILS.MetersToNM(_distance))
else
distancetext=string.format("%.1fkm",_distance/1000.0)
end
if _value.frequency==0 or self.CreateRadioBeacons==false then
table.insert(_csarList,{dist=_distance,msg=string.format("%s at %s - %s ",_value.desc,_coordinatesText,distancetext)})
else
table.insert(_csarList,{dist=_distance,msg=string.format("%s at %s - %.2f KHz ADF - %s ",_value.desc,_coordinatesText,_value.frequency/1000,distancetext)})
end
end
end
local function sortDistance(a,b)
return a.dist<b.dist
end
table.sort(_csarList,sortDistance)
for _,_line in pairs(_csarList)do
_msg=_msg.."\n".._line.msg
end
self:_DisplayMessageToSAR(_heli,_msg,self.messageTime*2,false,false,true)
return self
end
function CSAR:_GetClosestDownedPilot(_heli)
self:T(self.lid.." _GetClosestDownedPilot")
local _side=self.coalition
local _closestGroup=nil
local _shortestDistance=-1
local _distance=0
local _closestGroupInfo=nil
local _heliCoord=_heli:GetCoordinate()or _heli:GetCoordinate()
if _heliCoord==nil then
self:E("****Error obtaining coordinate!")
return nil
end
local DownedPilotsTable=self.downedPilots
for _,_groupInfo in UTILS.spairs(DownedPilotsTable)do
local _woundedName=_groupInfo.name
local _tempWounded=_groupInfo.group
if _tempWounded then
local _tempCoord=_tempWounded:GetCoordinate()
_distance=self:_GetDistance(_heliCoord,_tempCoord)
if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then
_shortestDistance=_distance
_closestGroup=_tempWounded
_closestGroupInfo=_groupInfo
end
end
end
return{pilot=_closestGroup,distance=_shortestDistance,groupInfo=_closestGroupInfo}
end
function CSAR:_SignalFlare(_unitName)
self:T(self.lid.." _SignalFlare")
local _heli=self:_GetSARHeli(_unitName)
if _heli==nil then
return
end
local _closest=self:_GetClosestDownedPilot(_heli)
local smokedist=8000
if self.approachdist_far>smokedist then smokedist=self.approachdist_far end
if _closest~=nil and _closest.pilot~=nil and _closest.distance>0 and _closest.distance<smokedist then
local _clockDir=self:_GetClockDirection(_heli,_closest.pilot)
local _distance=""
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
else
_distance=string.format("%.1fkm",_closest.distance/1000)
end
local _msg=string.format("%s - Firing signal flare at your %s o\'clock. Distance %s",self:_GetCustomCallSign(_unitName),_clockDir,_distance)
self:_DisplayMessageToSAR(_heli,_msg,self.messageTime,false,true,true)
local _coord=_closest.pilot:GetCoordinate()
_coord:FlareRed(_clockDir)
else
local _distance=smokedist
local dtext=""
if _SETTINGS:IsImperial()then
dtext=string.format("%.1fnm",UTILS.MetersToNM(smokedist))
else
dtext=string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli,string.format("No Pilots within %s",dtext),self.messageTime,false,false,true)
end
return self
end
function CSAR:_DisplayToAllSAR(_message,_side,_messagetime,ToSRS,ToScreen)
self:T(self.lid.." _DisplayToAllSAR")
local messagetime=_messagetime or self.messageTime
self:T({_message,ToSRS=ToSRS,ToScreen=ToScreen})
if self.msrs and(ToSRS==true or ToSRS==nil)then
local voice=self.CSARVoice or MSRS.Voices.Google.Standard.en_GB_Standard_F
if self.msrs:GetProvider()==MSRS.Provider.WINDOWS then
voice=self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda
end
self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate)
end
if ToScreen==true or ToScreen==nil then
for _,_unitName in pairs(self.csarUnits)do
local _unit=self:_GetSARHeli(_unitName)
if _unit and not self.suppressmessages then
self:_DisplayMessageToSAR(_unit,_message,_messagetime)
end
end
end
return self
end
function CSAR:_ReqIRStrobe(_unitName)
self:T(self.lid.." _ReqIRStrobe")
local _heli=self:_GetSARHeli(_unitName)
if _heli==nil then
return
end
local smokedist=8000
if smokedist<self.approachdist_far then smokedist=self.approachdist_far end
local _closest=self:_GetClosestDownedPilot(_heli)
if _closest~=nil and _closest.pilot~=nil and _closest.distance>0 and _closest.distance<smokedist then
local _clockDir=self:_GetClockDirection(_heli,_closest.pilot)
local _distance=string.format("%.1fkm",_closest.distance/1000)
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
else
_distance=string.format("%.1fkm",_closest.distance/1000)
end
local _msg=string.format("%s - IR Strobe active at your %s o\'clock. Distance %s",self:_GetCustomCallSign(_unitName),_clockDir,_distance)
self:_DisplayMessageToSAR(_heli,_msg,self.messageTime,false,true,true)
_closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300)
else
local _distance=string.format("%.1fkm",smokedist/1000)
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(smokedist))
else
_distance=string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli,string.format("No Pilots within %s",_distance),self.messageTime,false,false,true)
end
return self
end
function CSAR:_Reqsmoke(_unitName)
self:T(self.lid.." _Reqsmoke")
local _heli=self:_GetSARHeli(_unitName)
if _heli==nil then
return
end
local smokedist=8000
if smokedist<self.approachdist_far then smokedist=self.approachdist_far end
local _closest=self:_GetClosestDownedPilot(_heli)
if _closest~=nil and _closest.pilot~=nil and _closest.distance>0 and _closest.distance<smokedist then
local _clockDir=self:_GetClockDirection(_heli,_closest.pilot)
local _distance=string.format("%.1fkm",_closest.distance/1000)
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
else
_distance=string.format("%.1fkm",_closest.distance/1000)
end
local _msg=string.format("%s - Popping smoke at your %s o\'clock. Distance %s",self:_GetCustomCallSign(_unitName),_clockDir,_distance)
self:_DisplayMessageToSAR(_heli,_msg,self.messageTime,false,true,true)
local _coord=_closest.pilot:GetCoordinate()
local color=self.smokecolor
_coord:Smoke(color)
else
local _distance=string.format("%.1fkm",smokedist/1000)
if _SETTINGS:IsImperial()then
_distance=string.format("%.1fnm",UTILS.MetersToNM(smokedist))
else
_distance=string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli,string.format("No Pilots within %s",_distance),self.messageTime,false,false,true)
end
return self
end
function CSAR:_GetClosestMASH(_heli)
self:T(self.lid.." _GetClosestMASH")
local _mashset=self.mash
local MashSets={}
table.insert(MashSets,_mashset.Set)
table.insert(MashSets,self.zonemashes.Set)
table.insert(MashSets,self.staticmashes.Set)
local _shortestDistance=-1
local _distance=0
local _helicoord=_heli:GetCoordinate()
local function GetCloseAirbase(coordinate,Coalition,Category)
local a=coordinate:GetVec3()
local distmin=math.huge
local airbase=nil
for DCSairbaseID,DCSairbase in pairs(world.getAirbases(Coalition))do
local b=DCSairbase:getPoint()
local c=UTILS.VecSubstract(a,b)
local dist=UTILS.VecNorm(c)
if dist<distmin and(Category==nil or Category==DCSairbase:getDesc().category)then
distmin=dist
airbase=DCSairbase
end
end
return distmin
end
if self.allowFARPRescue then
local position=_heli:GetCoordinate()
local afb,distance=position:GetClosestAirbase(nil,self.coalition)
_shortestDistance=distance
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
_distance=self:_GetDistance(_helicoord,_mashcoord)
if _distance~=nil and(_shortestDistance==-1 or _distance<_shortestDistance)then
_shortestDistance=_distance
end
end
end
if _shortestDistance~=-1 then
return _shortestDistance
else
return-1
end
end
function CSAR:_CheckOnboard(_unitName)
self:T(self.lid.." _CheckOnboard")
local _unit=self:_GetSARHeli(_unitName)
if _unit==nil then
return
end
local _inTransit=self.inTransitGroups[_unitName]
if _inTransit==nil then
self:_DisplayMessageToSAR(_unit,"No Rescued Pilots onboard",self.messageTime,false,false,true)
else
local _text="Onboard - RTB to FARP/Airfield or MASH: "
for _,_onboard in pairs(self.inTransitGroups[_unitName])do
_text=_text.."\n".._onboard.desc
end
self:_DisplayMessageToSAR(_unit,_text,self.messageTime*2,false,false,true)
end
return self
end
function CSAR:_AddMedevacMenuItem()
self:T(self.lid.." _AddMedevacMenuItem")
local coalition=self.coalition
local allheligroupset=self.allheligroupset
local _allHeliGroups=allheligroupset:GetSetObjects()
local _UnitList={}
for _key,_group in pairs(_allHeliGroups)do
local _unit=_group:GetFirstUnitAlive()
if _unit then
if _unit:IsAlive()and _unit:IsPlayer()then
local unitName=_unit:GetName()
_UnitList[unitName]=unitName
end
end
end
self.csarUnits=_UnitList
for _,_unitName in pairs(self.csarUnits)do
local _unit=self:_GetSARHeli(_unitName)
if _unit then
local _group=_unit:GetGroup()
if _group then
local groupname=_group:GetName()
if self.addedTo[groupname]==nil then
self.addedTo[groupname]=true
local menuname=self.topmenuname or"CSAR"
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)
if self.AllowIRStrobe then
local _rootMenu5=MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath,self._ReqIRStrobe,self,_unitName):Refresh()
else
_rootMenu4:Refresh()
end
end
end
end
end
return self
end
function CSAR:_GetDistance(_point1,_point2)
self:T(self.lid.." _GetDistance")
if _point1 and _point2 then
local distance1=_point1:Get2DDistance(_point2)
local distance2=_point1:DistanceFromPointVec2(_point2)
if distance1 and type(distance1)=="number"then
return distance1
elseif distance2 and type(distance2)=="number"then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return-1
end
end
function CSAR:_GenerateVHFrequencies()
self:T(self.lid.." _GenerateVHFrequencies")
local FreeVHFFrequencies={}
FreeVHFFrequencies=UTILS.GenerateVHFrequencies()
self.FreeVHFFrequencies=FreeVHFFrequencies
return self
end
function CSAR:_GenerateADFFrequency()
self:T(self.lid.." _GenerateADFFrequency")
if#self.FreeVHFFrequencies<=3 then
self.FreeVHFFrequencies=self.UsedVHFFrequencies
self.UsedVHFFrequencies={}
end
local _vhf=table.remove(self.FreeVHFFrequencies,math.random(#self.FreeVHFFrequencies))
return _vhf
end
function CSAR:_GetClockDirection(_heli,_group)
self:T(self.lid.." _GetClockDirection")
local _playerPosition=_heli:GetCoordinate()
local _targetpostions=_group:GetCoordinate()
local _heading=_heli:GetHeading()
local DirectionVec3=_playerPosition:GetDirectionVec3(_targetpostions)
local Angle=_playerPosition:GetAngleDegrees(DirectionVec3)
self:T(self.lid.." _GetClockDirection"..tostring(Angle).." "..tostring(_heading))
local hours=0
local clock=12
if _heading and Angle then
clock=12
clock=_heading-Angle
hours=(clock/30)*-1
clock=12+hours
clock=UTILS.Round(clock,0)
if clock>12 then clock=clock-12 end
end
return clock
end
function CSAR:_AddBeaconToGroup(_group,_freq,_name)
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
if _current==_freq then
table.insert(self.FreeVHFFrequencies,_freq)
table.remove(self.UsedVHFFrequencies,_i)
end
end
return
end
if _group:IsAlive()then
local _radioUnit=_group:GetUnit(1)
if _radioUnit then
local name=_radioUnit:GetName()
local Frequency=_freq
local name=_radioUnit:GetName()
local Sound="l10n/DEFAULT/"..self.radioSound
local vec3=_radioUnit:GetVec3()or _radioUnit:GetPositionVec3()or{x=0,y=0,z=0}
trigger.action.radioTransmission(Sound,vec3,0,false,Frequency,self.ADFRadioPwr or 1000,_name)
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
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)
trigger.action.stopRadioTransmission(bname)
if group and group:IsAlive()and frequency>0 then
self:_AddBeaconToGroup(group,frequency,bname)
end
end
end
return self
end
function CSAR:_CountActiveDownedPilots()
self:T(self.lid.." _CountActiveDownedPilots")
local PilotsInFieldN=0
for _,_unitName in pairs(self.downedPilots)do
self:T({_unitName.desc})
if _unitName.alive==true then
PilotsInFieldN=PilotsInFieldN+1
end
end
return PilotsInFieldN
end
function CSAR:_ReachedPilotLimit()
self:T(self.lid.." _ReachedPilotLimit")
local limit=self.maxdownedpilots
local islimited=self.limitmaxdownedpilots
local count=self:_CountActiveDownedPilots()
if islimited and(count>=limit)then
return true
else
return false
end
end
function CSAR: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..")")
self:HandleEvent(EVENTS.Takeoff,self._EventHandler)
self:HandleEvent(EVENTS.Land,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.LandingAfterEjection,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
if self.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
local prefixes=self.csarPrefix or{}
self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart()
else
self.allheligroupset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
end
self.mash=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
self.staticmashes=SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce()
self.zonemashes=SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce()
if not self.coordinate then
local csarhq=self.mash:GetRandom()
if csarhq then
self.coordinate=csarhq:GetCoordinate()
end
end
if self.wetfeettemplate then
self.usewetfeet=true
end
if self.useSRS then
local path=self.SRSPath
local modulation=self.SRSModulation
local channel=self.SRSchannel
self.msrs=MSRS:New(path,channel,modulation)
self.msrs:SetPort(self.SRSport)
self.msrs:SetLabel("CSAR")
self.msrs:SetCulture(self.SRSCulture)
self.msrs:SetCoalition(self.coalition)
self.msrs:SetVoice(self.SRSVoice)
self.msrs:SetGender(self.SRSGender)
if self.SRSGPathToCredentials then
self.msrs:SetProviderOptionsGoogle(self.SRSGPathToCredentials,self.SRSGPathToCredentials)
self.msrs:SetProvider(MSRS.Provider.GOOGLE)
end
self.msrs:SetVolume(self.SRSVolume)
self.msrs:SetLabel("CSAR")
self.SRSQueue=MSRSQUEUE:New("CSAR")
end
self:__Status(-10)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CSAR:_CheckDownedPilotTable()
local pilots=self.downedPilots
local npilots={}
for _ind,_entry in pairs(pilots)do
local _group=_entry.group
if _group:IsAlive()then
npilots[_ind]=_entry
else
if _entry.alive then
self:__KIA(1,_entry.desc)
end
end
end
self.downedPilots=npilots
return self
end
function CSAR:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
self:_AddMedevacMenuItem()
if not self.BeaconTimer or(self.BeaconTimer and not self.BeaconTimer:IsRunning())then
self.BeaconTimer=TIMER:New(self._RefreshRadioBeacons,self)
self.BeaconTimer:Start(2,self.beaconRefresher)
end
self:_CheckDownedPilotTable()
for _,_sar in pairs(self.csarUnits)do
local PilotTable=self.downedPilots
for _,_entry in pairs(PilotTable)do
if _entry.alive then
local entry=_entry
local name=entry.name
local timestamp=entry.timestamp or 0
local now=timer.getAbsTime()
if now-timestamp>17 then
self:_CheckWoundedGroupStatus(_sar,name)
end
end
end
end
return self
end
function CSAR:onafterStatus(From,Event,To)
self:T({From,Event,To})
local NumberOfSARPilots=0
for _,_unitName in pairs(self.csarUnits)do
NumberOfSARPilots=NumberOfSARPilots+1
end
local PilotsInFieldN=self:_CountActiveDownedPilots()
local PilotsBoarded=0
for _,_unitName in pairs(self.inTransitGroups)do
for _,_units in pairs(_unitName)do
PilotsBoarded=PilotsBoarded+1
end
end
if self.verbose>0 then
local text=string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d",
self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots)
self:T(text)
if self.verbose<2 then
self:I(text)
elseif self.verbose>1 then
self:I(text)
local m=MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition)
end
end
self:__Status(-20)
return self
end
function CSAR:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.LandingAfterEjection)
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PilotDead)
self:T(self.lid.."Stopped.")
return self
end
function CSAR:onbeforeApproach(From,Event,To,Heliname,Woundedgroupname)
self:T({From,Event,To,Heliname,Woundedgroupname})
self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname)
return self
end
function CSAR:onbeforeBoarded(From,Event,To,Heliname,Woundedgroupname)
self:T({From,Event,To,Heliname,Woundedgroupname})
self:_ScheduledSARFlight(Heliname,Woundedgroupname)
local Unit=UNIT:FindByName(Heliname)
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
local dropcoord=Unit:GetCoordinate()or COORDINATE:New(0,0,0)
local dropvec2=dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
local targetzone=task.Target:GetObject()
if(targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE")and targetzone:IsVec2InZone(dropvec2))
or(string.find(task.CSARPilotName,Woundedgroupname))then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
end
)
end
return self
end
function CSAR:onbeforeReturning(From,Event,To,Heliname,Woundedgroupname,IsAirPort)
self:T({From,Event,To,Heliname,Woundedgroupname})
return self
end
function CSAR:onbeforeRescued(From,Event,To,HeliUnit,HeliName,PilotsSaved)
self:T({From,Event,To,HeliName,HeliUnit})
self.rescues=self.rescues+1
self.rescuedpilots=self.rescuedpilots+PilotsSaved
local Unit=HeliUnit or UNIT:FindByName(HeliName)
if Unit and Unit:IsPlayer()and self.PlayerTaskQueue then
local playername=Unit:GetPlayerName()
self.PlayerTaskQueue:ForEach(
function(Task)
local task=Task
local subtype=task:GetSubType()
if Event==subtype and not task:IsDone()then
if task.Clients:HasUniqueID(playername)then
task:__Success(-1)
end
end
end
)
end
return self
end
function CSAR:onbeforePilotDown(From,Event,To,Group,Frequency,Leadername,CoordinatesText,Playername)
self:T({From,Event,To,Group,Frequency,Leadername,CoordinatesText,tostring(Playername)})
return self
end
function CSAR:onbeforeLanded(From,Event,To,HeliName,Airbase)
self:T({From,Event,To,HeliName,Airbase})
return self
end
function CSAR:onbeforeSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
function CSAR:onafterSave(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _savefile(filename,data)
local f=assert(io.open(filename,"wb"))
f:write(data)
f:close()
end
if lfs then
path=self.filepath or lfs.writedir()
end
filename=filename or self.filename
if path~=nil then
filename=path.."\\"..filename
end
local pilots=self.downedPilots
local data="playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n"
local n=0
for _,_grp in pairs(pilots)do
local DownedPilot=_grp
if DownedPilot and DownedPilot.alive then
local playerName=DownedPilot.player
local group=DownedPilot.group
local coalition=group:GetCoalition()
local country=group:GetCountry()
local description=DownedPilot.desc
local typeName=DownedPilot.typename
local freq=DownedPilot.frequency
local location=group:GetVec3()
local unitName=DownedPilot.originalUnit
local txt=string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
self:I(self.lid.."Saving to CSAR File: "..txt)
data=data..txt
end
end
_savefile(filename,data)
if self.enableLoadSave then
local interval=self.saveinterval
local filename=self.filename
local filepath=self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
function CSAR:onbeforeLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
filename=filename or self.filename
path=path or self.filepath
if not io then
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
return false
end
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: State file %s might not exist.",filename))
return false
end
end
function CSAR:onafterLoad(From,Event,To,path,filename)
self:T({From,Event,To,path,filename})
if not self.enableLoadSave then
return self
end
local function _loadfile(filename)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
return data
end
filename=filename or self.filename
path=path or self.filepath
if lfs then
path=path or lfs.writedir()
end
if path~=nil then
filename=path.."\\"..filename
end
local text=string.format("Loading CSAR state from file %s",filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local file=assert(io.open(filename,"rb"))
local loadeddata={}
for line in file:lines()do
loadeddata[#loadeddata+1]=line
end
file:close()
table.remove(loadeddata,1)
for _id,_entry in pairs(loadeddata)do
local dataset=UTILS.Split(_entry,",")
local playerName=dataset[1]
local vec3={}
vec3.x=tonumber(dataset[2])
vec3.y=tonumber(dataset[3])
vec3.z=tonumber(dataset[4])
local point=COORDINATE:NewFromVec3(vec3)
local coalition=tonumber(dataset[5])
local country=tonumber(dataset[6])
local description=dataset[7]
local typeName=dataset[8]
local unitName=dataset[9]
local freq=tonumber(dataset[10])
self:_AddCsar(coalition,country,point,typeName,unitName,playerName,freq,nil,description,nil)
end
return self
end
AIRWING={
ClassName="AIRWING",
verbose=0,
lid=nil,
menu=nil,
payloads={},
payloadcounter=0,
pointsCAP={},
pointsTANKER={},
pointsAWACS={},
pointsRecon={},
markpoints=false,
capOptionPatrolRaceTrack=false,
capFormation=nil,
capOptionVaryStartTime=nil,
capOptionVaryEndTime=nil,
}
AIRWING.version="0.9.6"
function AIRWING:New(warehousename,airwingname)
local self=BASE:Inherit(self,LEGION:New(warehousename,airwingname))
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!",warehousename))
return nil
end
self.lid=string.format("AIRWING %s | ",self.alias)
self.nflightsCAP=0
self.nflightsAWACS=0
self.nflightsRecon=0
self.nflightsTANKERboom=0
self.nflightsTANKERprobe=0
self.nflightsRecoveryTanker=0
self.nflightsRescueHelo=0
self.markpoints=false
self:AddTransition("*","FlightOnMission","*")
return self
end
function AIRWING:AddSquadron(Squadron)
table.insert(self.cohorts,Squadron)
self:AddAssetToSquadron(Squadron,Squadron.Ngroups)
if Squadron.attribute==GROUP.Attribute.AIR_AWACS then
self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.AWACS)
elseif Squadron.attribute==GROUP.Attribute.AIR_TANKER then
self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.TANKER)
end
self:NewPayload(Squadron.templategroup,-1,AUFTRAG.Type.RELOCATECOHORT,0)
Squadron:SetAirwing(self)
if Squadron:IsStopped()then
Squadron:Start()
end
return self
end
function AIRWING:NewPayload(Unit,Npayloads,MissionTypes,Performance)
Performance=Performance or 50
if type(Unit)=="string"then
local name=Unit
Unit=UNIT:FindByName(name)
if not Unit then
Unit=GROUP:FindByName(name)
end
end
if Unit then
if Unit:IsInstanceOf("GROUP")then
Unit=Unit:GetUnit(1)
end
if MissionTypes and type(MissionTypes)~="table"then
MissionTypes={MissionTypes}
end
local payload={}
payload.uid=self.payloadcounter
payload.unitname=Unit:GetName()
payload.aircrafttype=Unit:GetTypeName()
payload.pylons=Unit:GetTemplatePayload()
self:SetPayloadAmount(payload,Npayloads)
payload.capabilities={}
for _,missiontype in pairs(MissionTypes)do
local capability={}
capability.MissionType=missiontype
capability.Performance=Performance
table.insert(payload.capabilities,capability)
end
if not AUFTRAG.CheckMissionType(AUFTRAG.Type.ORBIT,MissionTypes)then
local capability={}
capability.MissionType=AUFTRAG.Type.ORBIT
capability.Performance=50
table.insert(payload.capabilities,capability)
end
if not AUFTRAG.CheckMissionType(AUFTRAG.Type.RELOCATECOHORT,MissionTypes)then
local capability={}
capability.MissionType=AUFTRAG.Type.RELOCATECOHORT
capability.Performance=50
table.insert(payload.capabilities,capability)
end
self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s",
payload.unitname,payload.aircrafttype,payload.uid,payload.navail,tostring(payload.unlimited),Performance,table.concat(MissionTypes,", ")))
table.insert(self.payloads,payload)
self.payloadcounter=self.payloadcounter+1
return payload
end
self:E(self.lid.."ERROR: No UNIT found to create PAYLOAD!")
return nil
end
function AIRWING:SetPayloadAmount(Payload,Navailable)
Navailable=Navailable or 99
if Payload then
Payload.unlimited=Navailable<0
if Payload.unlimited then
Payload.navail=1
else
Payload.navail=Navailable
end
end
return self
end
function AIRWING:IncreasePayloadAmount(Payload,N)
N=N or 1
if Payload and Payload.navail>=0 then
Payload.navail=Payload.navail+N
Payload.navail=math.max(Payload.navail,0)
end
return self
end
function AIRWING:GetPayloadAmount(Payload)
return Payload.navail
end
function AIRWING:GetPayloadCapabilities(Payload)
return Payload.capabilities
end
function AIRWING:AddPayloadCapability(Payload,MissionTypes,Performance)
if MissionTypes and type(MissionTypes)~="table"then
MissionTypes={MissionTypes}
end
Payload.capabilities=Payload.capabilities or{}
for _,missiontype in pairs(MissionTypes)do
local capability={}
capability.MissionType=missiontype
capability.Performance=Performance
table.insert(Payload.capabilities,capability)
end
return self
end
function AIRWING:FetchPayloadFromStock(UnitType,MissionType,Payloads)
if not self.payloads or#self.payloads==0 then
self:T(self.lid.."WARNING: No payloads in stock!")
return nil
end
if self.verbose>=4 then
self:I(self.lid..string.format("Looking for payload for unit type=%s and mission type=%s",UnitType,MissionType))
for i,_payload in pairs(self.payloads)do
local payload=_payload
local performance=self:GetPayloadPeformance(payload,MissionType)
self:I(self.lid..string.format("[%d] Payload type=%s navail=%d unlimited=%s",i,payload.aircrafttype,payload.navail,tostring(payload.unlimited)))
end
end
local function sortpayloads(a,b)
local pA=a
local pB=b
if a and b then
local performanceA=self:GetPayloadPeformance(a,MissionType)
local performanceB=self:GetPayloadPeformance(b,MissionType)
return(performanceA>performanceB)or(performanceA==performanceB and a.unlimited==true and b.unlimited~=true)or(performanceA==performanceB and a.unlimited==true and b.unlimited==true and a.navail>b.navail)
elseif not a then
self:I(self.lid..string.format("FF ERROR in sortpayloads: a is nil"))
return false
elseif not b then
self:I(self.lid..string.format("FF ERROR in sortpayloads: b is nil"))
return true
else
self:I(self.lid..string.format("FF ERROR in sortpayloads: a and b are nil"))
return false
end
end
local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads)do
if Payload.uid==payload.uid then
return true
end
end
else
return nil
end
return false
end
local payloads={}
for _,_payload in pairs(self.payloads)do
local payload=_payload
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities)
local goforit=specialpayload or(specialpayload==nil and compatible)
if payload.aircrafttype==UnitType and payload.navail>0 and goforit then
table.insert(payloads,payload)
end
end
if self.verbose>=4 then
self:I(self.lid..string.format("Sorted payloads for mission type %s and aircraft type=%s:",MissionType,UnitType))
for _,_payload in ipairs(self.payloads)do
local payload=_payload
if payload.aircrafttype==UnitType and AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities)then
local performace=self:GetPayloadPeformance(payload,MissionType)
self:I(self.lid..string.format("- %s payload for %s: avail=%d performace=%d",MissionType,payload.aircrafttype,payload.navail,performace))
end
end
end
if#payloads==0 then
self:T(self.lid..string.format("WARNING: Could not find a payload for airframe %s mission type %s!",UnitType,MissionType))
return nil
elseif#payloads==1 then
local payload=payloads[1]
if not payload.unlimited then
payload.navail=payload.navail-1
end
return payload
else
table.sort(payloads,sortpayloads)
local payload=payloads[1]
if not payload.unlimited then
payload.navail=payload.navail-1
end
return payload
end
end
function AIRWING:ReturnPayloadFromAsset(asset)
local payload=asset.payload
if payload then
if not payload.unlimited then
payload.navail=payload.navail+1
end
asset.payload=nil
else
self:E(self.lid.."ERROR: asset had no payload attached!")
end
end
function AIRWING:AddAssetToSquadron(Squadron,Nassets)
if Squadron then
local Group=GROUP:FindByName(Squadron.templatename)
if Group then
local text=string.format("Adding asset %s to squadron %s",Group:GetName(),Squadron.name)
self:T(self.lid..text)
self:AddAsset(Group,Nassets,nil,nil,nil,nil,Squadron.skill,Squadron.livery,Squadron.name)
else
self:E(self.lid.."ERROR: Group does not exist!")
end
else
self:E(self.lid.."ERROR: Squadron does not exit!")
end
return self
end
function AIRWING:GetSquadron(SquadronName)
local squad=self:_GetCohort(SquadronName)
return squad
end
function AIRWING:GetSquadronOfAsset(Asset)
return self:GetSquadron(Asset.squadname)
end
function AIRWING:RemoveAssetFromSquadron(Asset)
local squad=self:GetSquadronOfAsset(Asset)
if squad then
squad:DelAsset(Asset)
end
end
function AIRWING:SetNumberCAP(n)
self.nflightsCAP=n or 1
return self
end
function AIRWING:SetCAPFormation(Formation)
self.capFormation=Formation
return self
end
function AIRWING:SetCapCloseRaceTrack(OnOff)
self.capOptionPatrolRaceTrack=OnOff
return self
end
function AIRWING:SetCapStartTimeVariation(Start,End)
self.capOptionVaryStartTime=Start or 5
self.capOptionVaryEndTime=End or 60
return self
end
function AIRWING:SetNumberTankerBoom(Nboom)
self.nflightsTANKERboom=Nboom or 1
return self
end
function AIRWING:ShowPatrolPointMarkers(onoff)
if onoff then
self.markpoints=true
else
self.markpoints=false
end
return self
end
function AIRWING:SetNumberTankerProbe(Nprobe)
self.nflightsTANKERprobe=Nprobe or 1
return self
end
function AIRWING:SetNumberAWACS(n)
self.nflightsAWACS=n or 1
return self
end
function AIRWING:SetNumberRecon(n)
self.nflightsRecon=n or 1
return self
end
function AIRWING:SetNumberRescuehelo(n)
self.nflightsRescueHelo=n or 1
return self
end
function AIRWING:_PatrolPointMarkerText(point)
local text=string.format("%s Occupied=%d, \nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts",
point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed)
return text
end
function AIRWING:UpdatePatrolPointMarker(point)
if self.markpoints then
local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts",
point.type,point.noccupied,point.heading,point.leg,point.altitude,point.speed)
point.marker:UpdateText(text,1)
end
end
function AIRWING:NewPatrolPoint(Type,Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem)
if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE")then
Coordinate=Coordinate:GetCoordinate()
end
local patrolpoint={}
patrolpoint.type=Type or"Unknown"
patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10,15)),math.random(360))
patrolpoint.heading=Heading or math.random(360)
patrolpoint.leg=LegLength or 15
patrolpoint.altitude=Altitude or math.random(10,20)*1000
patrolpoint.speed=Speed or 350
patrolpoint.noccupied=0
patrolpoint.refuelsystem=RefuelSystem
if self.markpoints then
patrolpoint.marker=MARKER:New(Coordinate,"New Patrol Point"):ToAll()
AIRWING.UpdatePatrolPointMarker(patrolpoint)
end
return patrolpoint
end
function AIRWING:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength)
local patrolpoint=self:NewPatrolPoint("CAP",Coordinate,Altitude,Speed,Heading,LegLength)
table.insert(self.pointsCAP,patrolpoint)
return self
end
function AIRWING:AddPatrolPointRecon(Coordinate,Altitude,Speed,Heading,LegLength)
local patrolpoint=self:NewPatrolPoint("RECON",Coordinate,Altitude,Speed,Heading,LegLength)
table.insert(self.pointsRecon,patrolpoint)
return self
end
function AIRWING:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem)
local patrolpoint=self:NewPatrolPoint("Tanker",Coordinate,Altitude,Speed,Heading,LegLength,RefuelSystem)
table.insert(self.pointsTANKER,patrolpoint)
return self
end
function AIRWING:AddPatrolPointAWACS(Coordinate,Altitude,Speed,Heading,LegLength)
local patrolpoint=self:NewPatrolPoint("AWACS",Coordinate,Altitude,Speed,Heading,LegLength)
table.insert(self.pointsAWACS,patrolpoint)
return self
end
function AIRWING:SetAirboss(airboss)
self.airboss=airboss
return self
end
function AIRWING:SetTakeoffType(TakeoffType)
TakeoffType=TakeoffType or"Cold"
if TakeoffType:lower()=="hot"then
self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot
elseif TakeoffType:lower()=="cold"then
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
elseif TakeoffType:lower()=="air"then
self.takeoffType=COORDINATE.WaypointType.TurningPoint
else
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
end
return self
end
function AIRWING:SetTakeoffCold()
self:SetTakeoffType("Cold")
return self
end
function AIRWING:SetTakeoffHot()
self:SetTakeoffType("Hot")
return self
end
function AIRWING:SetTakeoffAir()
self:SetTakeoffType("Air")
return self
end
function AIRWING:SetDespawnAfterLanding(Switch)
if Switch then
self.despawnAfterLanding=Switch
else
self.despawnAfterLanding=true
end
return self
end
function AIRWING:SetDespawnAfterHolding(Switch)
if Switch then
self.despawnAfterHolding=Switch
else
self.despawnAfterHolding=true
end
return self
end
function AIRWING:onafterStart(From,Event,To)
self:GetParent(self,AIRWING).onafterStart(self,From,Event,To)
self:I(self.lid..string.format("Starting AIRWING v%s",AIRWING.version))
end
function AIRWING:onafterStatus(From,Event,To)
self:GetParent(self).onafterStatus(self,From,Event,To)
local fsmstate=self:GetState()
self:CheckCAP()
self:CheckTANKER()
self:CheckAWACS()
self:CheckRescuhelo()
self:CheckRECON()
self:_TacticalOverview()
self:CheckTransportQueue()
self:CheckMissionQueue()
if self.verbose>=1 then
local Nmissions=self:CountMissionsInQueue()
local Npayloads=self:CountPayloadsInStock(AUFTRAG.Type)
local Npq,Np,Nq=self:CountAssetsOnMission()
local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)",self:CountAssets(),Npq,Np,Nq)
local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s",fsmstate,Nmissions,Npayloads,#self.payloads,#self.cohorts,assets)
self:I(self.lid..text)
end
if self.verbose>=2 then
local text=string.format("Missions Total=%d:",#self.missionqueue)
for i,_mission in pairs(self.missionqueue)do
local mission=_mission
local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end
local assets=string.format("%d/%d",mission:CountOpsGroups(),mission:GetNumberOfRequiredAssets())
local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage())
local mystatus=mission:GetLegionStatus(self)
text=text..string.format("\n[%d] %s %s: Status=%s [%s], Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mystatus,mission.status,prio,assets,target)
end
self:I(self.lid..text)
end
if self.verbose>=3 then
local text="Squadrons:"
for i,_squadron in pairs(self.cohorts)do
local squadron=_squadron
local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName)or"N/A"
local modex=squadron.modex and squadron.modex or-1
local skill=squadron.skill and tostring(squadron.skill)or"N/A"
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",squadron.name,squadron:GetState(),squadron.aircrafttype,squadron:CountAssets(true),#squadron.assets,callsign,modex,skill)
end
self:I(self.lid..text)
end
end
function AIRWING:_GetPatrolData(PatrolPoints,RefuelSystem)
local function sort(a,b)
return a.noccupied<b.noccupied
end
if PatrolPoints and#PatrolPoints>0 then
table.sort(PatrolPoints,sort)
for _,_patrolpoint in pairs(PatrolPoints)do
local patrolpoint=_patrolpoint
if(RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem)or RefuelSystem==nil or patrolpoint.refuelsystem==nil then
return patrolpoint
end
end
end
return self:NewPatrolPoint()
end
function AIRWING:CheckCAP()
local Ncap=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:IsNotOver()and(mission.type==AUFTRAG.Type.GCICAP or mission.type==AUFTRAG.Type.PATROLRACETRACK)and mission.patroldata then
Ncap=Ncap+1
end
end
for i=1,self.nflightsCAP-Ncap do
local patrol=self:_GetPatrolData(self.pointsCAP)
local altitude=patrol.altitude+1000*patrol.noccupied
local missionCAP=nil
if self.capOptionPatrolRaceTrack then
missionCAP=AUFTRAG:NewPATROL_RACETRACK(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,self.capFormation)
else
missionCAP=AUFTRAG:NewGCICAP(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg)
end
if self.capOptionVaryStartTime then
local ClockStart=math.random(self.capOptionVaryStartTime,self.capOptionVaryEndTime)
missionCAP:SetTime(ClockStart)
end
missionCAP.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
self:AddMission(missionCAP)
end
return self
end
function AIRWING:CheckRECON()
local Ncap=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:IsNotOver()and mission.type==AUFTRAG.Type.RECON and mission.patroldata then
Ncap=Ncap+1
end
end
for i=1,self.nflightsRecon-Ncap do
local patrol=self:_GetPatrolData(self.pointsRecon)
local altitude=patrol.altitude
local ZoneSet=SET_ZONE:New()
local Zone=ZONE_RADIUS:New(self.alias.." Recon "..math.random(1,10000),patrol.coord:GetVec2(),UTILS.NMToMeters(patrol.leg/2))
ZoneSet:AddZone(Zone)
if self.Debug then
Zone:DrawZone(self.coalition,{0,0,1},Alpha,FillColor,FillAlpha,2,true)
end
local missionRECON=AUFTRAG:NewRECON(ZoneSet,patrol.speed,patrol.altitude,true)
missionRECON.patroldata=patrol
missionRECON.categories={AUFTRAG.Category.AIRCRAFT}
patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
self:AddMission(missionRECON)
end
return self
end
function AIRWING:CheckTANKER()
local Nboom=0
local Nprob=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:IsNotOver()and mission.type==AUFTRAG.Type.TANKER and mission.patroldata then
if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then
Nboom=Nboom+1
elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then
Nprob=Nprob+1
end
end
end
for i=1,self.nflightsTANKERboom-Nboom do
local patrol=self:_GetPatrolData(self.pointsTANKER)
local altitude=patrol.altitude+1000*patrol.noccupied
local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)
mission.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
self:AddMission(mission)
end
for i=1,self.nflightsTANKERprobe-Nprob do
local patrol=self:_GetPatrolData(self.pointsTANKER)
local altitude=patrol.altitude+1000*patrol.noccupied
local mission=AUFTRAG:NewTANKER(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg,Unit.RefuelingSystem.PROBE_AND_DROGUE)
mission.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
self:AddMission(mission)
end
return self
end
function AIRWING:CheckAWACS()
local N=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:IsNotOver()and mission.type==AUFTRAG.Type.AWACS and mission.patroldata then
N=N+1
end
end
for i=1,self.nflightsAWACS-N do
local patrol=self:_GetPatrolData(self.pointsAWACS)
local altitude=patrol.altitude+1000*patrol.noccupied
local mission=AUFTRAG:NewAWACS(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg)
mission.patroldata=patrol
patrol.noccupied=patrol.noccupied+1
if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end
self:AddMission(mission)
end
return self
end
function AIRWING:CheckRescuhelo()
local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO})
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)
local tankers=self:GetAssetsOnMission(AUFTRAG.Type.TANKER)
if#tankers>0 then
local tankeropt={}
for _,_tanker in pairs(tankers)do
local tanker=_tanker
if flightgroup.refueltype and flightgroup.refueltype==tanker.flightgroup.tankertype then
local tankercoord=tanker.flightgroup.group:GetCoordinate()
local assetcoord=flightgroup.group:GetCoordinate()
local dist=assetcoord:Get2DDistance(tankercoord)
if dist>5 then
table.insert(tankeropt,{tanker=tanker,dist=dist})
end
end
end
table.sort(tankeropt,function(a,b)return a.dist<b.dist end)
if#tankeropt>0 then
return tankeropt[1].tanker
else
return nil
end
end
return nil
end
function AIRWING:SetUsingOpsAwacs(ConnectecdAwacs)
self:I(self.lid.."Added AWACS Object: "..ConnectecdAwacs:GetName()or"unknown")
self.UseConnectedOpsAwacs=true
self.ConnectedOpsAwacs=ConnectecdAwacs
return self
end
function AIRWING:RemoveUsingOpsAwacs()
self:I(self.lid.."Reomve AWACS Object: "..self.ConnectedOpsAwacs:GetName()or"unknown")
self.UseConnectedOpsAwacs=false
return self
end
function AIRWING:onafterFlightOnMission(From,Event,To,FlightGroup,Mission)
self:T(self.lid..string.format("Group %s on %s mission %s",FlightGroup:GetName(),Mission:GetType(),Mission:GetName()))
if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then
self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission)
end
end
function AIRWING:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads)
if MissionTypes then
if type(MissionTypes)=="string"then
MissionTypes={MissionTypes}
end
end
if UnitTypes then
if type(UnitTypes)=="string"then
UnitTypes={UnitTypes}
end
end
local function _checkUnitTypes(payload)
if UnitTypes then
for _,unittype in pairs(UnitTypes)do
if unittype==payload.aircrafttype then
return true
end
end
else
return true
end
return false
end
local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads)do
if Payload.uid==payload.uid then
return true
end
end
else
return nil
end
return false
end
local n=0
for _,_payload in pairs(self.payloads)do
local payload=_payload
for _,MissionType in pairs(MissionTypes)do
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities)
local goforit=specialpayload or(specialpayload==nil and compatible)
if goforit and _checkUnitTypes(payload)then
if payload.unlimited then
return 999
else
n=n+payload.navail
end
end
end
end
return n
end
function AIRWING:GetPayloadPeformance(Payload,MissionType)
if Payload then
for _,Capability in pairs(Payload.capabilities)do
local capability=Capability
if capability.MissionType==MissionType then
return capability.Performance
end
end
else
self:E(self.lid.."ERROR: Payload is nil!")
end
return-1
end
function AIRWING:GetPayloadMissionTypes(Payload)
local missiontypes={}
for _,Capability in pairs(Payload.capabilities)do
local capability=Capability
table.insert(missiontypes,capability.MissionType)
end
return missiontypes
end
ARMYGROUP={
ClassName="ARMYGROUP",
formationPerma=nil,
engage={},
}
ARMYGROUP.version="1.0.3"
function ARMYGROUP:New(group)
local og=_DATABASE:GetOpsGroup(group)
if og then
og:I(og.lid..string.format("WARNING: OPS group already exists in data base!"))
return og
end
local self=BASE:Inherit(self,OPSGROUP:New(group))
self.lid=string.format("ARMYGROUP %s | ",self.groupname)
self:SetDefaultROE()
self:SetDefaultAlarmstate()
self:SetDefaultEPLRS(self.isEPLRS)
self:SetDefaultEmission()
self:SetDetection()
self:SetPatrolAdInfinitum(false)
self:SetRetreatZones()
self:AddTransition("*","FullStop","Holding")
self:AddTransition("*","Cruise","Cruising")
self:AddTransition("*","RTZ","Returning")
self:AddTransition("Holding","Returned","Returned")
self:AddTransition("Returning","Returned","Returned")
self:AddTransition("*","Detour","OnDetour")
self:AddTransition("OnDetour","DetourReached","Cruising")
self:AddTransition("*","Retreat","Retreating")
self:AddTransition("Retreating","Retreated","Retreated")
self:AddTransition("*","Suppressed","*")
self:AddTransition("*","Unsuppressed","*")
self:AddTransition("Cruising","EngageTarget","Engaging")
self:AddTransition("Holding","EngageTarget","Engaging")
self:AddTransition("OnDetour","EngageTarget","Engaging")
self:AddTransition("Engaging","Disengage","Cruising")
self:AddTransition("*","Rearm","Rearm")
self:AddTransition("Rearm","Rearming","Rearming")
self:AddTransition("*","Rearmed","Cruising")
self:_InitWaypoints()
self:_InitGroup()
self:HandleEvent(EVENTS.Birth,self.OnEventBirth)
self:HandleEvent(EVENTS.Dead,self.OnEventDead)
self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit)
self:HandleEvent(EVENTS.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)
self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(2,30)
_DATABASE:AddOpsGroup(self)
return self
end
function ARMYGROUP:SetPatrolAdInfinitum(switch)
if switch==false then
self.adinfinitum=false
else
self.adinfinitum=true
end
return self
end
function ARMYGROUP:GetClosestRoad()
local coord=self:GetCoordinate():GetClosestPointToRoad()
return coord
end
function ARMYGROUP:GetClosestRoadDist()
local road=self:GetClosestRoad()
if road then
local dist=road:Get2DDistance(self:GetCoordinate())
return dist
end
return math.huge
end
function ARMYGROUP:AddTaskFireAtPoint(Coordinate,Clock,Radius,Nshots,WeaponType,Prio)
Coordinate=self:_CoordinateFromObject(Coordinate)
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType)
local task=self:AddTask(DCStask,Clock,nil,Prio)
return task
end
function ARMYGROUP:AddTaskBarrage(Clock,Heading,Alpha,Altitude,Radius,Nshots,WeaponType,Prio)
Heading=Heading or 0
Alpha=Alpha or 60
Altitude=Altitude or 100
local distance=Altitude/math.tan(math.rad(Alpha))
local a=self:GetVec2()
local vec2=UTILS.Vec2Translate(a,distance,Heading)
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,vec2,Radius,Nshots,WeaponType,Altitude)
local task=self:AddTask(DCStask,Clock,nil,Prio)
return task
end
function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate,Waypoint,Radius,Nshots,WeaponType,Prio)
Coordinate=self:_CoordinateFromObject(Coordinate)
Waypoint=Waypoint or self:GetWaypointNext()
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType)
local task=self:AddTaskWaypoint(DCStask,Waypoint,nil,Prio)
return task
end
function ARMYGROUP:AddTaskAttackGroup(TargetGroup,WeaponExpend,WeaponType,Clock,Prio)
local DCStask=CONTROLLABLE.TaskAttackGroup(nil,TargetGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack)
local task=self:AddTask(DCStask,Clock,nil,Prio)
return task
end
function ARMYGROUP:AddTaskCargoGroup(GroupSet,PickupZone,DeployZone,Clock,Prio)
local DCStask={}
DCStask.id="CargoTransport"
DCStask.params={}
DCStask.params.cargoqueu=1
local task=self:AddTask(DCStask,Clock,nil,Prio)
return task
end
function ARMYGROUP:SetRetreatZones(RetreatZoneSet)
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
return self
end
function ARMYGROUP:AddRetreatZone(RetreatZone)
self.retreatZones:AddZone(RetreatZone)
return self
end
function ARMYGROUP:SetSuppressionOn(Tave,Tmin,Tmax)
self.suppressionOn=true
self.TsuppressMin=Tmin or 1
self.TsuppressMin=math.max(self.TsuppressMin,1)
self.TsuppressMax=Tmax or 15
self.TsuppressMax=math.max(self.TsuppressMax,self.TsuppressMin)
self.TsuppressAve=Tave or 10
self.TsuppressAve=math.max(self.TsuppressMin)
self.TsuppressAve=math.min(self.TsuppressMax)
self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.TsuppressAve))
self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.TsuppressMin))
self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.TsuppressMax))
return self
end
function ARMYGROUP:SetSuppressionOff()
self.suppressionOn=false
end
function ARMYGROUP:IsHolding()
return self:Is("Holding")
end
function ARMYGROUP:IsCruising()
return self:Is("Cruising")
end
function ARMYGROUP:IsOnDetour()
return self:Is("OnDetour")
end
function ARMYGROUP:IsCombatReady()
local combatready=true
if self:IsRearming()or self:IsRetreating()or self:IsOutOfAmmo()or self:IsEngaging()or self:IsDead()or self:IsStopped()or self:IsInUtero()then
combatready=false
end
if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsLoaded()or self:IsCargo()or self:IsCarrier()then
combatready=false
end
return combatready
end
function ARMYGROUP:Status()
local fsmstate=self:GetState()
local alive=self:IsAlive()
if alive then
self:_UpdatePosition()
self:_CheckDetectedUnits()
self:_CheckAmmoStatus()
self:_CheckDamage()
self:_CheckStuck()
if self:IsEngaging()then
self:_UpdateEngageTarget()
end
if self:IsWaiting()then
if self.Twaiting and self.dTwait then
if timer.getAbsTime()>self.Twaiting+self.dTwait then
self.Twaiting=nil
self.dTwait=nil
if self:_CountPausedMissions()>0 then
self:UnpauseMission()
else
self:Cruise()
end
end
end
end
local mission=self:GetMissionCurrent()
if mission and mission.updateDCSTask then
if mission.type==AUFTRAG.Type.CAPTUREZONE then
local Task=mission:GetGroupWaypointTask(self)
if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then
self:_UpdateTask(Task,mission)
end
end
end
else
self:_CheckDamage()
end
if alive~=nil then
if self.verbose>=1 then
local nelem=self:CountElements()
local Nelem=#self.elements
local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks()
local nMissions=self:CountRemainingMissison()
local roe=self:GetROE()or-1
local als=self:GetAlarmstate()or-1
local wpidxCurr=self.currentwp
local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0
local wpidxNext=self:GetWaypointIndexNext()or 0
local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0
local wpN=#self.waypoints or 0
local wpF=tostring(self.passedfinalwp)
local speed=UTILS.MpsToKnots(self.velocity or 0)
local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed())
local alt=self.position and self.position.y or 0
local hdg=self.heading or 0
local formation=self.option.Formation or"unknown"
local life=self.life or 0
local ammo=self:GetAmmoTot().Total
local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off"
local cargo=0
for _,_element in pairs(self.elements)do
local element=_element
cargo=cargo+element.weightCargo
end
local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) [%s] | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f",
fsmstate,nelem,Nelem,roe,als,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,formation,hdg,ammo,ndetected,cargo)
self:I(self.lid..text)
end
else
if self.verbose>=1 then
local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive()))
self:I(self.lid..text)
end
end
if self.verbose>=2 then
local text="Elements:"
for i,_element in pairs(self.elements)do
local element=_element
local name=element.name
local status=element.status
local unit=element.unit
local life,life0=self:GetLifePoints(element)
local life0=element.life0
local ammo=self:GetAmmoElement(element)
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
i,name,status,life,life0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,element.weightCargo,element.weightMaxCargo)
end
if#self.elements==0 then
text=text.." none!"
end
self:T(self.lid..text)
end
if self:IsCruising()and self.detectionOn and self.engagedetectedOn then
local targetgroup,targetdist=self:_GetDetectedTarget()
if targetgroup then
self:T(self.lid..string.format("Engaging target group %s at distance %d meters",targetgroup:GetName(),targetdist))
self:EngageTarget(targetgroup)
end
end
self:_CheckCargoTransport()
self:_PrintTaskAndMissionStatus()
end
function ARMYGROUP:onafterElementSpawned(From,Event,To,Element)
self:T(self.lid..string.format("Element spawned %s",Element.name))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED)
end
function ARMYGROUP:onafterSpawned(From,Event,To)
self:T(self.lid..string.format("Group spawned!"))
if self.verbose>=1 then
local text=string.format("Initialized Army Group %s:\n",self.groupname)
text=text..string.format("Unit type    = %s\n",self.actype)
text=text..string.format("Speed max    = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax))
text=text..string.format("Speed cruise = %.1f Knots\n",UTILS.KmphToKnots(self.speedCruise))
text=text..string.format("Weight       = %.1f kg\n",self:GetWeightTotal())
text=text..string.format("Cargo bay    = %.1f kg\n",self:GetFreeCargobay())
text=text..string.format("Has EPLRS    = %s\n",tostring(self.isEPLRS))
text=text..string.format("Elements     = %d\n",#self.elements)
text=text..string.format("Waypoints    = %d\n",#self.waypoints)
text=text..string.format("Radio        = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On))
text=text..string.format("Ammo         = %d (G=%d/R=%d/M=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Missiles)
text=text..string.format("FSM state    = %s\n",self:GetState())
text=text..string.format("Is alive     = %s\n",tostring(self:IsAlive()))
text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated()))
self:I(self.lid..text)
end
self:_UpdatePosition()
self.isDead=false
self.isDestroyed=false
if self.isAI then
self:SwitchROE(self.option.ROE)
self:SwitchAlarmstate(self.option.Alarm)
self:SwitchEmission(self.option.Emission)
self:SwitchEPLRS(self.option.EPLRS)
self:SwitchInvisible(self.option.Invisible)
self:SwitchImmortal(self.option.Immortal)
self:_SwitchTACAN()
if self.radioDefault then
self:SwitchRadio(self.radioDefault.Freq,self.radioDefault.Modu)
else
self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,true)
end
if not self.option.Formation then
end
local Nwp=#self.waypoints
if Nwp>1 and self.isMobile then
self:T(self.lid..string.format("Got %d waypoints on spawn ==> Cruise in -1.0 sec!",Nwp))
local wp=self:GetWaypointNext()
self.option.Formation=wp.action
self:__Cruise(-1)
else
self:T(self.lid.."No waypoints on spawn ==> Full Stop!")
self:FullStop()
end
end
end
function ARMYGROUP:onbeforeUpdateRoute(From,Event,To,n,N,Speed,Formation)
local allowed=true
local trepeat=nil
if self:IsWaiting()then
self:T(self.lid.."Update route denied. Group is WAITING!")
return false
elseif self:IsInUtero()then
self:T(self.lid.."Update route denied. Group is INUTERO!")
return false
elseif self:IsDead()then
self:T(self.lid.."Update route denied. Group is DEAD!")
return false
elseif self:IsStopped()then
self:T(self.lid.."Update route denied. Group is STOPPED!")
return false
elseif self:IsHolding()then
self:T(self.lid.."Update route denied. Group is holding position!")
return false
elseif self:IsEngaging()then
self:T(self.lid.."Update route allowed. Group is engaging!")
return true
end
if self.taskcurrent>0 then
local task=self:GetTaskByID(self.taskcurrent)
if task then
if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
self:T2(self.lid.."Allowing update route for Task: PatrolZone")
elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then
self:T2(self.lid.."Allowing update route for Task: ReconMission")
elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then
self:T2(self.lid.."Allowing update route for Task: Relocate Cohort")
elseif task.dcstask.id==AUFTRAG.SpecialTask.REARMING then
self:T2(self.lid.."Allowing update route for Task: Rearming")
else
local taskname=task and task.description or"No description"
self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname)))
allowed=false
end
else
self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent))
allowed=false
end
end
if not self.isAI then
allowed=false
end
self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat)))
if trepeat then
self:__UpdateRoute(trepeat,n)
end
return allowed
end
function ARMYGROUP:onafterUpdateRoute(From,Event,To,n,N,Speed,Formation)
n=n or self:GetWaypointIndexNext(self.adinfinitum)
N=N or#self.waypoints
N=math.min(N,#self.waypoints)
local text=string.format("Update route state=%s: n=%s, N=%s, Speed=%s, Formation=%s",self:GetState(),tostring(n),tostring(N),tostring(Speed),tostring(Formation))
self:T(self.lid..text)
local waypoints={}
local wp=self.waypoints[n]
local coordinate=self:GetCoordinate()
local coordRoad=coordinate:GetClosestPointToRoad()
local roaddist=coordinate:Get2DDistance(coordRoad)
local formation0=wp.action
if formation0==ENUMS.Formation.Vehicle.OnRoad then
if roaddist>10 then
formation0=ENUMS.Formation.Vehicle.OffRoad
else
formation0=ENUMS.Formation.Vehicle.OnRoad
end
end
local current=coordinate:WaypointGround(UTILS.MpsToKmph(self.speedWp),formation0)
table.insert(waypoints,1,current)
if N-n>0 then
for j=n,N do
local i=j-1
if i==0 then
i=self.currentwp
end
local wp=UTILS.DeepCopy(self.waypoints[j])
local wp0=self.waypoints[i]
if false and self.attribute==GROUP.Attribute.GROUND_APC then
local text=string.format("FF Update: i=%d, wp[i]=%s, wp[i-1]=%s",i,wp.action,wp0.action)
env.info(text)
end
if Speed then
wp.speed=UTILS.KnotsToMps(tonumber(Speed))
else
if wp.speed<0.1 then
wp.speed=UTILS.KmphToMps(self.speedCruise)
end
end
if self.formationPerma then
wp.action=self.formationPerma
elseif Formation then
wp.action=Formation
end
if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp0.roaddist>=0 then
local wproad=wp0.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad)
table.insert(waypoints,wproad)
end
if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>=0 then
wp.action=ENUMS.Formation.Vehicle.OffRoad
local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad)
table.insert(waypoints,wproad)
end
table.insert(waypoints,wp)
end
else
local wp=UTILS.DeepCopy(self.waypoints[n])
if wp.speed<0.1 then
wp.speed=UTILS.KmphToMps(self.speedCruise)
end
local formation=wp.action
if self.formationPerma then
formation=self.formationPerma
elseif Formation then
formation=Formation
end
if formation==ENUMS.Formation.Vehicle.OnRoad then
if roaddist>10 then
local wproad=coordRoad:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad)
table.insert(waypoints,wproad)
end
if wp.roaddist>10 then
local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed),ENUMS.Formation.Vehicle.OnRoad)
table.insert(waypoints,wproad)
end
end
if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>10 then
wp.action=ENUMS.Formation.Vehicle.OffRoad
end
table.insert(waypoints,wp)
end
local wp=waypoints[1]
self.option.Formation=wp.action
self.speedWp=wp.speed
self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp))
if self.verbose>=10 then
for i,_wp in pairs(waypoints)do
local wp=_wp
local text=string.format("WP #%d UID=%d Formation=%s: Speed=%d m/s, Alt=%d m, Type=%s",i,wp.uid and wp.uid or-1,wp.action,wp.speed,wp.alt,wp.type)
local coord=COORDINATE:NewFromWaypoint(wp):MarkToAll(text)
self:I(text)
end
end
if self:IsEngaging()or not self.passedfinalwp then
self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Formation=%s",
self.currentwp,n,#waypoints,#self.waypoints,UTILS.MpsToKnots(self.speedWp),tostring(self.option.Formation)))
self:Route(waypoints)
else
self:T(self.lid..string.format("WARNING: Passed final WP when UpdateRoute() ==> Full Stop!"))
self:FullStop()
end
end
function ARMYGROUP:onafterGotoWaypoint(From,Event,To,UID,Speed,Formation)
local n=self:GetWaypointIndex(UID)
if n then
Speed=Speed or self:GetSpeedToWaypoint(n)
self:__UpdateRoute(-0.01,n,nil,Speed,Formation)
end
end
function ARMYGROUP:onafterDetour(From,Event,To,Coordinate,Speed,Formation,ResumeRoute)
for _,_wp in pairs(self.waypoints)do
local wp=_wp
if wp.detour then
self:RemoveWaypointByID(wp.uid)
end
end
Speed=Speed or self:GetSpeedCruise()
local uid=self:GetWaypointCurrentUID()
local wp=self:AddWaypoint(Coordinate,Speed,uid,Formation,true)
if ResumeRoute then
wp.detour=1
else
wp.detour=0
end
end
function ARMYGROUP:onafterOutOfAmmo(From,Event,To)
self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime()))
local task=self:GetTaskCurrent()
if task then
if task.dcstask.id=="FireAtPoint"or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then
self:T(self.lid..string.format("Cancelling current %s task because out of ammo!",task.dcstask.id))
self:TaskCancel(task)
end
end
if self.rearmOnOutOfAmmo then
local truck,dist=self:FindNearestAmmoSupply(30)
if truck then
self:T(self.lid..string.format("Found Ammo Truck %s [%s]",truck:GetName(),truck:GetTypeName()))
local Coordinate=truck:GetCoordinate()
self:__Rearm(-1,Coordinate)
return
end
end
if self.retreatOnOutOfAmmo then
self:T(self.lid.."Retreat on out of ammo")
self:__Retreat(-1)
return
end
if self.rtzOnOutOfAmmo and not self:IsMissionTypeInQueue(AUFTRAG.Type.REARMING)then
self:T(self.lid.."RTZ on out of ammo")
self:__RTZ(-1)
end
end
function ARMYGROUP:onbeforeRearm(From,Event,To,Coordinate,Formation)
local dt=nil
local allowed=true
if self:IsOnMission()then
local mission=self:GetMissionCurrent()
if mission and mission.type~=AUFTRAG.Type.REARMING then
self:T(self.lid.."Rearm command but have current mission ==> Pausing mission!")
self:PauseMission()
dt=-0.1
allowed=false
else
self:T(self.lid.."Rearm command and current mission is REARMING ==> Transition ALLOWED!")
end
end
if self:IsEngaging()then
self:T(self.lid.."Rearm command but currently engaging ==> Disengage!")
self:Disengage()
dt=-0.1
allowed=false
end
if allowed and not Coordinate then
local truck=self:FindNearestAmmoSupply()
if truck and truck:IsAlive()then
self:__Rearm(-0.1,truck:GetCoordinate(),Formation)
end
return false
end
if dt then
self:T(self.lid..string.format("Trying Rearm again in %.2f sec",dt))
self:__Rearm(dt,Coordinate,Formation)
allowed=false
end
return allowed
end
function ARMYGROUP:onafterRearm(From,Event,To,Coordinate,Formation)
self:T(self.lid..string.format("Group send to rearm"))
local uid=self:GetWaypointCurrentUID()
local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true)
wp.detour=0
end
function ARMYGROUP:onafterRearmed(From,Event,To)
self:T(self.lid.."Group rearmed")
local mission=self:GetMissionCurrent()
if mission and mission.type==AUFTRAG.Type.REARMING then
self:MissionDone(mission)
else
self:_CheckGroupDone(1)
end
end
function ARMYGROUP:onbeforeRTZ(From,Event,To,Zone,Formation)
self:T2(self.lid.."onbeforeRTZ")
local zone=Zone or self.homezone
if zone then
if(not self.isMobile)and(not self:IsInZone(zone))then
self:Teleport(zone:GetCoordinate(),0,true)
self:__RTZ(-1,Zone,Formation)
return false
end
else
return false
end
return true
end
function ARMYGROUP:onafterRTZ(From,Event,To,Zone,Formation)
self:T2(self.lid.."onafterRTZ")
local zone=Zone or self.homezone
self:CancelAllMissions()
if zone then
if self:IsInZone(zone)then
self:Returned()
else
self:T(self.lid..string.format("RTZ to Zone %s",zone:GetName()))
local Coordinate=zone:GetRandomCoordinate()
local uid=self:GetWaypointCurrentUID()
local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true)
wp.detour=0
end
else
self:T(self.lid.."ERROR: No RTZ zone given!")
end
end
function ARMYGROUP:onafterReturned(From,Event,To)
self:T(self.lid..string.format("Group returned"))
if self.legion then
self:T(self.lid..string.format("Adding group back to warehouse stock"))
self.legion:__AddAsset(10,self.group,1)
end
end
function ARMYGROUP:onafterRearming(From,Event,To)
local pos=self:GetCoordinate()
local wp=pos:WaypointGround(0)
self:Route({wp})
end
function ARMYGROUP:onbeforeRetreat(From,Event,To,Zone,Formation)
if not Zone then
local a=self:GetVec2()
local distmin=math.huge
local zonemin=nil
for _,_zone in pairs(self.retreatZones:GetSet())do
local zone=_zone
local b=zone:GetVec2()
local dist=UTILS.VecDist2D(a,b)
if dist<distmin then
distmin=dist
zonemin=zone
end
end
if zonemin then
self:__Retreat(0.1,zonemin,Formation)
end
return false
end
return true
end
function ARMYGROUP:onafterRetreat(From,Event,To,Zone,Formation)
local uid=self:GetWaypointCurrentUID()
local Coordinate=Zone:GetRandomCoordinate()
self:T(self.lid..string.format("Retreating to zone %s",Zone:GetName()))
local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true)
wp.detour=0
self:CancelAllMissions()
end
function ARMYGROUP:onafterRetreated(From,Event,To)
local pos=self:GetCoordinate()
local wp=pos:WaypointGround(0)
self:Route({wp})
end
function ARMYGROUP:onbeforeEngageTarget(From,Event,To,Target,Speed,Formation)
local dt=nil
local allowed=true
local ammo=self:GetAmmoTot()
if ammo.Total==0 then
self:T(self.lid.."WARNING: Cannot engage TARGET because no ammo left!")
return false
end
local mission=self:GetMissionCurrent()
if mission and mission.type~=AUFTRAG.Type.GROUNDATTACK and mission.type~=AUFTRAG.Type.CAPTUREZONE then
self:T(self.lid.."Engage command but have current mission ==> Pausing mission!")
self:PauseMission()
dt=-0.1
allowed=false
end
if dt then
self:T(self.lid..string.format("Trying Engage again in %.2f sec",dt))
self:__EngageTarget(dt,Target)
allowed=false
end
return allowed
end
function ARMYGROUP:onafterEngageTarget(From,Event,To,Target,Speed,Formation)
self:T(self.lid.."Engaging Target")
if Target:IsInstanceOf("TARGET")then
self.engage.Target=Target
else
self.engage.Target=TARGET:New(Target)
end
self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate())
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.95)
self.engage.roe=self:GetROE()
self.engage.alarmstate=self:GetAlarmstate()
self:SwitchAlarmstate(ENUMS.AlarmState.Auto)
self:SwitchROE(ENUMS.ROE.OpenFire)
local uid=self:GetWaypointCurrentUID()
self.engage.Formation=Formation or ENUMS.Formation.Vehicle.Vee
self.engage.Speed=Speed
self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true)
self.engage.Waypoint.detour=1
end
function ARMYGROUP:_UpdateEngageTarget()
if self.engage.Target and self.engage.Target:IsAlive()then
local vec3=self.engage.Target:GetVec3()
if vec3 then
local dist=UTILS.VecDist3D(vec3,self.engage.Coordinate:GetVec3())
local los=self:HasLoS(vec3)
if dist>100 or los==false then
self.engage.Coordinate:UpdateFromVec3(vec3)
local uid=self:GetWaypointCurrentUID()
self:RemoveWaypointByID(self.engage.Waypoint.uid)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9)
self.engage.Waypoint=self:AddWaypoint(intercoord,self.engage.Speed,uid,self.engage.Formation,true)
self.engage.Waypoint.detour=0
end
else
self:T(self.lid.."Could not get position of target ==> Disengage!")
self:Disengage()
end
else
self:T(self.lid.."Target not ALIVE ==> Disengage!")
self:Disengage()
end
end
function ARMYGROUP:onafterDisengage(From,Event,To)
self:T(self.lid.."Disengage Target")
self:SwitchROE(self.engage.roe)
self:SwitchAlarmstate(self.engage.alarmstate)
local task=self:GetTaskCurrent()
if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then
self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!")
self:TaskDone(task)
end
if self.engage.Waypoint then
self:RemoveWaypointByID(self.engage.Waypoint.uid)
end
self:_CheckGroupDone(1)
end
function ARMYGROUP:onafterDetourReached(From,Event,To)
self:T(self.lid.."Group reached detour coordinate")
end
function ARMYGROUP:onafterFullStop(From,Event,To)
self:T(self.lid..string.format("Full stop!"))
local pos=self:GetCoordinate()
local wp=pos:WaypointGround(0)
self:Route({wp})
end
function ARMYGROUP:onafterCruise(From,Event,To,Speed,Formation)
self.Twaiting=nil
self.dTwait=nil
self:T(self.lid..string.format("Cruise ==> Update route in 0.01 sec (speed=%s, formation=%s)",tostring(Speed),tostring(Formation)))
self:__UpdateRoute(-0.01,nil,nil,Speed,Formation)
end
function ARMYGROUP:onafterHit(From,Event,To,Enemy)
self:T(self.lid..string.format("ArmyGroup hit by %s",Enemy and Enemy:GetName()or"unknown"))
if self.suppressionOn then
env.info(self.lid.."FF suppress")
self:_Suppress()
end
end
function ARMYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Formation,Updateroute)
self:T(self.lid..string.format("AddWaypoint Formation = %s",tostring(Formation)))
local coordinate=self:_CoordinateFromObject(Coordinate)
local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID)
Speed=Speed or self:GetSpeedCruise()
if not Formation then
if self.formationPerma then
Formation=self.formationPerma
elseif self.optionDefault.Formation then
Formation=self.optionDefault.Formation
elseif self.option.Formation then
Formation=self.option.Formation
else
Formation=ENUMS.Formation.Vehicle.OnRoad
end
self:T2(self.lid..string.format("Formation set to = %s",tostring(Formation)))
end
local wp=coordinate:WaypointGround(UTILS.KnotsToKmph(Speed),Formation)
local waypoint=self:_CreateWaypoint(wp)
self:_AddWaypoint(waypoint,wpnumber)
waypoint.roadcoord=coordinate:GetClosestPointToRoad(false)
if waypoint.roadcoord then
waypoint.roaddist=coordinate:Get2DDistance(waypoint.roadcoord)
else
waypoint.roaddist=1000*1000
end
self:T(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s",waypoint.uid,wpnumber,Speed,waypoint.roaddist,waypoint.action))
if Updateroute==nil or Updateroute==true then
self:__UpdateRoute(-0.01)
end
return waypoint
end
function ARMYGROUP:_InitGroup(Template,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,ARMYGROUP._InitGroup,self,Template,0)
else
if self.groupinitialized then
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
return
end
local template=Template or self:_GetTemplate()
self.isAI=true
self.isLateActivated=template.lateActivation
self.isUncontrolled=false
self.speedMax=self.group:GetSpeedMax()
if self.speedMax and self.speedMax>3.6 then
self.isMobile=true
else
self.isMobile=false
self.speedMax=0
end
self.speedCruise=self.speedMax*0.7
self.ammo=self:GetAmmoTot()
self.radio.On=false
self.radio.Freq=133
self.radio.Modu=radio.modulation.AM
self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On)
self.option.Formation=template.route.points[1].action
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
if 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()
local u=dcsgroup:getUnits()
if#units~=size0 then
self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units! u=%d",#units,size0,#u))
end
for _,unit in pairs(units)do
local unitname=unit:GetName()
self:_AddElementByName(unitname)
end
self.groupinitialized=true
end
return self
end
function ARMYGROUP:SwitchFormation(Formation,Permanently,NoRouteUpdate)
if self:IsAlive()or self:IsInUtero()then
Formation=Formation or(self.optionDefault.Formation or"Off road")
Permanently=Permanently or false
if Permanently then
self.formationPerma=Formation
else
self.formationPerma=nil
end
self.option.Formation=Formation or"Off road"
if self:IsInUtero()then
self:T(self.lid..string.format("Will switch formation to %s (permanently=%s) when group is spawned",tostring(self.option.Formation),tostring(Permanently)))
else
if NoRouteUpdate then
else
self:__UpdateRoute(-1,nil,nil,Formation)
end
self:T(self.lid..string.format("Switching formation to %s (permanently=%s)",tostring(self.option.Formation),tostring(Permanently)))
end
end
return self
end
function ARMYGROUP:FindNearestAmmoSupply(Radius)
Radius=UTILS.NMToMeters(Radius or 30)
local coord=self:GetCoordinate()
local myCoalition=self:GetCoalition()
local units=coord:ScanUnits(Radius)
local dmin=math.huge
local truck=nil
for _,_unit in pairs(units.Set)do
local unit=_unit
if unit:IsAlive()and unit:GetCoalition()==myCoalition and unit:IsAmmoSupply()and unit:GetVelocityKMH()<1 then
local d=coord:Get2DDistance(unit:GetCoord())
if d<dmin then
dmin=d
truck=unit
self:T(self.lid..string.format("Ammo truck %s [%s] at dist=%d meters",unit:GetName(),unit:GetTypeName(),d))
end
end
end
if truck then
return truck:GetGroup(),dmin
end
return nil,nil
end
function ARMYGROUP:_Suppress()
local Tnow=timer.getTime()
local currROE=self:GetROE()
local sigma=(self.TsuppressMax-self.TsuppressMin)/4
local Tsuppress=UTILS.RandomGaussian(self.TsuppressAve,sigma,self.TsuppressMin,self.TsuppressMax)
local renew=true
if not self.TsuppressionOver then
self.TsuppressionOver=Tnow+Tsuppress
self:SwitchROE(ENUMS.ROE.WeaponHold)
self.suppressionROE=currROE
else
if Tsuppress+Tnow>self.TsuppressionOver then
self.TsuppressionOver=Tnow+Tsuppress
else
renew=false
end
end
if renew then
self:__Unsuppressed(self.TsuppressionOver-Tnow)
end
self:T(self.lid..string.format("Suppressed for %d sec",Tsuppress))
end
function ARMYGROUP:onbeforeUnsuppressed(From,Event,To)
local Tnow=timer.getTime()
self:T(self.lid..string.format("onbeforeRecovered: Time now: %d  - Time over: %d",Tnow,self.TsuppressionOver))
if Tnow>=self.TsuppressionOver then
return true
else
return false
end
end
function ARMYGROUP:onafterUnsuppressed(From,Event,To)
local text=string.format("Group %s has recovered!",self:GetName())
MESSAGE:New(text,10):ToAll()
self:T(self.lid..text)
self:SwitchROE(self.suppressionROE)
if true then
self.group:FlareGreen()
end
end
AUFTRAG={
ClassName="AUFTRAG",
verbose=0,
lid=nil,
auftragsnummer=nil,
groupdata={},
legions={},
statusLegion={},
requestID={},
assets={},
NassetsLegMin={},
NassetsLegMax={},
missionFraction=0.5,
enrouteTasks={},
marker=nil,
markerOn=nil,
markerCoalition=nil,
conditionStart={},
conditionSuccess={},
conditionFailure={},
conditionPush={},
conditionSuccessSet=false,
conditionFailureSet=false,
}
_AUFTRAGSNR=0
AUFTRAG.Type={
ANTISHIP="Anti Ship",
AWACS="AWACS",
BAI="BAI",
BOMBING="Bombing",
BOMBRUNWAY="Bomb Runway",
BOMBCARPET="Carpet Bombing",
CAP="CAP",
CAS="CAS",
ESCORT="Escort",
FAC="FAC",
FACA="FAC-A",
FERRY="Ferry Flight",
GROUNDESCORT="Ground Escort",
INTERCEPT="Intercept",
ORBIT="Orbit",
GCICAP="Ground Controlled CAP",
RECON="Recon",
RECOVERYTANKER="Recovery Tanker",
RESCUEHELO="Rescue Helo",
SEAD="SEAD",
STRIKE="Strike",
TANKER="Tanker",
TROOPTRANSPORT="Troop Transport",
ARTY="Fire At Point",
PATROLZONE="Patrol Zone",
OPSTRANSPORT="Ops Transport",
AMMOSUPPLY="Ammo Supply",
FUELSUPPLY="Fuel Supply",
ALERT5="Alert5",
ONGUARD="On Guard",
ARMOREDGUARD="Armored Guard",
BARRAGE="Barrage",
ARMORATTACK="Armor Attack",
CASENHANCED="CAS Enhanced",
HOVER="Hover",
LANDATCOORDINATE="Land at Coordinate",
GROUNDATTACK="Ground Attack",
CARGOTRANSPORT="Cargo Transport",
RELOCATECOHORT="Relocate Cohort",
AIRDEFENSE="Air Defence",
EWR="Early Warning Radar",
REARMING="Rearming",
CAPTUREZONE="Capture Zone",
NOTHING="Nothing",
PATROLRACETRACK="Patrol Racetrack",
STRAFING="Strafing",
}
AUFTRAG.SpecialTask={
FORMATION="Formation",
PATROLZONE="PatrolZone",
RECON="ReconMission",
AMMOSUPPLY="Ammo Supply",
FUELSUPPLY="Fuel Supply",
ALERT5="Alert5",
ONGUARD="On Guard",
ARMOREDGUARD="ArmoredGuard",
BARRAGE="Barrage",
ARMORATTACK="AmorAttack",
HOVER="Hover",
GROUNDATTACK="Ground Attack",
FERRY="Ferry",
RELOCATECOHORT="Relocate Cohort",
AIRDEFENSE="Air Defense",
EWR="Early Warning Radar",
RECOVERYTANKER="Recovery Tanker",
REARMING="Rearming",
CAPTUREZONE="Capture Zone",
NOTHING="Nothing",
PATROLRACETRACK="Patrol Racetrack",
}
AUFTRAG.Status={
PLANNED="planned",
QUEUED="queued",
REQUESTED="requested",
SCHEDULED="scheduled",
STARTED="started",
EXECUTING="executing",
DONE="done",
CANCELLED="cancelled",
SUCCESS="success",
FAILED="failed",
}
AUFTRAG.GroupStatus={
SCHEDULED="scheduled",
STARTED="started",
EXECUTING="executing",
PAUSED="paused",
DONE="done",
CANCELLED="cancelled",
}
AUFTRAG.TargetType={
GROUP="Group",
UNIT="Unit",
STATIC="Static",
COORDINATE="Coordinate",
AIRBASE="Airbase",
SETGROUP="SetGroup",
SETUNIT="SetUnit",
}
AUFTRAG.Category={
ALL="All",
AIRCRAFT="Aircraft",
AIRPLANE="Airplane",
HELICOPTER="Helicopter",
GROUND="Ground",
NAVAL="Naval",
}
AUFTRAG.version="1.2.1"
function AUFTRAG:New(Type)
local self=BASE:Inherit(self,FSM:New())
_AUFTRAGSNR=_AUFTRAGSNR+1
self.type=Type
self.auftragsnummer=_AUFTRAGSNR
self:_SetLogID()
self.status=AUFTRAG.Status.PLANNED
self:SetName()
self:SetPriority()
self:SetTime()
self:SetRequiredAssets()
self.engageAsGroup=true
self.dTevaluate=5
self.repeated=0
self.repeatedSuccess=0
self.repeatedFailure=0
self.Nrepeat=0
self.NrepeatFailure=0
self.NrepeatSuccess=0
self.Ncasualties=0
self.Nkills=0
self.Nelements=0
self.Ngroups=0
self.Nassigned=nil
self.Ndead=0
self:SetStartState(self.status)
self:AddTransition("*","Planned",AUFTRAG.Status.PLANNED)
self:AddTransition(AUFTRAG.Status.PLANNED,"Queued",AUFTRAG.Status.QUEUED)
self:AddTransition(AUFTRAG.Status.QUEUED,"Requested",AUFTRAG.Status.REQUESTED)
self:AddTransition(AUFTRAG.Status.REQUESTED,"Scheduled",AUFTRAG.Status.SCHEDULED)
self:AddTransition(AUFTRAG.Status.PLANNED,"Scheduled",AUFTRAG.Status.SCHEDULED)
self:AddTransition(AUFTRAG.Status.SCHEDULED,"Started",AUFTRAG.Status.STARTED)
self:AddTransition(AUFTRAG.Status.STARTED,"Executing",AUFTRAG.Status.EXECUTING)
self:AddTransition("*","Done",AUFTRAG.Status.DONE)
self:AddTransition("*","Cancel",AUFTRAG.Status.CANCELLED)
self:AddTransition("*","Success",AUFTRAG.Status.SUCCESS)
self:AddTransition("*","Failed",AUFTRAG.Status.FAILED)
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","*")
self:AddTransition("*","Repeat",AUFTRAG.Status.PLANNED)
self:AddTransition("*","ElementDestroyed","*")
self:AddTransition("*","GroupDead","*")
self:AddTransition("*","AssetDead","*")
self:__Status(-1)
return self
end
function AUFTRAG:NewANTISHIP(Target,Altitude)
local mission=AUFTRAG:New(AUFTRAG.Type.ANTISHIP)
mission:_TargetFromObject(Target)
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000)
mission.missionTask=ENUMS.MissionTask.ANTISHIPSTRIKE
mission.missionAltitude=mission.engageAltitude
mission.missionFraction=0.4
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewHOVER(Coordinate,Altitude,Time,Speed,MissionAlt)
local mission=AUFTRAG:New(AUFTRAG.Type.HOVER)
if Altitude then
mission.hoverAltitude=Coordinate:GetLandHeight()+UTILS.FeetToMeters(Altitude)
else
mission.hoverAltitude=Coordinate:GetLandHeight()+UTILS.FeetToMeters(50)
end
mission:_TargetFromObject(Coordinate)
mission.hoverSpeed=0.1
mission.hoverTime=Time or 300
self:SetMissionSpeed(Speed or 150)
self:SetMissionAltitude(MissionAlt or 1000)
mission.missionFraction=0.9
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.HELICOPTER}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewLANDATCOORDINATE(Coordinate,OuterRadius,InnerRadius,Time,Speed,MissionAlt,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
mission.stayAt=Coordinate:GetRandomCoordinateInRadius(UTILS.FeetToMeters(OuterRadius),UTILS.FeetToMeters(InnerRadius or 0))
end
mission.missionFraction=0.9
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.HELICOPTER}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewPATROL_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg,Formation)
local mission=AUFTRAG:New(AUFTRAG.Type.PATROLRACETRACK)
mission:_TargetFromObject(Coordinate)
if Altitude then
mission.TrackAltitude=UTILS.FeetToMeters(Altitude)
else
mission.TrackAltitude=UTILS.FeetToMeters(20000)
end
mission.TrackPoint1=Coordinate
local leg=UTILS.NMToMeters(Leg)or UTILS.NMToMeters(10)
local heading=Heading or 90
if heading<0 or heading>360 then heading=90 end
mission.TrackPoint2=Coordinate:Translate(leg,heading,true)
mission.TrackSpeed=UTILS.IasToTas(UTILS.KnotsToKmph(Speed or 300),mission.TrackAltitude)
mission.missionSpeed=UTILS.KnotsToKmph(Speed or 300)
mission.missionAltitude=mission.TrackAltitude*0.9
mission.missionTask=ENUMS.MissionTask.CAP
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg)
local mission=AUFTRAG:New(AUFTRAG.Type.ORBIT)
mission:_TargetFromObject(Coordinate)
if Altitude then
mission.orbitAltitude=UTILS.FeetToMeters(Altitude)
else
mission.orbitAltitude=Coordinate.y
end
mission.orbitSpeed=UTILS.IasToTas(UTILS.KnotsToMps(Speed or 350),mission.orbitAltitude)
mission.missionSpeed=UTILS.KnotsToKmph(Speed or 350)
if Leg then
mission.orbitLeg=UTILS.NMToMeters(Leg)
if Heading and Heading<0 then
mission.orbitHeadingRel=true
Heading=-Heading
end
mission.orbitHeading=Heading
end
mission.missionAltitude=mission.orbitAltitude*0.9
mission.missionFraction=0.9
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed)
local mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed)
return mission
end
function AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg)
Heading=Heading or math.random(360)
Leg=Leg or 10
local mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg)
return mission
end
function AUFTRAG:NewORBIT_GROUP(Group,Altitude,Speed,Leg,Heading,OffsetVec2,Distance)
Altitude=Altitude or 6000
local mission=AUFTRAG:NewORBIT(Group,Altitude,Speed,Heading,Leg)
mission.updateDCSTask=true
if OffsetVec2 then
if OffsetVec2.x then
OffsetVec2.x=UTILS.NMToMeters(OffsetVec2.x)
end
if OffsetVec2.y then
OffsetVec2.y=UTILS.NMToMeters(OffsetVec2.y)
end
if OffsetVec2.r then
OffsetVec2.r=UTILS.NMToMeters(OffsetVec2.r)
end
end
mission.orbitOffsetVec2=OffsetVec2
mission.orbitDeltaR=UTILS.NMToMeters(Distance or 5)
mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewGCICAP(Coordinate,Altitude,Speed,Heading,Leg)
local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg)
mission.type=AUFTRAG.Type.GCICAP
mission:_SetLogID()
mission.missionTask=ENUMS.MissionTask.INTERCEPT
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
return mission
end
function AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem)
local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg)
mission.type=AUFTRAG.Type.TANKER
mission:_SetLogID()
mission.refuelSystem=RefuelSystem
mission.missionTask=ENUMS.MissionTask.REFUELING
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg)
local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg)
mission.type=AUFTRAG.Type.AWACS
mission:_SetLogID()
mission.missionTask=ENUMS.MissionTask.AWACS
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewINTERCEPT(Target)
local mission=AUFTRAG:New(AUFTRAG.Type.INTERCEPT)
mission:_TargetFromObject(Target)
mission.missionTask=ENUMS.MissionTask.INTERCEPT
mission.missionFraction=0.1
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewCAP(ZoneCAP,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes)
TargetTypes=UTILS.EnsureTable(TargetTypes,true)
Altitude=Altitude or 10000
local mission=AUFTRAG:NewORBIT(Coordinate or ZoneCAP:GetCoordinate(),Altitude,Speed or 350,Heading,Leg)
mission.type=AUFTRAG.Type.CAP
mission:_SetLogID()
mission.engageZone=ZoneCAP
mission.engageTargetTypes=TargetTypes or{"Air"}
mission.missionTask=ENUMS.MissionTask.CAP
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.missionSpeed=UTILS.KnotsToKmph(UTILS.KnotsToAltKIAS(Speed or 350,Altitude))
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewCAPGROUP(Grp,Altitude,Speed,RelHeading,Leg,OffsetDist,OffsetAngle,UpdateDistance,TargetTypes,EngageRange)
TargetTypes=UTILS.EnsureTable(TargetTypes,true)
local OffsetVec2={r=OffsetDist or 6,phi=OffsetAngle or 180}
Leg=Leg or 14
local Heading=nil
if RelHeading then
Heading=-math.abs(RelHeading)
end
local mission=AUFTRAG:NewORBIT_GROUP(Grp,Altitude,Speed,Leg,Heading,OffsetVec2,UpdateDistance)
mission.type=AUFTRAG.Type.CAP
mission:_SetLogID()
local engage=EngageRange or 32
local zoneCAPGroup=ZONE_GROUP:New("CAPGroup",Grp,UTILS.NMToMeters(engage))
mission.engageZone=zoneCAPGroup
mission.engageTargetTypes=TargetTypes or{"Air"}
mission.missionTask=ENUMS.MissionTask.CAP
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewCAS(ZoneCAS,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes)
TargetTypes=UTILS.EnsureTable(TargetTypes,true)
local mission=AUFTRAG:NewORBIT(Coordinate or ZoneCAS:GetCoordinate(),Altitude or 10000,Speed,Heading,Leg)
mission.type=AUFTRAG.Type.CAS
mission:_SetLogID()
mission.engageZone=ZoneCAS
mission.engageTargetTypes=TargetTypes or{"Helicopters","Ground Units","Light armed ships"}
mission.missionTask=ENUMS.MissionTask.CAS
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewCASENHANCED(CasZone,Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes)
local mission=AUFTRAG:New(AUFTRAG.Type.CASENHANCED)
if type(CasZone)=="string"then
CasZone=ZONE:New(CasZone)
end
mission:_TargetFromObject(CasZone)
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CASENHANCED)
mission:SetEngageDetected(RangeMax,TargetTypes or{"Helicopters","Ground Units","Light armed ships"},CasZone,NoEngageZoneSet)
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.missionFraction=0.5
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil
mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil
mission.dTevaluate=15
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewFAC(FacZone,Speed,Altitude,Frequency,Modulation)
local mission=AUFTRAG:New(AUFTRAG.Type.FAC)
if type(FacZone)=="string"then
FacZone=ZONE:FindByName(FacZone)
end
mission:_TargetFromObject(FacZone)
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.FAC)
mission.facFreq=Frequency or 133
mission.facModu=Modulation or radio.modulation.AM
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil
mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil
mission.categories={AUFTRAG.Category.AIRCRAFT,AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation)
local mission=AUFTRAG:New(AUFTRAG.Type.FACA)
mission:_TargetFromObject(Target)
mission.facDesignation=Designation
mission.facDatalink=true
mission.facFreq=Frequency or 133
mission.facModu=Modulation or radio.modulation.AM
mission.missionTask=ENUMS.MissionTask.AFAC
mission.missionAltitude=nil
mission.missionFraction=0.5
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewBAI(Target,Altitude)
local mission=AUFTRAG:New(AUFTRAG.Type.BAI)
mission:_TargetFromObject(Target)
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 5000)
mission.missionTask=ENUMS.MissionTask.GROUNDATTACK
mission.missionAltitude=mission.engageAltitude
mission.missionFraction=0.75
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewSEAD(Target,Altitude)
local mission=AUFTRAG:New(AUFTRAG.Type.SEAD)
mission:_TargetFromObject(Target)
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
mission.missionTask=ENUMS.MissionTask.SEAD
mission.missionAltitude=mission.engageAltitude
mission.missionFraction=0.2
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewSTRIKE(Target,Altitude,EngageWeaponType)
local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE)
mission:_TargetFromObject(Target)
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
mission.missionAltitude=mission.engageAltitude
mission.missionFraction=0.75
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewBOMBING(Target,Altitude,EngageWeaponType)
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING)
mission:_TargetFromObject(Target)
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
mission.missionAltitude=mission.engageAltitude*0.8
mission.missionFraction=0.5
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.NoReaction
mission.dTevaluate=5*60
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG: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()
return mission
end
function AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude)
if type(Airdrome)=="string"then
Airdrome=AIRBASE:FindByName(Airdrome)
end
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBRUNWAY)
mission:_TargetFromObject(Airdrome)
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
mission.missionTask=ENUMS.MissionTask.RUNWAYATTACK
mission.missionAltitude=mission.engageAltitude*0.8
mission.missionFraction=0.75
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.dTevaluate=5*60
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewBOMBCARPET(Target,Altitude,CarpetLength)
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBCARPET)
mission:_TargetFromObject(Target)
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
mission.engageLength=CarpetLength or 500
mission.engageAsGroup=false
mission.engageDirection=nil
mission.missionTask=ENUMS.MissionTask.GROUNDATTACK
mission.missionAltitude=mission.engageAltitude*0.8
mission.missionFraction=0.5
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.NoReaction
mission.dTevaluate=5*60
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewGROUNDESCORT(EscortGroup,OrbitDistance,TargetTypes)
local mission=AUFTRAG:New(AUFTRAG.Type.GROUNDESCORT)
if type(EscortGroup)=="string"then
mission.escortGroupName=EscortGroup
mission:_TargetFromObject()
else
mission:_TargetFromObject(EscortGroup)
end
mission.orbitDistance=OrbitDistance and UTILS.NMToMeters(OrbitDistance)or UTILS.NMToMeters(1.5)
mission.engageTargetTypes=TargetTypes or{"Ground vehicles"}
mission.missionTask=ENUMS.MissionTask.GROUNDESCORT
mission.missionFraction=0.1
mission.missionAltitude=100
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.EvadeFire
mission.categories={AUFTRAG.Category.HELICOPTER}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewESCORT(EscortGroup,OffsetVector,EngageMaxDistance,TargetTypes)
local mission=AUFTRAG:New(AUFTRAG.Type.ESCORT)
if type(EscortGroup)=="string"then
mission.escortGroupName=EscortGroup
mission:_TargetFromObject()
else
mission:_TargetFromObject(EscortGroup)
end
mission.escortVec3=OffsetVector or{x=-100,y=0,z=200}
mission.engageMaxDistance=EngageMaxDistance and UTILS.NMToMeters(EngageMaxDistance)or UTILS.NMToMeters(32)
mission.engageTargetTypes=TargetTypes or{"Air"}
mission.missionTask=ENUMS.MissionTask.ESCORT
mission.missionFraction=0.1
mission.missionAltitude=1000
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewRESCUEHELO(Carrier)
local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO)
mission:_TargetFromObject(Carrier)
mission.missionTask=ENUMS.MissionTask.NOTHING
mission.missionFraction=0.5
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionROT=ENUMS.ROT.NoReaction
mission.categories={AUFTRAG.Category.HELICOPTER}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewRECOVERYTANKER(Carrier,Altitude,Speed,Leg,RelHeading,OffsetDist,OffsetAngle,UpdateDistance)
local OffsetVec2={r=OffsetDist or 6,phi=OffsetAngle or 180}
Leg=Leg or 14
Speed=Speed or 250
local Heading=nil
if RelHeading then
Heading=-math.abs(RelHeading)
end
local mission=AUFTRAG:NewORBIT_GROUP(Carrier,Altitude,Speed,Leg,Heading,OffsetVec2,UpdateDistance)
mission.type=AUFTRAG.Type.RECOVERYTANKER
mission.missionTask=ENUMS.MissionTask.REFUELING
mission.missionFraction=0.9
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionROT=ENUMS.ROT.NoReaction
mission.categories={AUFTRAG.Category.AIRPLANE}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet,DropoffCoordinate,PickupCoordinate,PickupRadius)
local mission=AUFTRAG:New(AUFTRAG.Type.TROOPTRANSPORT)
if TransportGroupSet:IsInstanceOf("GROUP")then
mission.transportGroupSet=SET_GROUP:New()
mission.transportGroupSet:AddGroup(TransportGroupSet)
elseif TransportGroupSet:IsInstanceOf("SET_GROUP")then
mission.transportGroupSet=TransportGroupSet
else
mission:E(mission.lid.."ERROR: TransportGroupSet must be a GROUP or SET_GROUP object!")
return nil
end
mission:_TargetFromObject(mission.transportGroupSet)
mission.transportPickup=PickupCoordinate or mission:GetTargetCoordinate()
mission.transportDropoff=DropoffCoordinate
mission.transportPickupRadius=PickupRadius or 100
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.TROOPTRANSPORT)
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.HELICOPTER,AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewCARGOTRANSPORT(StaticCargo,DropZone)
local mission=AUFTRAG:New(AUFTRAG.Type.CARGOTRANSPORT)
mission:_TargetFromObject(StaticCargo)
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CARGOTRANSPORT)
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.categories={AUFTRAG.Category.HELICOPTER}
mission.DCStask=mission:GetDCSMissionTask()
mission.DCStask.params.groupId=StaticCargo:GetID()
mission.DCStask.params.zoneId=DropZone.ZoneID
mission.DCStask.params.zone=DropZone
mission.DCStask.params.cargo=StaticCargo
return mission
end
function AUFTRAG:NewARTY(Target,Nshots,Radius,Altitude)
local mission=AUFTRAG:New(AUFTRAG.Type.ARTY)
mission:_TargetFromObject(Target)
mission.artyShots=Nshots or nil
mission.artyRadius=Radius or 100
mission.artyAltitude=Altitude
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=0
mission.missionFraction=0.0
mission.missionWaypointRadius=0.0
mission.dTevaluate=8*60
mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewBARRAGE(Zone,Heading,Angle,Radius,Altitude,Nshots)
local mission=AUFTRAG:New(AUFTRAG.Type.BARRAGE)
mission:_TargetFromObject(Zone)
mission.artyShots=Nshots
mission.artyRadius=Radius or 100
mission.artyAltitude=Altitude
mission.artyHeading=Heading
mission.artyAngle=Angle
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=0
mission.missionFraction=0.0
mission.dTevaluate=10
mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewPATROLZONE(Zone,Speed,Altitude,Formation)
local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE)
if type(Zone)=="string"then
Zone=ZONE:New(Zone)
end
mission:_TargetFromObject(Zone)
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.PATROLZONE)
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil
mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil
mission.categories={AUFTRAG.Category.ALL}
mission.DCStask=mission:GetDCSMissionTask()
mission.DCStask.params.formation=Formation or"Off Road"
return mission
end
function AUFTRAG:NewCAPTUREZONE(OpsZone,Coalition,Speed,Altitude,Formation)
local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE)
mission:_TargetFromObject(OpsZone)
mission.coalition=Coalition
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZONE)
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=0.1
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil
mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or nil
mission.categories={AUFTRAG.Category.ALL}
mission.DCStask=mission:GetDCSMissionTask()
mission.updateDCSTask=true
local params={}
params.formation=Formation or"Off Road"
params.zone=mission:GetObjective()
params.altitude=mission.missionAltitude
params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil
mission.DCStask.params=params
return mission
end
function AUFTRAG:NewARMORATTACK(Target,Speed,Formation)
local mission=AUFTRAG:NewGROUNDATTACK(Target,Speed,Formation)
mission.type=AUFTRAG.Type.ARMORATTACK
return mission
end
function AUFTRAG:NewGROUNDATTACK(Target,Speed,Formation)
local mission=AUFTRAG:New(AUFTRAG.Type.GROUNDATTACK)
mission:_TargetFromObject(Target)
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.GROUNDATTACK)
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.optionFormation="On Road"
mission.missionFraction=0.70
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil
mission.categories={AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed)or nil
mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee
return mission
end
function AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation)
local mission=AUFTRAG:New(AUFTRAG.Type.RECON)
mission:_TargetFromObject(ZoneSet)
if ZoneSet:IsInstanceOf("SET_ZONE")then
mission.missionZoneSet=ZoneSet
elseif ZoneSet:IsInstanceOf("ZONE_BASE")then
mission.missionZoneSet=SET_ZONE:New()
mission.missionZoneSet:AddZone(ZoneSet)
end
mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.RECON)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionROT=ENUMS.ROT.PassiveDefense
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=0.5
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil
mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude)or UTILS.FeetToMeters(2000)
mission.categories={AUFTRAG.Category.ALL}
mission.DCStask=mission:GetDCSMissionTask()
mission.DCStask.params.adinfinitum=Adinfinitum
mission.DCStask.params.randomly=Randomly
mission.DCStask.params.formation=Formation
return mission
end
function AUFTRAG:NewAMMOSUPPLY(Zone)
local mission=AUFTRAG:New(AUFTRAG.Type.AMMOSUPPLY)
mission:_TargetFromObject(Zone)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.missionWaypointRadius=0
mission.categories={AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewFUELSUPPLY(Zone)
local mission=AUFTRAG:New(AUFTRAG.Type.FUELSUPPLY)
mission:_TargetFromObject(Zone)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.categories={AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewREARMING(Zone)
local mission=AUFTRAG:New(AUFTRAG.Type.REARMING)
mission:_TargetFromObject(Zone)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.missionWaypointRadius=0
mission.categories={AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewALERT5(MissionType)
local mission=AUFTRAG:New(AUFTRAG.Type.ALERT5)
mission.missionTask=self:GetMissionTaskforMissionType(MissionType)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionROT=ENUMS.ROT.NoReaction
mission.alert5MissionType=MissionType
mission.missionFraction=1.0
mission.categories={AUFTRAG.Category.AIRCRAFT}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewONGUARD(Coordinate)
local mission=AUFTRAG:New(AUFTRAG.Type.ONGUARD)
mission:_TargetFromObject(Coordinate)
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewAIRDEFENSE(Zone)
local mission=AUFTRAG:New(AUFTRAG.Type.AIRDEFENSE)
mission:_TargetFromObject(Zone)
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewEWR(Zone)
local mission=AUFTRAG:New(AUFTRAG.Type.EWR)
mission:_TargetFromObject(Zone)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.categories={AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort)
local mission=AUFTRAG:New(AUFTRAG.Type.RELOCATECOHORT)
mission:_TargetFromObject(Legion.spawnzone)
mission.optionROE=ENUMS.ROE.ReturnFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=0.0
mission.categories={AUFTRAG.Category.ALL}
mission.DCStask=mission:GetDCSMissionTask()
if Cohort.isGround then
mission.optionFormation=ENUMS.Formation.Vehicle.OnRoad
end
mission.DCStask.params.legion=Legion
mission.DCStask.params.cohort=Cohort
return mission
end
function AUFTRAG:NewNOTHING(RelaxZone)
local mission=AUFTRAG:New(AUFTRAG.Type.NOTHING)
mission:_TargetFromObject(RelaxZone)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.categories={AUFTRAG.Category.GROUND,AUFTRAG.Category.NAVAL}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewARMOREDGUARD(Coordinate,Formation)
local mission=AUFTRAG:New(AUFTRAG.Type.ARMOREDGUARD)
mission:_TargetFromObject(Coordinate)
mission.optionROE=ENUMS.ROE.OpenFire
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.optionFormation=Formation or"On Road"
mission.missionFraction=1.0
mission.categories={AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
function AUFTRAG:NewFromTarget(Target,MissionType)
local mission=nil
if MissionType==AUFTRAG.Type.ANTISHIP then
mission=self:NewANTISHIP(Target,Altitude)
elseif MissionType==AUFTRAG.Type.ARTY then
mission=self:NewARTY(Target,Nshots,Radius)
elseif MissionType==AUFTRAG.Type.BAI then
mission=self:NewBAI(Target,Altitude)
elseif MissionType==AUFTRAG.Type.BOMBCARPET then
mission=self:NewBOMBCARPET(Target,Altitude,CarpetLength)
elseif MissionType==AUFTRAG.Type.BOMBING then
mission=self:NewBOMBING(Target,Altitude)
elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then
mission=self:NewBOMBRUNWAY(Target,Altitude)
elseif MissionType==AUFTRAG.Type.STRAFING then
mission=self:NewSTRAFING(Target,Altitude)
elseif MissionType==AUFTRAG.Type.CAS then
mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,Target:GetAverageCoordinate(),Heading,Leg,TargetTypes)
elseif MissionType==AUFTRAG.Type.CASENHANCED then
mission=self:NewCASENHANCED(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000),Altitude,Speed,RangeMax,NoEngageZoneSet,TargetTypes)
elseif MissionType==AUFTRAG.Type.INTERCEPT then
mission=self:NewINTERCEPT(Target)
elseif MissionType==AUFTRAG.Type.SEAD then
mission=self:NewSEAD(Target,Altitude)
elseif MissionType==AUFTRAG.Type.STRIKE then
mission=self:NewSTRIKE(Target,Altitude)
elseif MissionType==AUFTRAG.Type.ARMORATTACK then
mission=self:NewARMORATTACK(Target,Speed)
elseif MissionType==AUFTRAG.Type.GROUNDATTACK then
mission=self:NewGROUNDATTACK(Target,Speed,Formation)
else
return nil
end
return mission
end
function AUFTRAG:_DetermineAuftragType(Target)
local group=nil
local airbase=nil
local scenery=nil
local coordinate=nil
local auftrag=nil
if Target:IsInstanceOf("GROUP")then
group=Target
elseif Target:IsInstanceOf("UNIT")then
group=Target:GetGroup()
elseif Target:IsInstanceOf("AIRBASE")then
airbase=Target
elseif Target:IsInstanceOf("SCENERY")then
scenery=Target
end
if group then
local category=group:GetCategory()
local attribute=group:GetAttribute()
if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then
auftrag=AUFTRAG.Type.INTERCEPT
elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then
if attribute==GROUP.Attribute.GROUND_SAM then
auftrag=AUFTRAG.Type.SEAD
elseif attribute==GROUP.Attribute.GROUND_AAA then
auftrag=AUFTRAG.Type.BAI
elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then
auftrag=AUFTRAG.Type.BAI
elseif attribute==GROUP.Attribute.GROUND_INFANTRY then
auftrag=AUFTRAG.Type.CAS
elseif attribute==GROUP.Attribute.GROUND_TANK then
auftrag=AUFTRAG.Type.BAI
else
auftrag=AUFTRAG.Type.BAI
end
elseif category==Group.Category.SHIP then
auftrag=AUFTRAG.Type.ANTISHIP
else
self:T(self.lid.."ERROR: Unknown Group category!")
end
elseif airbase then
auftrag=AUFTRAG.Type.BOMBRUNWAY
elseif scenery then
auftrag=AUFTRAG.Type.STRIKE
elseif coordinate then
auftrag=AUFTRAG.Type.BOMBING
end
return auftrag
end
function AUFTRAG:NewAUTO(EngageGroup)
local mission=nil
local Target=EngageGroup
local auftrag=self:_DetermineAuftragType(EngageGroup)
if auftrag==AUFTRAG.Type.ANTISHIP then
mission=AUFTRAG:NewANTISHIP(Target)
elseif auftrag==AUFTRAG.Type.ARTY then
mission=AUFTRAG:NewARTY(Target)
elseif auftrag==AUFTRAG.Type.AWACS then
mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg)
elseif auftrag==AUFTRAG.Type.BAI then
mission=AUFTRAG:NewBAI(Target,Altitude)
elseif auftrag==AUFTRAG.Type.BOMBING then
mission=AUFTRAG:NewBOMBING(Target,Altitude)
elseif auftrag==AUFTRAG.Type.BOMBRUNWAY then
mission=AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude)
elseif auftrag==AUFTRAG.Type.BOMBCARPET then
mission=AUFTRAG:NewBOMBCARPET(Target,Altitude,CarpetLength)
elseif auftrag==AUFTRAG.Type.CAP then
mission=AUFTRAG:NewCAP(ZoneCAP,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes)
elseif auftrag==AUFTRAG.Type.CAS then
mission=AUFTRAG:NewCAS(ZoneCAS,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes)
elseif auftrag==AUFTRAG.Type.ESCORT then
mission=AUFTRAG:NewESCORT(EscortGroup,OffsetVector,EngageMaxDistance,TargetTypes)
elseif auftrag==AUFTRAG.Type.FACA then
mission=AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation)
elseif auftrag==AUFTRAG.Type.FERRY then
elseif auftrag==AUFTRAG.Type.GCICAP then
mission=AUFTRAG:NewGCICAP(Coordinate,Altitude,Speed,Heading,Leg)
elseif auftrag==AUFTRAG.Type.INTERCEPT then
mission=AUFTRAG:NewINTERCEPT(Target)
elseif auftrag==AUFTRAG.Type.ORBIT then
mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg)
elseif auftrag==AUFTRAG.Type.RECON then
mission=AUFTRAG:NewRECON(ZoneSet,Speed,Altitude,Adinfinitum,Randomly,Formation)
elseif auftrag==AUFTRAG.Type.RESCUEHELO then
mission=AUFTRAG:NewRESCUEHELO(Carrier)
elseif auftrag==AUFTRAG.Type.SEAD then
mission=AUFTRAG:NewSEAD(Target,Altitude)
elseif auftrag==AUFTRAG.Type.STRIKE then
mission=AUFTRAG:NewSTRIKE(Target,Altitude)
elseif auftrag==AUFTRAG.Type.TANKER then
mission=AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem)
elseif auftrag==AUFTRAG.Type.TROOPTRANSPORT then
mission=AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet,DropoffCoordinate,PickupCoordinate)
elseif auftrag==AUFTRAG.Type.PATROLRACETRACK then
mission=AUFTRAG:NewPATROL_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg,Formation)
else
end
if mission then
mission:SetPriority(10,true)
end
return mission
end
function AUFTRAG:SetTime(ClockStart,ClockStop)
local Tnow=timer.getAbsTime()
local Tstart=Tnow+5
if ClockStart and type(ClockStart)=="number"then
Tstart=Tnow+ClockStart
elseif ClockStart and type(ClockStart)=="string"then
Tstart=UTILS.ClockToSeconds(ClockStart)
end
local Tstop=nil
if ClockStop and type(ClockStop)=="number"then
Tstop=Tnow+ClockStop
elseif ClockStop and type(ClockStop)=="string"then
Tstop=UTILS.ClockToSeconds(ClockStop)
end
self.Tstart=Tstart
self.Tstop=Tstop
if Tstop then
self.duration=self.Tstop-self.Tstart
end
return self
end
function AUFTRAG:SetDuration(Duration)
self.durationExe=Duration
return self
end
function AUFTRAG:SetTeleport(Switch)
if Switch==nil then
Switch=true
end
self.teleport=Switch
return self
end
function AUFTRAG:SetReturnToLegion(Switch)
self.legionReturn=Switch
self:T(self.lid..string.format("Setting ReturnToLetion=%s",tostring(self.legionReturn)))
return self
end
function AUFTRAG:SetPushTime(ClockPush)
if ClockPush then
if type(ClockPush)=="string"then
self.Tpush=UTILS.ClockToSeconds(ClockPush)
elseif type(ClockPush)=="number"then
self.Tpush=timer.getAbsTime()+ClockPush
end
end
return self
end
function AUFTRAG:SetPriority(Prio,Urgent,Importance)
self.prio=Prio or 50
self.urgent=Urgent
self.importance=Importance
return self
end
function AUFTRAG:SetRepeat(Nrepeat)
self.Nrepeat=Nrepeat or 0
return self
end
function AUFTRAG:SetRepeatOnFailure(Nrepeat)
self.NrepeatFailure=Nrepeat or 0
return self
end
function AUFTRAG:SetRepeatOnSuccess(Nrepeat)
self.NrepeatSuccess=Nrepeat or 0
return self
end
function AUFTRAG:SetReinforce(Nreinforce)
self.reinforce=Nreinforce
return self
end
function AUFTRAG:SetRequiredAssets(NassetsMin,NassetsMax)
self.NassetsMin=NassetsMin or 1
self.NassetsMax=NassetsMax or self.NassetsMin
if self.NassetsMax<self.NassetsMin then
self.NassetsMax=self.NassetsMin
end
return self
end
function AUFTRAG:GetRequiredAssets()
local Nmin=self.NassetsMin
local Nmax=self.NassetsMax
if self.type==AUFTRAG.Type.RELOCATECOHORT then
local cohort=self.DCStask.params.cohort
Nmin=#cohort.assets
Nmax=Nmin
else
if self:IsExecuting()and self.reinforce and self.reinforce>0 then
local N=self:CountOpsGroups()
if N<Nmin then
Nmin=math.min(Nmin-N,self.reinforce)
Nmax=Nmin
self:T(self.lid..string.format("FF Executing Nmin=%d, N=%d, Nreinfoce=%d ==> Nmin=%d",self.NassetsMin,N,self.reinforce,Nmin))
end
end
end
return Nmin,Nmax
end
function AUFTRAG:SetAssetsStayAlive(Switch)
if Switch==nil then
Switch=true
end
self.assetStayAlive=Switch
return self
end
function AUFTRAG:SetRequiredEscorts(NescortMin,NescortMax,MissionType,TargetTypes,EngageRange)
self.NescortMin=NescortMin or 1
self.NescortMax=NescortMax or self.NescortMin
if self.NescortMax<self.NescortMin then
self.NescortMax=self.NescortMin
end
self.escortMissionType=MissionType
self.escortTargetTypes=TargetTypes
self.escortEngageRange=EngageRange or 32
self:T(self.lid..string.format("NescortMin=%s, NescortMax=%s",tostring(self.NescortMin),tostring(self.NescortMax)))
return self
end
function AUFTRAG:SetName(Name)
self.name=Name or string.format("Auftrag Nr. %d",self.auftragsnummer)
return self
end
function AUFTRAG:SetEnableMarkers(Coalition)
self.markerOn=true
self.markerCoaliton=Coalition or-1
return self
end
function AUFTRAG:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function AUFTRAG:SetWeaponType(WeaponType)
self.engageWeaponType=WeaponType or ENUMS.WeaponFlag.Auto
self.DCStask=self:GetDCSMissionTask()
return self
end
function AUFTRAG:SetWeaponExpend(WeaponExpend)
self.engageWeaponExpend=WeaponExpend or"Auto"
self.DCStask=self:GetDCSMissionTask()
return self
end
function AUFTRAG:SetEngageAsGroup(Switch)
if Switch==nil then
Switch=true
end
self.engageAsGroup=Switch
self.DCStask=self:GetDCSMissionTask()
return self
end
function AUFTRAG:SetEngageAltitude(Altitude)
self.engageAltitude=UTILS.FeetToMeters(Altitude or 6000)
self.DCStask=self:GetDCSMissionTask()
return self
end
function AUFTRAG:SetEngageDetected(RangeMax,TargetTypes,EngageZoneSet,NoEngageZoneSet)
if TargetTypes then
if type(TargetTypes)~="table"then
TargetTypes={TargetTypes}
end
else
TargetTypes={"All"}
end
if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE")then
local zoneset=SET_ZONE:New():AddZone(EngageZoneSet)
EngageZoneSet=zoneset
end
if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE")then
local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet)
NoEngageZoneSet=zoneset
end
self.engagedetectedOn=true
self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25)
self.engagedetectedTypes=TargetTypes
self.engagedetectedEngageZones=EngageZoneSet
self.engagedetectedNoEngageZones=NoEngageZoneSet
return self
end
function AUFTRAG:SetMissionAltitude(Altitude)
self.missionAltitude=UTILS.FeetToMeters(Altitude)
return self
end
function AUFTRAG:SetMissionSpeed(Speed)
self.missionSpeed=Speed and UTILS.KnotsToKmph(Speed)or nil
return self
end
function AUFTRAG:SetMissionRange(Range)
self.engageRange=UTILS.NMToMeters(Range or 100)
return self
end
function AUFTRAG:SetOpsTransport(OpsTransport)
self.opstransport=OpsTransport
return self
end
function AUFTRAG:GetOpsTransport()
return self.opstransport
end
function AUFTRAG:SetRequiredTransport(DeployZone,NcarriersMin,NcarriersMax,DisembarkZone,Categories,Attributes,Properties)
self.transportDeployZone=DeployZone
self.transportDisembarkZone=DisembarkZone
self:SetRequiredCarriers(NcarriersMin,NcarriersMax,Categories,Attributes,Properties)
return self
end
function AUFTRAG:AddTransportCarriers(Carriers)
if self.opstransport then
if Carriers:IsInstanceOf("SET_OPSGROUP")then
for _,_carrier in pairs(Carriers.Set)do
local carrier=_carrier
carrier:AddOpsTransport(self.opstransport)
end
elseif Carriers:IsInstanceOf("OPSGROUP")then
Carriers:AddOpsTransport(self.opstransport)
end
end
return self
end
function AUFTRAG:SetRequiredAttribute(Attributes)
self.attributes=UTILS.EnsureTable(Attributes,true)
return self
end
function AUFTRAG:SetRequiredProperty(Properties)
self.properties=UTILS.EnsureTable(Properties,true)
return self
end
function AUFTRAG:SetRequiredCarriers(NcarriersMin,NcarriersMax,Categories,Attributes,Properties)
self.NcarriersMin=NcarriersMin or 1
self.NcarriersMax=NcarriersMax or self.NcarriersMin
if self.NcarriersMax<self.NcarriersMin then
self.NcarriersMax=self.NcarriersMin
end
self.carrierCategories=UTILS.EnsureTable(Categories,true)
self.carrierAttributes=UTILS.EnsureTable(Attributes,true)
self.carrierProperties=UTILS.EnsureTable(Properties,true)
return self
end
function AUFTRAG:SetProhibitAfterburner()
self.prohibitAB=true
return self
end
function AUFTRAG:SetAllowAfterburner()
self.prohibitAB=false
return self
end
function AUFTRAG:SetProhibitAfterburnerExecutePhase()
self.prohibitABExecute=true
return self
end
function AUFTRAG:SetAllowAfterburnerExecutePhase()
self.prohibitABExecute=false
return self
end
function AUFTRAG:AssignCohort(Cohort)
self.specialCohorts=self.specialCohorts or{}
self:T3(self.lid..string.format("Assigning cohort %s",tostring(Cohort.name)))
table.insert(self.specialCohorts,Cohort)
return self
end
function AUFTRAG:AssignLegion(Legion)
self.specialLegions=self.specialLegions or{}
self:T3(self.lid..string.format("Assigning Legion %s",tostring(Legion.alias)))
table.insert(self.specialLegions,Legion)
return self
end
function AUFTRAG:AssignSquadrons(Squadrons)
for _,_squad in pairs(Squadrons)do
local squadron=_squad
self:T(self.lid..string.format("Assigning squadron %s",tostring(squadron.name)))
self:AssignCohort(squadron)
end
return self
end
function AUFTRAG:AssignTransportLegion(Legion)
self.transportLegions=self.transportLegions or{}
table.insert(self.transportLegions,Legion)
return self
end
function AUFTRAG:AssignTransportCohort(Cohort)
self.transportCohorts=self.transportCohorts or{}
table.insert(self.transportCohorts,Cohort)
return self
end
function AUFTRAG:AssignEscortLegion(Legion)
self.escortLegions=self.escortLegions or{}
table.insert(self.escortLegions,Legion)
return self
end
function AUFTRAG:AssignEscortCohort(Cohort)
self.escortCohorts=self.escortCohorts or{}
table.insert(self.escortCohorts,Cohort)
return self
end
function AUFTRAG:SetROE(roe)
self.optionROE=roe
return self
end
function AUFTRAG:SetROT(rot)
self.optionROT=rot
return self
end
function AUFTRAG:SetAlarmstate(Alarmstate)
self.optionAlarm=Alarmstate
return self
end
function AUFTRAG:SetEPLRS(OnOffSwitch)
if OnOffSwitch==nil then
self.optionEPLRS=true
else
self.optionEPLRS=OnOffSwitch
end
return self
end
function AUFTRAG:SetEmission(OnOffSwitch)
if OnOffSwitch==nil then
self.optionEmission=true
else
self.optionEmission=OnOffSwitch
end
return self
end
function AUFTRAG:SetInvisible(OnOffSwitch)
if OnOffSwitch==nil then
self.optionInvisible=true
else
self.optionInvisible=OnOffSwitch
end
return self
end
function AUFTRAG:SetImmortal(OnOffSwitch)
if OnOffSwitch==nil then
self.optionImmortal=true
else
self.optionImmortal=OnOffSwitch
end
return self
end
function AUFTRAG:SetFormation(Formation)
self.optionFormation=Formation
return self
end
function AUFTRAG:SetRadio(Frequency,Modulation)
self.radio={}
self.radio.Freq=Frequency
self.radio.Modu=Modulation
return self
end
function AUFTRAG:SetTACAN(Channel,Morse,UnitName,Band)
self.tacan={}
self.tacan.Channel=Channel
self.tacan.Morse=Morse or"XXX"
self.tacan.UnitName=UnitName
self.tacan.Band=Band
return self
end
function AUFTRAG:SetICLS(Channel,Morse,UnitName)
self.icls={}
self.icls.Channel=Channel
self.icls.Morse=Morse or"XXX"
self.icls.UnitName=UnitName
return self
end
function AUFTRAG:SetEvaluationTime(Teval)
self.dTevaluate=Teval or 60
return self
end
function AUFTRAG:GetType()
return self.type
end
function AUFTRAG:GetName()
return self.name
end
function AUFTRAG:GetNumberOfRequiredAssets()
return self.Nassets or self.NassetsMin
end
function AUFTRAG:GetPriority()
return self.prio
end
function AUFTRAG:GetCasualties()
return self.Ncasualties or 0
end
function AUFTRAG:GetKills()
return self.Nkills or 0
end
function AUFTRAG:IsUrgent()
return self.urgent
end
function AUFTRAG:GetImportance()
return self.importance
end
function AUFTRAG:AddConditionStart(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionStart,condition)
return self
end
function AUFTRAG:AddConditionSuccess(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionSuccess,condition)
self.conditionSuccessSet=true
return self
end
function AUFTRAG:AddConditionFailure(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionFailure,condition)
self.conditionFailureSet=true
return self
end
function AUFTRAG:AddConditionPush(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionPush,condition)
return self
end
function AUFTRAG:AddRequiredPayload(Payload)
self.payloads=self.payloads or{}
table.insert(self.payloads,Payload)
return self
end
function AUFTRAG:AddOpsGroup(OpsGroup)
self:T(self.lid..string.format("Adding Ops group %s",OpsGroup.groupname))
local groupdata={}
groupdata.opsgroup=OpsGroup
groupdata.status=AUFTRAG.GroupStatus.SCHEDULED
groupdata.waypointcoordinate=nil
groupdata.waypointindex=nil
groupdata.waypointtask=nil
self.groupdata[OpsGroup.groupname]=groupdata
if self.opstransport then
for _,_tzc in pairs(self.opstransport.tzCombos)do
local tzc=_tzc
if tzc.assetsCargo and tzc.assetsCargo[OpsGroup:GetName()]then
self.opstransport:AddCargoGroups(OpsGroup,tzc)
end
end
end
return self
end
function AUFTRAG:DelOpsGroup(OpsGroup)
self:T(self.lid..string.format("Removing OPS group %s",OpsGroup and OpsGroup.groupname or"nil (ERROR)!"))
if OpsGroup then
OpsGroup:RemoveMission(self)
self.groupdata[OpsGroup.groupname]=nil
end
return self
end
function AUFTRAG:IsPlanned()
return self.status==AUFTRAG.Status.PLANNED
end
function AUFTRAG:IsQueued(Legion)
local is=self.status==AUFTRAG.Status.QUEUED
if Legion then
is=self:GetLegionStatus(Legion)==AUFTRAG.Status.QUEUED
end
return is
end
function AUFTRAG:IsRequested(Legion)
local is=self.status==AUFTRAG.Status.REQUESTED
if Legion then
is=self:GetLegionStatus(Legion)==AUFTRAG.Status.REQUESTED
end
return is
end
function AUFTRAG:IsScheduled()
return self.status==AUFTRAG.Status.SCHEDULED
end
function AUFTRAG:IsStarted()
return self.status==AUFTRAG.Status.STARTED
end
function AUFTRAG:IsExecuting(AllGroups)
local isExecuting=self.status==AUFTRAG.Status.EXECUTING
if AllGroups and isExecuting then
local n=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.EXECUTING)
local N
if self.Nassigned then
N=self.Nassigned-self.Ndead
else
N=self:CountOpsGroups()
end
if n==N then
return true
else
return false
end
end
return isExecuting
end
function AUFTRAG:IsCancelled()
return self.status==AUFTRAG.Status.CANCELLED
end
function AUFTRAG:IsDone()
return self.status==AUFTRAG.Status.DONE
end
function AUFTRAG:IsSuccess()
return self.status==AUFTRAG.Status.SUCCESS
end
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:IsNotOver()
return not self:IsOver()
end
function AUFTRAG:IsAircraft()
for _,category in pairs(self.categories)do
if category==AUFTRAG.Category.AIRCRAFT or category==AUFTRAG.Category.AIRPLANE or category==AUFTRAG.Category.HELICOPTER then
return true
end
end
return false
end
function AUFTRAG:IsAirplane()
for _,category in pairs(self.categories)do
if category==AUFTRAG.Category.AIRPLANE then
return true
end
end
return false
end
function AUFTRAG:IsHelicopters()
for _,category in pairs(self.categories)do
if category==AUFTRAG.Category.HELICOPTER then
return true
end
end
return false
end
function AUFTRAG:IsGround()
for _,category in pairs(self.categories)do
if category==AUFTRAG.Category.GROUND then
return true
end
end
return false
end
function AUFTRAG:IsNaval()
for _,category in pairs(self.categories)do
if category==AUFTRAG.Category.NAVAL then
return true
end
end
return false
end
function AUFTRAG:IsReadyToGo()
local Tnow=timer.getAbsTime()
if self.Tstart and Tnow<self.Tstart then
return false
end
if self.Tstop and Tnow>self.Tstop then
return false
end
local startme=self:EvalConditionsAll(self.conditionStart)
if not startme then
return false
end
return true
end
function AUFTRAG:IsReadyToCancel()
local Tnow=timer.getAbsTime()
if self.Tstop and Tnow>=self.Tstop then
return true
end
local failure=self:EvalConditionsAny(self.conditionFailure)
if failure then
self.failurecondition=true
return true
end
local success=self:EvalConditionsAny(self.conditionSuccess)
if success then
self.successcondition=true
return true
end
return false
end
function AUFTRAG:IsReadyToPush()
local Tnow=timer.getAbsTime()
if self.Tpush and Tnow<=self.Tpush then
return false
end
local push=self:EvalConditionsAll(self.conditionPush)
return push
end
function AUFTRAG:EvalConditionsAll(Conditions)
for _,_condition in pairs(Conditions or{})do
local condition=_condition
local istrue=condition.func(unpack(condition.arg))
if not istrue then
return false
end
end
return true
end
function AUFTRAG:EvalConditionsAny(Conditions)
for _,_condition in pairs(Conditions or{})do
local condition=_condition
local istrue=condition.func(unpack(condition.arg))
if istrue then
return true
end
end
return false
end
function AUFTRAG:onafterStatus(From,Event,To)
local Tnow=timer.getAbsTime()
if self.escortGroupName then
local group=GROUP:FindByName(self.escortGroupName)
if group and group:IsAlive()then
self:T(self.lid..string.format("ESCORT group %s is now alive. Updating DCS task and adding group to TARGET",tostring(self.escortGroupName)))
self.engageTarget:AddObject(group)
self.DCStask=self:GetDCSMissionTask()
self.escortGroupName=nil
end
end
local Ntargets=self:CountMissionTargets()
local Ntargets0=self:GetTargetInitialNumber()
local Ngroups=self:CountOpsGroups()
local Nassigned=self.Nassigned and self.Nassigned-self.Ndead or 0
local conditionDone=false
if self.conditionFailureSet then
conditionDone=self:EvalConditionsAny(self.conditionFailure)
end
if self.conditionSuccessSet and not conditionDone then
conditionDone=self:EvalConditionsAny(self.conditionSuccess)
end
if self:IsNotOver()then
if self:CheckGroupsDone()then
self:Done()
elseif(self.Tstop and Tnow>self.Tstop+10)then
self:Cancel()
elseif conditionDone then
self:Cancel()
elseif self.durationExe and self.Texecuting and Tnow-self.Texecuting>self.durationExe then
local Nrepeat=self.Nrepeat
local NrepeatS=self.NrepeatSuccess
local NrepeatF=self.NrepeatFailure
self:Cancel()
self.Nrepeat=Nrepeat
self.NrepeatSuccess=NrepeatS
self.NrepeatFailure=NrepeatF
elseif(Ntargets0>0 and Ntargets==0)then
self:T(self.lid.."No targets left cancelling mission!")
self:Cancel()
elseif self:IsExecuting()and self:_IsNotReinforcing()then
if Ngroups==0 then
self:Done()
else
local done=true
for groupname,data in pairs(self.groupdata or{})do
local groupdata=data
local opsgroup=groupdata.opsgroup
if opsgroup:IsAlive()then
done=false
end
end
if done then
self:Done()
end
end
end
end
local fsmstate=self:GetState()
if fsmstate~=self.status then
self:T(self.lid..string.format("ERROR: FSM state %s != %s mission status!",fsmstate,self.status))
end
if self.verbose>=1 then
local Cstart=UTILS.SecondsToClock(self.Tstart,true)
local Cstop=self.Tstop and UTILS.SecondsToClock(self.Tstop,true)or"INF"
local targetname=self:GetTargetName()or"unknown"
local Nlegions=#self.legions
local commander=self.commander and self.statusCommander or"N/A"
local chief=self.chief and self.statusChief or"N/A"
self:T(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, legions=%d, commander=%s, chief=%s",
self.status,targetname,Cstart,Cstop,#self.assets,Ngroups,Ntargets,Nlegions,commander,chief))
end
if self.verbose>=2 then
local text="Group data:"
for groupname,_groupdata in pairs(self.groupdata)do
local groupdata=_groupdata
text=text..string.format("\n- %s: status mission=%s opsgroup=%s",groupname,groupdata.status,groupdata.opsgroup and groupdata.opsgroup:GetState()or"N/A")
end
self:I(self.lid..text)
end
if self.verbose>=3 then
local text=string.format("Assets [N=%d,Nassigned=%s, Ndead=%s]:",self.Nassets or 0,self.Nassigned or 0,self.Ndead or 0)
for i,_asset in pairs(self.assets or{})do
local asset=_asset
text=text..string.format("\n[%d] %s: spawned=%s, requested=%s, reserved=%s",i,asset.spawngroupname,tostring(asset.spawned),tostring(asset.requested),tostring(asset.reserved))
end
self:I(self.lid..text)
end
local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false
if self:IsOver()and ready2evaluate then
self:Evaluate()
else
self:__Status(-30)
end
if self.markerOn then
self:UpdateMarker()
end
end
function AUFTRAG:Evaluate()
local failed=false
local targetdamage=self:GetTargetDamage()
local owndamage=self.Ncasualties/self.Nelements*100
local Ntargets=self:CountMissionTargets()
local Ntargets0=self:GetTargetInitialNumber()
local Life=self:GetTargetLife()
local Life0=self:GetTargetInitialLife()
if Ntargets0>0 then
if self.type==AUFTRAG.Type.TROOPTRANSPORT or self.type==AUFTRAG.Type.ESCORT then
if Ntargets<Ntargets0 then
failed=true
end
elseif self.type==AUFTRAG.Type.CARGOTRANSPORT then
local zone=self.DCStask.params.zone
local cargo=self.DCStask.params.cargo
if cargo and zone then
failed=not cargo:IsInZone(zone)
else
failed=true
end
elseif self.type==AUFTRAG.Type.RESCUEHELO then
if self.Nelements==self.Ncasualties then
failed=true
end
else
if Ntargets>0 then
failed=true
end
end
else
if self.Nelements==self.Ncasualties then
failed=true
end
end
local successCondition=self:EvalConditionsAny(self.conditionSuccess)
local failureCondition=self:EvalConditionsAny(self.conditionFailure)
if failureCondition then
failed=true
elseif successCondition then
failed=false
end
if self.verbose>0 then
local text=string.format("Evaluating mission:\n")
text=text..string.format("Own casualties = %d/%d\n",self.Ncasualties,self.Nelements)
text=text..string.format("Own losses     = %.1f %%\n",owndamage)
text=text..string.format("Killed units   = %d\n",self.Nkills)
text=text..string.format("--------------------------\n")
text=text..string.format("Targets left   = %d/%d\n",Ntargets,Ntargets0)
text=text..string.format("Targets life   = %.1f/%.1f\n",Life,Life0)
text=text..string.format("Enemy losses   = %.1f %%\n",targetdamage)
text=text..string.format("--------------------------\n")
text=text..string.format("Success Cond   = %s\n",tostring(successCondition))
text=text..string.format("Failure Cond   = %s\n",tostring(failureCondition))
text=text..string.format("--------------------------\n")
text=text..string.format("Final Success  = %s\n",tostring(not failed))
text=text..string.format("=========================")
self:I(self.lid..text)
end
if failed then
self:I(self.lid..string.format("Mission %d [%s] failed!",self.auftragsnummer,self.type))
if self.chief then
self.chief.Nfailure=self.chief.Nfailure+1
end
self:Failed()
else
self:I(self.lid..string.format("Mission %d [%s] success!",self.auftragsnummer,self.type))
if self.chief then
self.chief.Nsuccess=self.chief.Nsuccess+1
end
self:Success()
end
return self
end
function AUFTRAG:GetOpsGroups()
local opsgroups={}
for _,_groupdata in pairs(self.groupdata or{})do
local groupdata=_groupdata
table.insert(opsgroups,groupdata.opsgroup)
end
return opsgroups
end
function AUFTRAG:GetAssetDataByName(AssetName)
return self.groupdata[tostring(AssetName)]
end
function AUFTRAG:GetGroupData(opsgroup)
if opsgroup and self.groupdata then
return self.groupdata[opsgroup.groupname]
end
return nil
end
function AUFTRAG:SetGroupStatus(opsgroup,status)
local oldstatus=self:GetGroupStatus(opsgroup)
self:T(self.lid..string.format("Setting OPSGROUP %s to status %s-->%s",opsgroup and opsgroup.groupname or"nil",tostring(oldstatus),tostring(status)))
if oldstatus==AUFTRAG.GroupStatus.CANCELLED and status==AUFTRAG.GroupStatus.DONE then
else
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
groupdata.status=status
else
self:T(self.lid.."WARNING: Could not SET flight data for flight group. Setting status to DONE")
end
end
local isNotOver=self:IsNotOver()
local groupsDone=self:CheckGroupsDone()
self:T2(self.lid..string.format("Setting OPSGROUP %s status to %s. IsNotOver=%s  CheckGroupsDone=%s",opsgroup.groupname,self:GetGroupStatus(opsgroup),tostring(self:IsNotOver()),tostring(groupsDone)))
if isNotOver and groupsDone then
self:T3(self.lid.."All assigned OPSGROUPs done ==> mission DONE!")
self:Done()
else
self:T3(self.lid.."Mission NOT DONE yet!")
end
return self
end
function AUFTRAG:GetGroupStatus(opsgroup)
self:T3(self.lid..string.format("Trying to get Flight status for flight group %s",opsgroup and opsgroup.groupname or"nil"))
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
return groupdata.status
else
self:T(self.lid..string.format("WARNING: Could not GET groupdata for opsgroup %s. Returning status DONE.",opsgroup and opsgroup.groupname or"nil"))
return AUFTRAG.GroupStatus.DONE
end
end
function AUFTRAG:AddLegion(Legion)
self:T(self.lid..string.format("Adding legion %s",Legion.alias))
table.insert(self.legions,Legion)
return self
end
function AUFTRAG:RemoveLegion(Legion)
for i=#self.legions,1,-1 do
local legion=self.legions[i]
if legion.alias==Legion.alias then
self:T(self.lid..string.format("Removing legion %s",Legion.alias))
table.remove(self.legions,i)
self.statusLegion[Legion.alias]=nil
return self
end
end
self:T(self.lid..string.format("ERROR: Legion %s not found and could not be removed!",Legion.alias))
return self
end
function AUFTRAG:SetLegionStatus(Legion,Status)
local status=self:GetLegionStatus(Legion)
self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s",Legion.alias,tostring(status),tostring(Status)))
self.statusLegion[Legion.alias]=Status
return self
end
function AUFTRAG:GetLegionStatus(Legion)
local status=self.statusLegion[Legion.alias]or"unknown"
return status
end
function AUFTRAG:SetGroupWaypointCoordinate(opsgroup,coordinate)
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
groupdata.waypointcoordinate=coordinate
end
return self
end
function AUFTRAG: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
return groupdata.waypointcoordinate
end
end
function AUFTRAG:SetGroupWaypointTask(opsgroup,task)
self:T2(self.lid..string.format("Setting waypoint task %s",task and task.description or"WTF"))
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
groupdata.waypointtask=task
end
end
function AUFTRAG:GetGroupWaypointTask(opsgroup)
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
return groupdata.waypointtask
end
end
function AUFTRAG:SetGroupWaypointIndex(opsgroup,waypointindex)
self:T2(self.lid..string.format("Setting Mission waypoint UID=%d",waypointindex))
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
groupdata.waypointindex=waypointindex
end
return self
end
function AUFTRAG:GetGroupWaypointIndex(opsgroup)
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
return groupdata.waypointindex
end
end
function AUFTRAG:SetGroupEgressWaypointUID(opsgroup,waypointindex)
self:T2(self.lid..string.format("Setting Egress waypoint UID=%d",waypointindex))
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
groupdata.waypointEgressUID=waypointindex
end
return self
end
function AUFTRAG:GetGroupEgressWaypointUID(opsgroup)
local groupdata=self:GetGroupData(opsgroup)
if groupdata then
return groupdata.waypointEgressUID
end
end
function AUFTRAG:CheckGroupsDone()
for groupname,data in pairs(self.groupdata)do
local groupdata=data
if groupdata then
if not(groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED)then
self:T2(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!",groupdata.opsgroup.groupname,groupdata.status:upper()))
return false
end
end
end
for _,_legion in pairs(self.legions)do
local legion=_legion
local status=self:GetLegionStatus(legion)
if not status==AUFTRAG.Status.CANCELLED then
self:T2(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!",legion.alias,status))
return false
end
end
if self.commander then
if not self.statusCommander==AUFTRAG.Status.CANCELLED then
self:T2(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!",self.statusCommander))
return false
end
end
if self.chief then
if not self.statusChief==AUFTRAG.Status.CANCELLED then
self:T2(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!",self.statusChief))
return false
end
end
if self:IsPlanned()or self:IsQueued()or self:IsRequested()then
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!",self.status,self:GetState()))
return false
end
if self:IsExecuting()and self:_IsReinforcing()then
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] and reinfoce=%d. Mission NOT DONE!",self.status,self:GetState(),self.reinforce))
return false
end
if self:IsStarted()and self:CountOpsGroups()==0 then
self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!",self.status,self:GetState()))
return true
end
return true
end
function AUFTRAG:OnEventUnitLost(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
for _,_groupdata in pairs(self.groupdata)do
local groupdata=_groupdata
if groupdata and groupdata.opsgroup and groupdata.opsgroup.groupname==EventData.IniGroupName then
self:T(self.lid..string.format("UNIT LOST event for opsgroup %s unit %s",groupdata.opsgroup.groupname,EventData.IniUnitName))
end
end
end
end
function AUFTRAG:onafterPlanned(From,Event,To)
self.status=AUFTRAG.Status.PLANNED
self:T(self.lid..string.format("New mission status=%s",self.status))
end
function AUFTRAG:onafterQueued(From,Event,To,Airwing)
self.status=AUFTRAG.Status.QUEUED
self:T(self.lid..string.format("New mission status=%s",self.status))
end
function AUFTRAG:onafterRequested(From,Event,To)
self.status=AUFTRAG.Status.REQUESTED
self:T(self.lid..string.format("New mission status=%s",self.status))
end
function AUFTRAG:onafterAssign(From,Event,To)
self.status=AUFTRAG.Status.ASSIGNED
self:T(self.lid..string.format("New mission status=%s",self.status))
end
function AUFTRAG:onafterScheduled(From,Event,To)
self.status=AUFTRAG.Status.SCHEDULED
self:T(self.lid..string.format("New mission status=%s",self.status))
end
function AUFTRAG:onafterStarted(From,Event,To)
self.status=AUFTRAG.Status.STARTED
self.Tstarted=timer.getAbsTime()
self:T(self.lid..string.format("New mission status=%s",self.status))
end
function AUFTRAG:onafterExecuting(From,Event,To)
self.status=AUFTRAG.Status.EXECUTING
self.Texecuting=timer.getAbsTime()
self:T(self.lid..string.format("New mission status=%s",self.status))
end
function AUFTRAG:onafterElementDestroyed(From,Event,To,OpsGroup,Element)
self.Ncasualties=self.Ncasualties+1
end
function AUFTRAG:onafterGroupDead(From,Event,To,OpsGroup)
local asset=self:GetAssetByName(OpsGroup.groupname)
if asset then
self:AssetDead(asset)
end
self.Ndead=self.Ndead+1
end
function AUFTRAG:onafterAssetDead(From,Event,To,Asset)
local N=self:CountOpsGroups()
local notreinforcing=self:_IsNotReinforcing()
self:T(self.lid..string.format("Asset %s dead! Number of ops groups remaining %d (reinforcing=%s)",tostring(Asset.spawngroupname),N,tostring(not notreinforcing)))
if N==0 and notreinforcing then
if self:IsNotOver()then
self:Cancel()
else
end
end
self:DelAsset(Asset)
end
function AUFTRAG:onafterCancel(From,Event,To)
local Ngroups=self:CountOpsGroups()
self:T(self.lid..string.format("CANCELLING mission in status %s. Will wait for %d groups to report mission DONE before evaluation",self.status,Ngroups))
self.Tover=timer.getAbsTime()
self.Nrepeat=self.repeated
self.NrepeatFailure=self.repeatedFailure
self.NrepeatSuccess=self.repeatedSuccess
self.dTevaluate=0
if self.chief then
self:T(self.lid..string.format("CHIEF will cancel the mission. Will wait for mission DONE before evaluation!"))
self.chief:MissionCancel(self)
elseif self.commander then
self:T(self.lid..string.format("COMMANDER will cancel the mission. Will wait for mission DONE before evaluation!"))
self.commander:MissionCancel(self)
elseif self.legions and#self.legions>0 then
for _,_legion in pairs(self.legions or{})do
local legion=_legion
self:T(self.lid..string.format("LEGION %s will cancel the mission. Will wait for mission DONE before evaluation!",legion.alias))
legion:MissionCancel(self)
end
else
self:T(self.lid..string.format("No legion, commander or chief. Attached groups will cancel the mission on their own. Will wait for mission DONE before evaluation!"))
for _,_groupdata in pairs(self.groupdata or{})do
local groupdata=_groupdata
groupdata.opsgroup:MissionCancel(self)
end
end
if self:IsPlanned()or self:IsQueued()or self:IsRequested()or Ngroups==0 then
self:T(self.lid..string.format("Cancelled mission was in %s stage with %d groups assigned and alive. Call it done!",self.status,Ngroups))
self:Done()
end
end
function AUFTRAG:onafterDone(From,Event,To)
self.status=AUFTRAG.Status.DONE
self:T(self.lid..string.format("New mission status=%s",self.status))
self.Tover=timer.getAbsTime()
self.Texecuting=nil
self.statusChief=AUFTRAG.Status.DONE
self.statusCommander=AUFTRAG.Status.DONE
for _,_legion in pairs(self.legions)do
local Legion=_legion
self:SetLegionStatus(Legion,AUFTRAG.Status.DONE)
if self.type==AUFTRAG.Type.RELOCATECOHORT then
local requestid=self.requestID[Legion.alias]
if requestid then
self:T(self.lid.."Removing request from pending queue")
Legion:_DeleteQueueItemByID(requestid,Legion.pending)
local Cohort=self.DCStask.params.cohort
Legion:DelCohort(Cohort)
else
self:E(self.lid.."WARNING: Could NOT remove relocation request from from pending queue (all assets were spawned?)")
end
end
end
if self.type==AUFTRAG.Type.RELOCATECOHORT then
local cohort=self.DCStask.params.cohort
cohort:Relocated()
end
end
function AUFTRAG:onafterSuccess(From,Event,To)
self.status=AUFTRAG.Status.SUCCESS
self:T(self.lid..string.format("New mission status=%s",self.status))
self.statusChief=self.status
self.statusCommander=self.status
for _,_legion in pairs(self.legions)do
local Legion=_legion
self:SetLegionStatus(Legion,self.status)
end
local repeatme=self.repeatedSuccess<self.NrepeatSuccess or self.repeated<self.Nrepeat
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()
else
self:T(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached  ==> Stopping mission!",self.repeated+1))
self:Stop()
end
end
function AUFTRAG:onafterFailed(From,Event,To)
self.status=AUFTRAG.Status.FAILED
self:T(self.lid..string.format("New mission status=%s",self.status))
self.statusChief=self.status
self.statusCommander=self.status
for _,_legion in pairs(self.legions)do
local Legion=_legion
self:SetLegionStatus(Legion,self.status)
end
local repeatme=self.repeatedFailure<self.NrepeatFailure or self.repeated<self.Nrepeat
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()
else
self:T(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1))
self:Stop()
end
end
function AUFTRAG:onbeforeRepeat(From,Event,To)
if not(self.chief or self.commander or#self.legions>0)then
self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG")
self:Stop()
return false
end
return true
end
function AUFTRAG:onafterRepeat(From,Event,To)
self.status=AUFTRAG.Status.PLANNED
self:T(self.lid..string.format("New mission status=%s (on Repeat)",self.status))
self.statusChief=self.status
self.statusCommander=self.status
for _,_legion in pairs(self.legions)do
local Legion=_legion
self:SetLegionStatus(Legion,self.status)
end
self.repeated=self.repeated+1
if self.chief then
self.statusChief=AUFTRAG.Status.PLANNED
if self.commander then
self.statusCommander=AUFTRAG.Status.PLANNED
end
for _,_legion in pairs(self.legions)do
local legion=_legion
legion:RemoveMission(self)
end
elseif self.commander then
self.statusCommander=AUFTRAG.Status.PLANNED
for _,_legion in pairs(self.legions)do
local legion=_legion
legion:RemoveMission(self)
self:SetLegionStatus(legion,AUFTRAG.Status.PLANNED)
end
elseif#self.legions>0 then
for _,_legion in pairs(self.legions)do
local legion=_legion
legion:RemoveMission(self)
self:SetLegionStatus(legion,AUFTRAG.Status.PLANNED)
legion:AddMission(self)
end
else
self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, COMMANDER or LEGION! Stopping AUFTRAG")
self:Stop()
return
end
self.assets={}
for groupname,_groupdata in pairs(self.groupdata)do
local groupdata=_groupdata
local opsgroup=groupdata.opsgroup
if opsgroup then
self:DelOpsGroup(opsgroup)
end
end
self.groupdata={}
self.Ncasualties=0
self.Nelements=0
self.Ngroups=0
self.Nassigned=nil
self.Ndead=0
self.DCStask=self:GetDCSMissionTask()
self:__Status(-30)
end
function AUFTRAG:onafterStop(From,Event,To)
self:T(self.lid..string.format("STOPPED mission in status=%s. Removing missions from queues. Stopping CallScheduler!",self.status))
if self.chief then
self.chief:RemoveMission(self)
end
if self.commander then
self.commander:RemoveMission(self)
end
if#self.legions>0 then
for _,_legion in pairs(self.legions)do
local legion=_legion
legion:RemoveMission(self)
end
end
for _,_groupdata in pairs(self.groupdata)do
local groupdata=_groupdata
groupdata.opsgroup:RemoveMission(self)
end
self.assets={}
self.groupdata={}
self.CallScheduler:Clear()
end
function AUFTRAG:_TargetFromObject(Object)
if not self.engageTarget then
if Object and Object:IsInstanceOf("TARGET")then
self.engageTarget=Object
else
self.engageTarget=TARGET:New(Object)
end
else
end
return self
end
function AUFTRAG:CountMissionTargets()
local N=0
local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition,true)or nil
if self.engageTarget then
N=self.engageTarget:CountTargets(Coalitions)
end
return N
end
function AUFTRAG:GetTargetInitialNumber()
local target=self:GetTargetData()
if target then
return target.N0
else
return 0
end
end
function AUFTRAG:GetTargetInitialLife()
local target=self:GetTargetData()
if target then
return target.life0
else
return 0
end
end
function AUFTRAG:GetTargetDamage()
local target=self:GetTargetData()
if target then
return target:GetDamage()
else
return 0
end
end
function AUFTRAG:GetTargetLife()
local target=self:GetTargetData()
if target then
return target:GetLife()
else
return 0
end
end
function AUFTRAG:GetTargetData()
return self.engageTarget
end
function AUFTRAG:GetObjective(RefCoordinate,Coalitions)
local objective=self:GetTargetData():GetObject(RefCoordinate,Coalitions)
return objective
end
function AUFTRAG:GetTargetType()
local target=self.engageTarget
if target then
local to=target:GetObjective()
if to then
return to.Type
else
return"Unknown"
end
else
return"Unknown"
end
end
function AUFTRAG:GetTargetVec2()
local coord=self:GetTargetCoordinate()
if coord then
local vec2=coord:GetVec2()
return vec2
end
return nil
end
function AUFTRAG:GetTargetCoordinate()
if self.transportPickup then
return self.transportPickup
elseif self.missionZoneSet and self.type==AUFTRAG.Type.RECON then
return self.missionZoneSet:GetAverageCoordinate()
elseif self.engageTarget then
local coord=self.engageTarget:GetCoordinate()
return coord
elseif self.type==AUFTRAG.Type.ALERT5 then
return nil
else
self:T(self.lid.."ERROR: Cannot get target coordinate!")
end
return nil
end
function AUFTRAG:GetTargetHeading()
if self.engageTarget then
local heading=self.engageTarget:GetHeading()
return heading
end
return nil
end
function AUFTRAG:GetTargetName()
if self.engageTarget then
local name=self.engageTarget:GetName()
return name
end
return"N/A"
end
function AUFTRAG:GetTargetDistance(FromCoord)
local TargetCoord=self:GetTargetCoordinate()
if TargetCoord and FromCoord then
return TargetCoord:Get2DDistance(FromCoord)
else
self:T(self.lid.."ERROR: TargetCoord or FromCoord does not exist in AUFTRAG:GetTargetDistance() function! Returning 0")
end
return 0
end
function AUFTRAG:AddAsset(Asset)
self:T(self.lid..string.format("Adding asset \"%s\" to mission",tostring(Asset.spawngroupname)))
self.assets=self.assets or{}
local asset=self:GetAssetByName(Asset.spawngroupname)
if not asset then
table.insert(self.assets,Asset)
self.Nassigned=self.Nassigned or 0
self.Nassigned=self.Nassigned+1
end
return self
end
function AUFTRAG:_AddAssets(Assets)
for _,asset in pairs(Assets)do
self:AddAsset(asset)
end
return self
end
function AUFTRAG:DelAsset(Asset)
for i,_asset in pairs(self.assets or{})do
local asset=_asset
if asset.uid==Asset.uid then
self:T(self.lid..string.format("Removing asset \"%s\" from mission",tostring(Asset.spawngroupname)))
table.remove(self.assets,i)
return self
end
end
return self
end
function AUFTRAG:GetAssetByName(Name)
for i,_asset in pairs(self.assets or{})do
local asset=_asset
if asset.spawngroupname==Name then
return asset
end
end
return nil
end
function AUFTRAG:CountOpsGroups()
local N=0
for _,_groupdata in pairs(self.groupdata)do
local groupdata=_groupdata
if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive()and not groupdata.opsgroup:IsDead()then
N=N+1
end
end
return N
end
function AUFTRAG:CountOpsGroupsInStatus(Status)
local N=0
for _,_groupdata in pairs(self.groupdata)do
local groupdata=_groupdata
if groupdata and groupdata.status==Status then
N=N+1
end
end
return N
end
function AUFTRAG:GetMissionTypesText(MissionTypes)
local text=""
for _,missiontype in pairs(MissionTypes)do
text=text..string.format("%s, ",missiontype)
end
return text
end
function AUFTRAG:SetMissionWaypointCoord(Coordinate)
if Coordinate:IsInstanceOf("ZONE_BASE")then
Coordinate=Coordinate:GetCoordinate()
end
self.missionWaypointCoord=Coordinate
return self
end
function AUFTRAG:SetMissionWaypointRandomization(Radius)
self.missionWaypointRadius=Radius
return self
end
function AUFTRAG:SetMissionEgressCoord(Coordinate,Altitude,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
if self.missionAltitude then
coord.y=self.missionAltitude
end
return coord
end
end
function AUFTRAG:GetMissionWaypointCoord(group,randomradius,surfacetypes)
if self.missionWaypointCoord then
local coord=self.missionWaypointCoord
if self.missionAltitude then
coord.y=self.missionAltitude
end
return coord
end
local 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
self:E(self.lid..string.format("ERROR: Cannot get coordinate of group %s (alive=%s)!",tostring(group:GetName()),tostring(group:IsAlive())))
end
local alt=waypointcoord.y
if randomradius then
waypointcoord=ZONE_RADIUS:New("Temp",waypointcoord:GetVec2(),randomradius):GetRandomCoordinate(nil,nil,surfacetypes):SetAltitude(alt,false)
end
if self.missionAltitude then
waypointcoord:SetAltitude(self.missionAltitude,true)
end
return waypointcoord
end
function AUFTRAG:_SetLogID()
self.lid=string.format("Auftrag #%d %s | ",self.auftragsnummer,tostring(self.type))
return self
end
function AUFTRAG:_GetRequestID(Legion)
local requestid=nil
local name=nil
if type(Legion)=="string"then
name=Legion
else
name=Legion.alias
end
if name then
requestid=self.requestID[name]
end
return nil
end
function AUFTRAG:_GetRequest(Legion)
local request=nil
local requestID=self:_GetRequestID(Legion)
if requestID then
request=Legion:GetRequestByID(requestID)
end
return request
end
function AUFTRAG:_SetRequestID(Legion,RequestID)
local requestid=nil
local name=nil
if type(Legion)=="string"then
name=Legion
else
name=Legion.alias
end
if name then
if self.requestID[name]then
self:I(self.lid..string.format("WARNING: Mission already has a request ID=%d!",self.requestID[name]))
end
self.requestID[name]=RequestID
end
return self
end
function AUFTRAG:_IsNotReinforcing()
local Nassigned=self.Nassigned and self.Nassigned-self.Ndead or 0
local notreinforcing=((not self.reinforce)or(self.reinforce==0 and Nassigned<=0))
return notreinforcing
end
function AUFTRAG:_IsReinforcing()
local reinforcing=not self:_IsNotReinforcing()
return reinforcing
end
function AUFTRAG:UpdateMarker()
local text=string.format("%s %s: %s",self.name,self.type:upper(),self.status:upper())
text=text..string.format("\n%s",self:GetTargetName())
text=text..string.format("\nTargets %d/%d, Life Points=%d/%d",self:CountMissionTargets(),self:GetTargetInitialNumber(),self:GetTargetLife(),self:GetTargetInitialLife())
text=text..string.format("\nOpsGroups %d/%d",self:CountOpsGroups(),self:GetNumberOfRequiredAssets())
if not self.marker then
local targetcoord=self:GetTargetCoordinate()
if targetcoord then
if self.markerCoaliton and self.markerCoaliton>=0 then
self.marker=MARKER:New(targetcoord,text):ReadOnly():ToCoalition(self.markerCoaliton)
else
self.marker=MARKER:New(targetcoord,text):ReadOnly():ToAll()
end
end
else
if self.marker:GetText()~=text then
self.marker:UpdateText(text)
end
end
return self
end
function AUFTRAG:GetDCSMissionTask()
local DCStasks={}
if self.type==AUFTRAG.Type.ANTISHIP then
local DCStask=CONTROLLABLE.EnRouteTaskAntiShip(nil)
table.insert(self.enrouteTasks,DCStask)
self:_GetDCSAttackTask(self.engageTarget,DCStasks)
elseif self.type==AUFTRAG.Type.AWACS then
local DCStask=CONTROLLABLE.EnRouteTaskAWACS(nil)
table.insert(self.enrouteTasks,DCStask)
elseif self.type==AUFTRAG.Type.BAI then
self:_GetDCSAttackTask(self.engageTarget,DCStasks)
elseif self.type==AUFTRAG.Type.BOMBING then
local DCStask=CONTROLLABLE.TaskBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,Divebomb)
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.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.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)
table.insert(self.enrouteTasks,DCStask)
elseif self.type==AUFTRAG.Type.CAS then
local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority)
table.insert(self.enrouteTasks,DCStask)
elseif self.type==AUFTRAG.Type.ESCORT then
local DCStask=CONTROLLABLE.TaskEscort(nil,self.engageTarget:GetObject(),self.escortVec3,nil,self.engageMaxDistance,self.engageTargetTypes)
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.GROUNDESCORT then
local DCSTask=CONTROLLABLE.TaskGroundEscort(nil,self.engageTarget:GetObject(),nil,self.orbitDistance,self.engageTargetTypes)
table.insert(DCStasks,DCSTask)
elseif self.type==AUFTRAG.Type.FACA then
local DCStask=CONTROLLABLE.TaskFAC_AttackGroup(nil,self.engageTarget:GetObject(),self.engageWeaponType,self.facDesignation,self.facDatalink,self.facFreq,self.facModu,CallsignName,CallsignNumber)
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.FAC then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.PATROLZONE
local param={}
param.zone=self:GetObjective()
param.altitude=self.missionAltitude
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil
DCStask.params=param
table.insert(DCStasks,DCStask)
local DCSenroute=CONTROLLABLE.EnRouteTaskFAC(self,self.facFreq,self.facModu)
table.insert(self.enrouteTasks,DCSenroute)
elseif self.type==AUFTRAG.Type.FERRY then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.FERRY
local param={}
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.RELOCATECOHORT then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.RELOCATECOHORT
local param={}
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.INTERCEPT then
self:_GetDCSAttackTask(self.engageTarget,DCStasks)
elseif self.type==AUFTRAG.Type.ORBIT then
elseif self.type==AUFTRAG.Type.GCICAP then
elseif self.type==AUFTRAG.Type.RECON then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.RECON
local param={}
param.target=self.engageTarget
param.altitude=self.missionAltitude
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil
param.lastindex=nil
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.SEAD then
self:_GetDCSAttackTask(self.engageTarget,DCStasks)
elseif self.type==AUFTRAG.Type.STRIKE then
local DCStask=CONTROLLABLE.TaskAttackMapObject(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType)
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then
local DCStask=CONTROLLABLE.EnRouteTaskTanker(nil)
table.insert(self.enrouteTasks,DCStask)
elseif self.type==AUFTRAG.Type.TROOPTRANSPORT then
local TaskEmbark=CONTROLLABLE.TaskEmbarking(TaskControllable,self.transportPickup,self.transportGroupSet,self.transportWaitForCargo)
local TaskDisEmbark=CONTROLLABLE.TaskDisembarking(TaskControllable,self.transportDropoff,self.transportGroupSet)
table.insert(DCStasks,TaskEmbark)
table.insert(DCStasks,TaskDisEmbark)
elseif self.type==AUFTRAG.Type.OPSTRANSPORT then
local DCStask={}
DCStask.id="OpsTransport"
local param={}
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.CARGOTRANSPORT then
local TaskCargoTransportation={
id="CargoTransportation",
params={}
}
table.insert(DCStasks,TaskCargoTransportation)
elseif self.type==AUFTRAG.Type.RESCUEHELO then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.FORMATION
local param={}
param.unitname=self:GetTargetName()
param.offsetX=200
param.offsetZ=240
param.altitude=70
param.dtFollow=1.0
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.ARTY then
if self.artyShots==1 or self.artyRadius<10 or true then
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,self:GetTargetVec2(),self.artyRadius,self.artyShots,self.engageWeaponType,self.artyAltitude)
table.insert(DCStasks,DCStask)
else
local Vec2=self:GetTargetVec2()
local zone=ZONE_RADIUS:New("temp",Vec2,self.artyRadius)
for i=1,self.artyShots do
local vec2=zone:GetRandomVec2()
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,vec2,0,1,self.engageWeaponType,self.artyAltitude)
table.insert(DCStasks,DCStask)
end
end
elseif self.type==AUFTRAG.Type.BARRAGE then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.BARRAGE
local param={}
param.zone=self:GetObjective()
param.altitude=self.artyAltitude
param.radius=self.artyRadius
param.heading=self.artyHeading
param.angle=self.artyAngle
param.shots=self.artyShots
param.weaponTypoe=self.engageWeaponType
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.PATROLZONE then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.PATROLZONE
local param={}
param.zone=self:GetObjective()
param.altitude=self.missionAltitude
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.CAPTUREZONE then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.CAPTUREZONE
local param={}
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.CASENHANCED then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.PATROLZONE
local param={}
param.zone=self:GetObjective()
param.altitude=self.missionAltitude
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.GROUNDATTACK then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.GROUNDATTACK
local param={}
param.target=self:GetTargetData()
param.action="Wedge"
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed)or nil
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.AMMOSUPPLY then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.AMMOSUPPLY
local param={}
param.zone=self:GetObjective()
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.FUELSUPPLY then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.FUELSUPPLY
local param={}
param.zone=self:GetObjective()
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.REARMING then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.REARMING
local param={}
param.zone=self:GetObjective()
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.ALERT5 then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.ALERT5
local param={}
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.NOTHING then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.NOTHING
local param={}
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.PATROLRACETRACK then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.PATROLRACETRACK
local param={}
param.TrackAltitude=self.TrackAltitude
param.TrackSpeed=self.TrackSpeed
param.TrackPoint1=self.TrackPoint1
param.TrackPoint2=self.TrackPoint2
param.missionSpeed=self.missionSpeed
param.missionAltitude=self.missionAltitude
param.TrackFormation=self.TrackFormation
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.HOVER then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.HOVER
local param={}
param.hoverAltitude=self.hoverAltitude
param.hoverTime=self.hoverTime
param.missionSpeed=self.missionSpeed
param.missionAltitude=self.missionAltitude
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.LANDATCOORDINATE then
local DCStask={}
local Vec2=self.stayAt:GetVec2()
local DCStask=CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime,self.combatLand,self.directionAfter)
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.ONGUARD or self.type==AUFTRAG.Type.ARMOREDGUARD then
local DCStask={}
DCStask.id=self.type==AUFTRAG.Type.ONGUARD and AUFTRAG.SpecialTask.ONGUARD or AUFTRAG.SpecialTask.ARMOREDGUARD
local param={}
param.coordinate=self:GetObjective()
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.AIRDEFENSE then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.AIRDEFENSE
local param={}
param.zone=self:GetObjective()
DCStask.params=param
table.insert(DCStasks,DCStask)
elseif self.type==AUFTRAG.Type.EWR then
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.EWR
local param={}
param.zone=self:GetObjective()
DCStask.params=param
table.insert(DCStasks,DCStask)
local Enroutetask=CONTROLLABLE.EnRouteTaskEWR()
table.insert(self.enrouteTasks,Enroutetask)
else
self:T(self.lid..string.format("ERROR: Unknown mission task!"))
return nil
end
if self.type==AUFTRAG.Type.ORBIT or
self.type==AUFTRAG.Type.CAP or
self.type==AUFTRAG.Type.CAS or
self.type==AUFTRAG.Type.GCICAP or
self.type==AUFTRAG.Type.AWACS or
self.type==AUFTRAG.Type.TANKER or
self.type==AUFTRAG.Type.RECOVERYTANKER then
self.orbitVec2=self:GetTargetVec2()
if self.orbitVec2 then
self.targetHeading=self:GetTargetHeading()
local OffsetVec2=nil
if(self.orbitOffsetVec2~=nil)then
OffsetVec2=UTILS.DeepCopy(self.orbitOffsetVec2)
end
if OffsetVec2 then
if self.orbitOffsetVec2.r then
local r=self.orbitOffsetVec2.r
local phi=(self.orbitOffsetVec2.phi or 0)+self.targetHeading
OffsetVec2.x=r*math.cos(math.rad(phi))
OffsetVec2.y=r*math.sin(math.rad(phi))
else
OffsetVec2.x=self.orbitOffsetVec2.x
OffsetVec2.y=self.orbitOffsetVec2.y
end
end
local orbitVec2=OffsetVec2 and UTILS.Vec2Add(self.orbitVec2,OffsetVec2)or self.orbitVec2
local orbitRaceTrack=nil
if self.orbitLeg then
local heading=0
if self.orbitHeading then
if self.orbitHeadingRel then
heading=self.targetHeading+self.orbitHeading
else
heading=self.orbitHeading
end
else
heading=self.targetHeading or 0
end
orbitRaceTrack=UTILS.Vec2Translate(orbitVec2,self.orbitLeg,heading)
end
local orbitRaceTrackCoord=nil
if orbitRaceTrack then
orbitRaceTrackCoord=COORDINATE:NewFromVec2(orbitRaceTrack)
end
local DCStask=CONTROLLABLE.TaskOrbit(nil,COORDINATE:NewFromVec2(orbitVec2),self.orbitAltitude,self.orbitSpeed,orbitRaceTrackCoord)
table.insert(DCStasks,DCStask)
end
end
self:T3({missiontask=DCStasks})
if#DCStasks==1 then
return DCStasks[1]
else
return CONTROLLABLE.TaskCombo(nil,DCStasks)
end
end
function AUFTRAG:_GetDCSAttackTask(Target,DCStasks)
DCStasks=DCStasks or{}
for _,_target in pairs(Target.targets)do
local target=_target
if target.Type==TARGET.ObjectType.GROUP then
local DCStask=CONTROLLABLE.TaskAttackGroup(nil,target.Object,self.engageWeaponType,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageAsGroup)
table.insert(DCStasks,DCStask)
elseif target.Type==TARGET.ObjectType.UNIT or target.Type==TARGET.ObjectType.STATIC then
local DCStask=CONTROLLABLE.TaskAttackUnit(nil,target.Object,self.engageAsGroup,self.WeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType)
table.insert(DCStasks,DCStask)
end
end
return DCStasks
end
function AUFTRAG:GetMissionTaskforMissionType(MissionType)
local mtask=ENUMS.MissionTask.NOTHING
if MissionType==AUFTRAG.Type.ANTISHIP then
mtask=ENUMS.MissionTask.ANTISHIPSTRIKE
elseif MissionType==AUFTRAG.Type.AWACS then
mtask=ENUMS.MissionTask.AWACS
elseif MissionType==AUFTRAG.Type.BAI then
mtask=ENUMS.MissionTask.GROUNDATTACK
elseif MissionType==AUFTRAG.Type.BOMBCARPET then
mtask=ENUMS.MissionTask.GROUNDATTACK
elseif MissionType==AUFTRAG.Type.BOMBING then
mtask=ENUMS.MissionTask.GROUNDATTACK
elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then
mtask=ENUMS.MissionTask.RUNWAYATTACK
elseif MissionType==AUFTRAG.Type.CAP then
mtask=ENUMS.MissionTask.CAP
elseif MissionType==AUFTRAG.Type.GCICAP then
mtask=ENUMS.MissionTask.CAP
elseif MissionType==AUFTRAG.Type.CAS then
mtask=ENUMS.MissionTask.CAS
elseif MissionType==AUFTRAG.Type.PATROLZONE then
mtask=ENUMS.MissionTask.CAS
elseif MissionType==AUFTRAG.Type.CASENHANCED then
mtask=ENUMS.MissionTask.CAS
elseif MissionType==AUFTRAG.Type.ESCORT then
mtask=ENUMS.MissionTask.ESCORT
elseif MissionType==AUFTRAG.Type.FACA then
mtask=ENUMS.MissionTask.AFAC
elseif MissionType==AUFTRAG.Type.FAC then
mtask=ENUMS.MissionTask.AFAC
elseif MissionType==AUFTRAG.Type.FERRY then
mtask=ENUMS.MissionTask.NOTHING
elseif MissionType==AUFTRAG.Type.GROUNDESCORT then
mtask=ENUMS.MissionTask.GROUNDESCORT
elseif MissionType==AUFTRAG.Type.INTERCEPT then
mtask=ENUMS.MissionTask.INTERCEPT
elseif MissionType==AUFTRAG.Type.RECON then
mtask=ENUMS.MissionTask.RECONNAISSANCE
elseif MissionType==AUFTRAG.Type.SEAD then
mtask=ENUMS.MissionTask.SEAD
elseif MissionType==AUFTRAG.Type.STRIKE then
mtask=ENUMS.MissionTask.GROUNDATTACK
elseif MissionType==AUFTRAG.Type.TANKER then
mtask=ENUMS.MissionTask.REFUELING
elseif MissionType==AUFTRAG.Type.TROOPTRANSPORT then
mtask=ENUMS.MissionTask.TRANSPORT
elseif MissionType==AUFTRAG.Type.CARGOTRANSPORT then
mtask=ENUMS.MissionTask.TRANSPORT
elseif MissionType==AUFTRAG.Type.ARMORATTACK then
mtask=ENUMS.MissionTask.NOTHING
elseif MissionType==AUFTRAG.Type.HOVER then
mtask=ENUMS.MissionTask.NOTHING
elseif MissionType==AUFTRAG.Type.PATROLRACETRACK then
mtask=ENUMS.MissionTask.CAP
end
return mtask
end
function AUFTRAG.CheckMissionType(MissionType,PossibleTypes)
if type(PossibleTypes)=="string"then
PossibleTypes={PossibleTypes}
end
for _,canmission in pairs(PossibleTypes)do
if canmission==MissionType then
return true
end
end
return false
end
function AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,All)
if type(MissionTypes)~="table"then
MissionTypes={MissionTypes}
end
for _,cap in pairs(Capabilities)do
local capability=cap
for _,MissionType in pairs(MissionTypes)do
if All==true then
if capability.MissionType~=MissionType then
return false
end
else
if capability.MissionType==MissionType then
return true
end
end
end
end
if All==true then
return true
else
return false
end
end
function AUFTRAG.CheckMissionCapabilityAny(MissionTypes,Capabilities)
local res=AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,false)
return res
end
function AUFTRAG.CheckMissionCapabilityAll(MissionTypes,Capabilities)
local res=AUFTRAG.CheckMissionCapability(MissionTypes,Capabilities,true)
return res
end
do
AWACS={
ClassName="AWACS",
version="0.2.71",
lid="",
coalition=coalition.side.BLUE,
coalitiontxt="blue",
OpsZone=nil,
StationZone=nil,
AirWing=nil,
Frequency=271,
Modulation=radio.modulation.AM,
Airbase=nil,
AwacsAngels=25,
OrbitZone=nil,
CallSign=CALLSIGN.AWACS.Magic,
CallSignNo=1,
debug=false,
verbose=false,
ManagedGrps={},
ManagedGrpID=0,
ManagedTaskID=0,
AnchorStacks={},
CAPIdleAI={},
CAPIdleHuman={},
TaskedCAPAI={},
TaskedCAPHuman={},
OpenTasks={},
ManagedTasks={},
PictureAO={},
PictureEWR={},
Contacts={},
Countactcounter=0,
ContactsAO={},
RadioQueue={},
PrioRadioQueue={},
TacticalQueue={},
AwacsTimeOnStation=4,
AwacsTimeStamp=0,
EscortsTimeOnStation=4,
EscortsTimeStamp=0,
CAPTimeOnStation=4,
AwacsROE="",
AwacsROT="",
MenuStrict=true,
MaxAIonCAP=3,
AIonCAP=0,
AICAPMissions={},
ShiftChangeAwacsFlag=false,
ShiftChangeEscortsFlag=false,
ShiftChangeAwacsRequested=false,
ShiftChangeEscortsRequested=false,
CAPAirwings={},
MonitoringData={},
MonitoringOn=false,
FlightGroups={},
AwacsMission=nil,
AwacsInZone=false,
AwacsReady=false,
CatchAllMissions={},
CatchAllFGs={},
PictureInterval=300,
ReassignTime=120,
PictureTimeStamp=0,
BorderZone=nil,
RejectZone=nil,
maxassigndistance=100,
PlayerGuidance=true,
ModernEra=true,
callsignshort=true,
keepnumber=true,
callsignTranslations=nil,
TacDistance=45,
MeldDistance=35,
ThreatDistance=25,
AOName="Rock",
AOCoordinate=nil,
clientmenus=nil,
RadarBlur=15,
ReassignmentPause=180,
NoGroupTags=false,
SuppressScreenOutput=false,
NoMissileCalls=true,
GoogleTTSPadding=1,
WindowsTTSPadding=2.5,
PlayerCapAssignment=true,
AllowMarkers=false,
PlayerStationName=nil,
GCI=false,
GCIGroup=nil,
locale="en",
IncludeHelicopters=false,
TacticalMenu=false,
TacticalFrequencies={},
TacticalSubscribers={},
TacticalBaseFreq=130,
TacticalIncrFreq=0.5,
TacticalModulation=radio.modulation.AM,
TacticalInterval=120,
DetectionSet=nil,
MaxMissionRange=125,
}
AWACS.CallSignClear={
[1]="Overlord",
[2]="Magic",
[3]="Wizard",
[4]="Focus",
[5]="Darkstar",
}
AWACS.AnchorNames={
[1]="One",
[2]="Two",
[3]="Three",
[4]="Four",
[5]="Five",
[6]="Six",
[7]="Seven",
[8]="Eight",
[9]="Nine",
[10]="Ten",
}
AWACS.IFF=
{
SPADES="Spades",
NEUTRAL="Neutral",
FRIENDLY="Friendly",
ENEMY="Hostile",
BOGEY="Bogey",
}
AWACS.Phonetic=
{
[1]='Alpha',
[2]='Bravo',
[3]='Charlie',
[4]='Delta',
[5]='Echo',
[6]='Foxtrot',
[7]='Golf',
[8]='Hotel',
[9]='India',
[10]='Juliett',
[11]='Kilo',
[12]='Lima',
[13]='Mike',
[14]='November',
[15]='Oscar',
[16]='Papa',
[17]='Quebec',
[18]='Romeo',
[19]='Sierra',
[20]='Tango',
[21]='Uniform',
[22]='Victor',
[23]='Whiskey',
[24]='Xray',
[25]='Yankee',
[26]='Zulu',
}
AWACS.Shipsize=
{
[1]="Singleton",
[2]="Two-Ship",
[3]="Heavy",
[4]="Gorilla",
}
AWACS.ROE={
POLICE="Police",
VID="Visual ID",
IFF="IFF",
BVR="Beyond Visual Range",
}
AWACS.ROT={
BYPASSESCAPE="Bypass and Escape",
EVADE="Evade Fire",
PASSIVE="Passive Defense",
RETURNFIRE="Return Fire",
OPENFIRE="Open Fire",
}
AWACS.THREATLEVEL={
GREEN=3,
AMBER=7,
RED=10,
}
AWACS.CapVoices={
[1]="de-DE-Wavenet-A",
[2]="de-DE-Wavenet-B",
[3]="fr-FR-Wavenet-A",
[4]="fr-FR-Wavenet-B",
[5]="en-GB-Wavenet-A",
[6]="en-GB-Wavenet-B",
[7]="en-GB-Wavenet-D",
[8]="en-AU-Wavenet-B",
[9]="en-US-Wavenet-J",
[10]="en-US-Wavenet-H",
}
AWACS.Messages={
EN=
{
DEFEND="%s, %s! %s! %s! Defend!",
VECTORTO="%s, %s. Vector%s %s",
VECTORTOTTS="%s, %s, Vector%s %s",
ANGELS=". Angels ",
ZERO="zero",
VANISHED="%s, %s Group. Vanished.",
VANISHEDTTS="%s, %s group vanished.",
SHIFTCHANGE="%s shift change for %s control.",
GROUPCAP="Group",
GROUP="group",
MILES="miles",
THOUSAND="thousand",
BOGEY="Bogey",
ALLSTATIONS="All Stations",
PICCLEAN="%s. %s. Picture Clean.",
PICTURE="Picture",
ONE="One",
GROUPMULTI="groups",
NOTCHECKEDIN="%s. %s. Negative. You are not checked in.",
CLEAN="%s. %s. Clean.",
DOPE="%s. %s. Bogey Dope. ",
VIDPOS="%s. %s. Copy, target identified as %s.",
VIDNEG="%s. %s. Negative, get closer to target.",
FFNEUTRAL="Neutral",
FFFRIEND="Friendly",
FFHOSTILE="Hostile",
FFSPADES="Spades",
FFCLEAN="Clean",
COPY="%s. %s. Copy.",
TARGETEDBY="Targeted by %s.",
STATUS="Status",
ALREADYCHECKEDIN="%s. %s. Negative. You are already checked in.",
ALPHACHECK="Alpha Check",
CHECKINAI="%s. %s. Checking in as fragged. Expected playtime %d hours. Request Alpha Check %s.",
SAFEFLIGHT="%s. %s. Copy. Have a safe flight home.",
VERYLOW="very low",
AIONSTATION="%s. %s. On station over anchor %d at angels  %d. Ready for tasking.",
POPUP="Pop-up",
NEWGROUP="New group",
HIGH=" High.",
VERYFAST=" Very fast.",
FAST=" Fast.",
THREAT="Threat",
MERGED="Merged",
SCREENVID="Intercept and VID %s group.",
SCREENINTER="Intercept %s group.",
ENGAGETAG="Targeted by %s.",
REQCOMMIT="%s. %s group. %s. %s, request commit.",
AICOMMIT="%s. %s group. %s. %s, commit.",
COMMIT="Commit",
SUNRISE="%s. All stations, SUNRISE SUNRISE SUNRISE, %s.",
AWONSTATION="%s on station for %s control.",
STATIONAT="%s. %s. Station at %s at angels %d.",
STATIONATLONG="%s. %s. Station at %s at angels %d doing %d knots.",
STATIONSCREEN="%s. %s.\nStation at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s.",
STATIONTASK="Station at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s",
VECTORSTATION=" to Station",
TEXTOPTIONS1="Lost friendly flight",
TEXTOPTIONS2="Vanished friendly flight",
TEXTOPTIONS3="Faded friendly contact",
TEXTOPTIONS4="Lost contact with",
},
}
AWACS.TaskDescription={
ANCHOR="Anchor",
REANCHOR="Re-Anchor",
VID="VID",
IFF="IFF",
INTERCEPT="Intercept",
SWEEP="Sweep",
RTB="RTB",
}
AWACS.TaskStatus={
IDLE="Idle",
UNASSIGNED="Unassigned",
REQUESTED="Requested",
ASSIGNED="Assigned",
EXECUTING="Executing",
SUCCESS="Success",
FAILED="Failed",
DEAD="Dead",
}
function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,StationZone,Frequency,Modulation)
local self=BASE:Inherit(self,FSM:New())
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
self.coalition=coalition.side.BLUE
self.coalitiontxt=Coalition
elseif Coalition=="red"then
self.coalition=coalition.side.RED
self.coalitiontxt=Coalition
elseif Coalition=="neutral"then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt=Coalition
else
self:E("ERROR: Unknown coalition in AWACS!")
end
else
self.coalition=Coalition
self.coalitiontxt=string.lower(UTILS.GetCoalitionName(self.coalition))
end
self.Name=Name
self.AirWing=AirWing
AirWing:SetUsingOpsAwacs(self)
self.CAPAirwings=FIFO:New()
self.CAPAirwings:Push(AirWing,1)
self.AwacsFG=nil
self.RadarBlur=15
if type(OpsZone)=="string"then
self.OpsZone=ZONE:New(OpsZone)
elseif type(OpsZone)=="table"and OpsZone.ClassName and string.find(OpsZone.ClassName,"ZONE")then
self.OpsZone=OpsZone
else
self:E("AWACS - Invalid Zone passed!")
return
end
self.AOCoordinate=COORDINATE:NewFromVec3(coalition.getMainRefPoint(self.coalition))
self.AOName=self.OpsZone:GetName()
self.UseBullsAO=true
self.ControlZoneRadius=100
self.StationZone=ZONE:New(StationZone)
self.StationZoneName=StationZone
self.Frequency=Frequency or 271
self.Modulation=Modulation or radio.modulation.AM
self.MultiFrequency={self.Frequency}
self.MultiModulation={self.Modulation}
self.Airbase=AIRBASE:FindByName(AirbaseName)
self.AwacsAngels=25
if AwacsOrbit then
self.OrbitZone=ZONE:New(AwacsOrbit)
end
self.BorderZone=nil
self.CallSign=CALLSIGN.AWACS.Magic
self.CallSignNo=1
self.NoHelos=true
self.AIRequested=0
self.AIonCAP=0
self.AICAPMissions=FIFO:New()
self.FlightGroups=FIFO:New()
self.Countactcounter=0
self.PictureInterval=300
self.PictureTimeStamp=0
self.ReassignTime=120
self.intelstarted=false
self.sunrisedone=false
local speed=250
self.SpeedBase=speed
self.Speed=speed
self.Heading=0
self.Leg=50
self.invisible=false
self.immortal=false
self.callsigntxt="AWACS"
self.AwacsTimeOnStation=4
self.AwacsTimeStamp=0
self.EscortsTimeOnStation=4
self.EscortsTimeStamp=0
self.ShiftChangeTime=0.25
self.ShiftChangeAwacsFlag=false
self.ShiftChangeEscortsFlag=false
self.CapSpeedBase=270
self.CAPTimeOnStation=4
self.MaxAIonCAP=4
self.AICAPCAllName=CALLSIGN.Aircraft.Colt
self.AICAPCAllNumber=0
self.CAPGender="male"
self.CAPCulture="en-US"
self.CAPVoice=nil
self.AwacsMission=nil
self.AwacsInZone=false
self.AwacsReady=false
self.AwacsROE=AWACS.ROE.IFF
self.AwacsROT=AWACS.ROT.BYPASSESCAPE
self.HasEscorts=false
self.EscortTemplate=""
self.EscortMission={}
self.EscortMissionReplacement={}
self.PathToSRS="C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender="female"
self.Culture="en-GB"
self.Voice=nil
self.Port=5002
self.Volume=1.0
self.RadioQueue=FIFO:New()
self.PrioRadioQueue=FIFO:New()
self.TacticalQueue=FIFO:New()
self.maxspeakentries=3
self.GoogleTTSPadding=1
self.WindowsTTSPadding=2.5
self.clientset=SET_CLIENT:New():FilterActive(true):FilterCoalitions(self.coalitiontxt):FilterCategories("plane"):FilterStart()
self.PlayerGuidance=true
self.ModernEra=true
self.NoGroupTags=false
self.SuppressScreenOutput=false
self.ReassignmentPause=180
self.callsignshort=true
self.DeclareRadius=5
self.MenuStrict=true
self.maxassigndistance=100
self.NoMissileCalls=true
self.PlayerCapAssignment=true
self.ManagedGrps={}
self.ManagedGrpID=0
self.callsignTranslations=nil
self.AnchorStacks=FIFO:New()
self.AnchorBaseAngels=22
self.AnchorStackDistance=2
self.AnchorMaxStacks=4
self.AnchorMaxAnchors=2
self.AnchorMaxZones=6
self.AnchorCurrZones=1
self.AnchorTurn=-(360/self.AnchorMaxZones)
self:_CreateAnchorStack()
self.ManagedTasks=FIFO:New()
local MonitoringData={}
MonitoringData.AICAPCurrent=0
MonitoringData.AICAPMax=self.MaxAIonCAP
MonitoringData.Airwings=1
MonitoringData.PlayersCheckedin=0
MonitoringData.Players=0
MonitoringData.AwacsShiftChange=false
MonitoringData.AwacsStateFG="unknown"
MonitoringData.AwacsStateMission="unknown"
MonitoringData.EscortsShiftChange=false
MonitoringData.EscortsStateFG={}
MonitoringData.EscortsStateMission={}
self.MonitoringOn=false
self.MonitoringData=MonitoringData
self.CatchAllMissions={}
self.CatchAllFGs={}
self.PictureAO=FIFO:New()
self.PictureEWR=FIFO:New()
self.Contacts=FIFO:New()
self.CID=0
self.ContactsAO=FIFO:New()
self.clientmenus=FIFO:New()
self.TacticalMenu=false
self.TacticalBaseFreq=130
self.TacticalIncrFreq=0.5
self.TacticalModulation=radio.modulation.AM
self.acticalFrequencies={}
self.TacticalSubscribers={}
self.TacticalInterval=120
self.DetectionSet=SET_GROUP:New()
self.lid=string.format("%s (%s) | ",self.Name,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","StartUp")
self:AddTransition("StartUp","Started","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","CheckedIn","*")
self:AddTransition("*","CheckedOut","*")
self:AddTransition("*","AssignAnchor","*")
self:AddTransition("*","AssignedAnchor","*")
self:AddTransition("*","ReAnchor","*")
self:AddTransition("*","NewCluster","*")
self:AddTransition("*","NewContact","*")
self:AddTransition("*","LostCluster","*")
self:AddTransition("*","LostContact","*")
self:AddTransition("*","CheckRadioQueue","*")
self:AddTransition("*","CheckTacticalQueue","*")
self:AddTransition("*","EscortShiftChange","*")
self:AddTransition("*","AwacsShiftChange","*")
self:AddTransition("*","FlightOnMission","*")
self:AddTransition("*","Intercept","*")
self:AddTransition("*","InterceptSuccess","*")
self:AddTransition("*","InterceptFailure","*")
self:AddTransition("*","Stop","Stopped")
local text=string.format("%sAWACS Version %s Initiated",self.lid,self.version)
self:I(text)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.Dead,self._EventHandler)
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
self:HandleEvent(EVENTS.BDA,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.Shot,self._EventHandler)
self:_InitLocalization()
return self
end
function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number)
self:T(self.lid.."SetTacticalRadios")
if not self.AwacsSRS then
MESSAGE:New("AWACS: Setup SRS in your code BEFORE trying to add tac radios please!",30,"ERROR",true):ToLog():ToAll()
return self
end
self.TacticalMenu=true
self.TacticalBaseFreq=BaseFreq or 130
self.TacticalIncrFreq=Increase or 0.5
self.TacticalModulation=Modulation or radio.modulation.AM
self.TacticalInterval=Interval or 120
local number=Number or 10
if number<1 then number=1 end
if number>10 then number=10 end
for i=1,number do
local freq=self.TacticalBaseFreq+((i-1)*self.TacticalIncrFreq)
self.TacticalFrequencies[freq]=freq
end
if self.AwacsSRS then
self.TacticalSRS=MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation,self.Backend)
self.TacticalSRS:SetCoalition(self.coalition)
self.TacticalSRS:SetGender(self.Gender)
self.TacticalSRS:SetCulture(self.Culture)
self.TacticalSRS:SetVoice(self.Voice)
self.TacticalSRS:SetPort(self.Port)
self.TacticalSRS:SetLabel("AWACS")
self.TacticalSRS:SetVolume(self.Volume)
if self.PathToGoogleKey then
self.TacticalSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey)
self.TacticalSRS:SetProvider(MSRS.Provider.GOOGLE)
end
self.TacticalSRSQ=MSRSQUEUE:New("Tactical AWACS")
end
return self
end
function AWACS:_RefreshMenuNonSubscribed()
self:T(self.lid.."_RefreshMenuNonSubscribed")
local aliveset=self.clientset:GetAliveSet()
for _,_group in pairs(aliveset)do
local grp=_group
local Group=grp:GetGroup()
local gname=nil
if Group and Group:IsAlive()then
gname=Group:GetName()
self:T(gname)
end
local menustr=self.clientmenus:ReadByID(gname)
local menu=menustr.tactical
if not self.TacticalSubscribers[gname]and menu then
menu:RemoveSubMenus()
for _,_freq in UTILS.spairs(self.TacticalFrequencies)do
local modu=UTILS.GetModulationName(self.TacticalModulation)
local text=string.format("Subscribe to %.3f %s",_freq,modu)
local entry=MENU_GROUP_COMMAND:New(Group,text,menu,self._SubScribeTactRadio,self,Group,_freq)
end
end
end
return self
end
function AWACS:_UnsubScribeTactRadio(Group)
self:T(self.lid.."_UnsubScribeTactRadio")
local text=""
local textScreen=""
local GID,Outcome=self:_GetManagedGrpID(Group)
local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1"
local gname=Group:GetName()or"unknown"
if Outcome and self.TacticalSubscribers[gname]then
local Freq=self.TacticalSubscribers[gname]
self.TacticalFrequencies[Freq]=Freq
self.TacticalSubscribers[gname]=nil
local modu=self.TacticalModulation==0 and"AM"or"FM"
text=string.format("%s, %s, switch back to AWACS main frequency!",gcallsign,self.callsigntxt)
self:_NewRadioEntry(text,text,GID,true,true,true,false,true)
self:_RefreshMenuNonSubscribed()
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_SubScribeTactRadio(Group,Frequency)
self:T(self.lid.."_SubScribeTactRadio")
local text=""
local textScreen=""
local GID,Outcome=self:_GetManagedGrpID(Group)
local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1"
local gname=Group:GetName()or"unknown"
if Outcome then
self.TacticalSubscribers[gname]=Frequency
self.TacticalFrequencies[Frequency]=nil
local modu=self.TacticalModulation==0 and"AM"or"FM"
text=string.format("%s, %s, switch to %.3f %s for tactical information!",gcallsign,self.callsigntxt,Frequency,modu)
self:_NewRadioEntry(text,text,GID,true,true,true,false,true)
local menustr=self.clientmenus:ReadByID(gname)
local menu=menustr.tactical
if menu then
menu:RemoveSubMenus()
local text=string.format("Unsubscribe %.3f %s",Frequency,modu)
local entry=MENU_GROUP_COMMAND:New(Group,text,menu,self._UnsubScribeTactRadio,self,Group)
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_CheckSubscribers()
self:T(self.lid.."_InitLocalization")
for _name,_freq in pairs(self.TacticalSubscribers or{})do
local grp=GROUP:FindByName(_name)
if(not grp)or(not grp:IsAlive())then
self.TacticalFrequencies[_freq]=_freq
self.TacticalSubscribers[_name]=nil
end
end
return self
end
function AWACS:_InitLocalization()
self:T(self.lid.."_InitLocalization")
self.gettext=TEXTANDSOUND:New("AWACS","en")
self.locale="en"
for locale,table in pairs(self.Messages)do
local Locale=string.lower(tostring(locale))
self:T("**** Adding locale: "..Locale)
for ID,Text in pairs(table)do
self:T(string.format('Adding ID %s',tostring(ID)))
self.gettext:AddEntry(Locale,tostring(ID),Text)
end
end
return self
end
function AWACS:SetLocale(Locale)
self:T(self.lid.."SetLocale")
self.locale=Locale or"en"
return self
end
function AWACS: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)
table.insert(self.MultiModulation,Modulation)
if self.AwacsSRS then
self.AwacsSRS:SetFrequencies(self.MultiFrequency)
self.AwacsSRS:SetModulations(self.MultiModulation)
end
return self
end
function AWACS:SetAsGCI(EWR,Delay)
self:T(self.lid.."SetGCI")
local delay=Delay or-5
if type(EWR)=="string"then
self.GCIGroup=GROUP:FindByName(EWR)
else
self.GCIGroup=EWR
end
self.GCI=true
self:SetEscort(0)
return self
end
function AWACS:_NewRadioEntry(TextTTS,TextScreen,GID,IsGroup,ToScreen,IsNew,FromAI,IsPrio,Tactical)
self:T(self.lid.."_NewRadioEntry")
local RadioEntry={}
RadioEntry.IsNew=IsNew
RadioEntry.TextTTS=TextTTS
RadioEntry.TextScreen=TextScreen or TextTTS
RadioEntry.GroupID=GID
RadioEntry.ToScreen=ToScreen
RadioEntry.Duration=MSRS.getSpeechTime(TextTTS,0.95,false)or 8
RadioEntry.FromAI=FromAI
RadioEntry.IsGroup=IsGroup
if Tactical then
self.TacticalQueue:Push(RadioEntry)
elseif IsPrio then
self.PrioRadioQueue:Push(RadioEntry)
else
self.RadioQueue:Push(RadioEntry)
end
return self
end
function AWACS:SetBullsEyeAlias(Name)
self:T(self.lid.."_SetBullsEyeAlias")
self.AOName=Name or"Rock"
return self
end
function AWACS:SetTOS(AICHours,CapHours)
self:T(self.lid.."SetTOS")
self.AwacsTimeOnStation=AICHours or 4
self.CAPTimeOnStation=CapHours or 4
return self
end
function AWACS:SetReassignmentPause(Seconds)
self.ReassignmentPause=Seconds or 180
return self
end
function AWACS:SuppressScreenMessages(Switch)
self:T(self.lid.."_SetBullsEyeAlias")
self.SuppressScreenOutput=Switch or false
return self
end
function AWACS:ZipLip()
self:T(self.lid.."ZipLip")
self:SuppressScreenMessages(true)
self.PlayerGuidance=false
self.callsignshort=true
self.NoMissileCalls=true
return self
end
function AWACS:SetCustomCallsigns(translationTable)
self.callsignTranslations=translationTable
end
function AWACS:_GetGIDFromGroupOrName(Group)
self:T(self.lid.."_GetGIDFromGroupOrName")
self:T({Group})
local GID=0
local Outcome=false
local CallSign="Ghost 1"
local nametocheck=CallSign
if Group and type(Group)=="string"then
nametocheck=Group
elseif Group and Group:IsInstanceOf("GROUP")then
nametocheck=Group:GetName()
else
return false,0,CallSign
end
local managedgrps=self.ManagedGrps or{}
for _,_managed in pairs(managedgrps)do
local managed=_managed
if managed.GroupName==nametocheck then
GID=managed.GID
Outcome=true
CallSign=managed.CallSign
end
end
self:T({Outcome,GID,CallSign})
return Outcome,GID,CallSign
end
function AWACS:_EventHandler(EventData)
self:T(self.lid.."_EventHandler")
self:T({Event=EventData.id})
local Event=EventData
if Event.id==EVENTS.PlayerEnterAircraft or Event.id==EVENTS.PlayerEnterUnit then
if Event.IniCoalition==self.coalition then
self:_SetClientMenus()
end
end
if Event.id==EVENTS.PlayerLeaveUnit 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))
if Event.IniCoalition==self.coalition then
local Outcome,GID,CallSign=self:_GetGIDFromGroupOrName(Event.IniGroupName)
if Outcome and GID>0 then
self:T("Task Abort and Checkout Called")
self:_TaskAbort(Event.IniGroupName)
self:_CheckOut(nil,GID,true)
end
end
end
if Event.id==EVENTS.Ejection or Event.id==EVENTS.Crash or Event.id==EVENTS.Dead or Event.id==EVENTS.PilotDead then
if Event.IniCoalition==self.coalition then
local Outcome,GID,CallSign=self:_GetGIDFromGroupOrName(Event.IniGroupName)
if Outcome and GID>0 then
self:_TaskAbort(Event.IniGroupName)
self:_CheckOut(nil,GID,true)
end
end
end
if Event.id==EVENTS.Shot and self.PlayerGuidance and not self.NoMissileCalls then
if Event.IniCoalition~=self.coalition then
self:T("Shot from: "..Event.IniGroupName)
local position=Event.IniGroup:GetCoordinate()
if not position then return self end
local Category=Event.WeaponCategory
local WeaponDesc=EventData.Weapon:getDesc()
self:T({WeaponDesc})
if WeaponDesc.category==1 and(WeaponDesc.missileCategory==1 or WeaponDesc.missileCategory==2)then
self:T("AAM or SAM Missile fired")
local warndist=25
local Type="SAM"
if WeaponDesc.category==1 then
Type="Missile"
local guidance=WeaponDesc.guidance or 4
if guidance==2 then
warndist=10
elseif guidance==3 then
warndist=25
elseif guidance==4 then
warndist=15
elseif guidance==5 then
warndist=10
end
end
self:_MissileWarning(position,Type,warndist)
end
end
end
return self
end
function AWACS:_MissileWarning(Coordinate,Type,Warndist)
self:T(self.lid.."_MissileWarning Type="..Type.." WarnDist="..Warndist)
if not Coordinate then return self end
local shotzone=ZONE_RADIUS:New("WarningZone",Coordinate:GetVec2(),UTILS.NMToMeters(Warndist))
local targetgrpset=SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryAirplane():FilterActive():FilterZones({shotzone}):FilterOnce()
if targetgrpset:Count()>0 then
local targets=targetgrpset:GetSetObjects()
for _,_grp in pairs(targets)do
if _grp and _grp:IsAlive()then
local isPlayer=_grp:IsPlayer()
if isPlayer then
local callsign=self:_GetCallSign(_grp)
local defend=self.gettext:GetEntry("DEFEND",self.locale)
local text=string.format(defend,callsign,Type,Type,Type)
self:_NewRadioEntry(text,text,0,false,self.debug,true,false,true)
end
end
end
end
return self
end
function AWACS:SetRadarBlur(Percent)
local percent=Percent or 15
if percent<0 then percent=0 end
if percent>100 then percent=100 end
self.RadarBlur=Percent
return self
end
function AWACS:SetColdWar()
self.ModernEra=false
self.AwacsROT=AWACS.ROT.PASSIVE
self.AwacsROE=AWACS.ROE.VID
self.RadarBlur=25
self:SetInterceptTimeline(35,25,15)
return self
end
function AWACS:SetModernEra()
self.ModernEra=true
self.AwacsROT=AWACS.ROT.EVADE
self.AwacsROE=AWACS.ROE.BVR
self.RadarBlur=15
return self
end
function AWACS:SetModernEraDefensive()
self.ModernEra=true
self.AwacsROT=AWACS.ROT.EVADE
self.AwacsROE=AWACS.ROE.IFF
self.RadarBlur=15
return self
end
function AWACS:SetModernEraAggressive()
self.ModernEra=true
self.AwacsROT=AWACS.ROT.RETURNFIRE
self.AwacsROE=AWACS.ROE.BVR
self.RadarBlur=15
return self
end
function AWACS:SetPolicingModern()
self.ModernEra=true
self.AwacsROT=AWACS.ROT.BYPASSESCAPE
self.AwacsROE=AWACS.ROE.VID
self.RadarBlur=15
return self
end
function AWACS:SetPolicingColdWar()
self.ModernEra=false
self.AwacsROT=AWACS.ROT.BYPASSESCAPE
self.AwacsROE=AWACS.ROE.VID
self.RadarBlur=25
self:SetInterceptTimeline(35,25,15)
return self
end
function AWACS:SetPlayerGuidance(Switch)
if(Switch==nil)or(Switch==true)then
self.PlayerGuidance=true
else
self.PlayerGuidance=false
end
return self
end
function AWACS:GetName()
return self.Name or"not set"
end
function AWACS:SetInterceptTimeline(TacDistance,MeldDistance,ThreatDistance)
self.TacDistance=TacDistance or 45
self.MeldDistance=MeldDistance or 35
self.ThreatDistance=ThreatDistance or 25
return self
end
function AWACS:SetAdditionalZone(Zone,Draw)
self:T(self.lid.."SetAdditionalZone")
self.BorderZone=Zone
if self.debug then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition)
elseif Draw then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
end
return self
end
function AWACS:SetRejectionZone(Zone,Draw)
self:T(self.lid.."SetRejectionZone")
self.RejectZone=Zone
if Draw then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
elseif self.debug then
Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true)
MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition)
end
return self
end
function AWACS:DrawFEZ()
self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true)
return self
end
function AWACS:SetAwacsDetails(CallSign,CallSignNo,Angels,Speed,Heading,Leg)
self:T(self.lid.."SetAwacsDetails")
self.CallSign=CallSign or CALLSIGN.AWACS.Magic
self.CallSignNo=CallSignNo or 1
self.AwacsAngels=Angels or 25
local speed=Speed or 250
self.SpeedBase=speed
self.Speed=speed
self.Heading=Heading or 0
self.Leg=Leg or 25
return self
end
function AWACS:SetCustomAWACSCallSign(CallsignTable)
self:T(self.lid.."SetCustomAWACSCallSign")
self.CallSignClear=CallsignTable
return self
end
function AWACS:AddGroupToDetection(Group)
self:T(self.lid.."AddGroupToDetection")
if Group and Group.ClassName and Group.ClassName=="GROUP"then
self.DetectionSet:AddGroup(Group)
elseif Group and Group.ClassName and Group.ClassName=="SET_GROUP"then
self.DetectionSet:AddSet(Group)
end
return self
end
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend)
self:T(self.lid.."SetSRS")
self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender=Gender or MSRS.gender or"male"
self.Culture=Culture or MSRS.culture or"en-US"
self.Port=Port or MSRS.port or 5002
self.Voice=Voice or MSRS.voice
self.PathToGoogleKey=PathToGoogleKey
self.AccessKey=AccessKey
self.Volume=Volume or 1.0
self.Backend=Backend or MSRS.backend
BASE:I({backend=self.Backend})
self.AwacsSRS=MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Backend)
self.AwacsSRS:SetCoalition(self.coalition)
self.AwacsSRS:SetGender(self.Gender)
self.AwacsSRS:SetCulture(self.Culture)
self.AwacsSRS:SetPort(self.Port)
self.AwacsSRS:SetLabel("AWACS")
self.AwacsSRS:SetVolume(Volume)
if self.PathToGoogleKey then
self.AwacsSRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey)
self.AwacsSRS:SetProvider(MSRS.Provider.GOOGLE)
end
if(not PathToGoogleKey)and self.AwacsSRS:GetProvider()==MSRS.Provider.GOOGLE then
self.PathToGoogleKey=MSRS.poptions.gcloud.credentials
self.Voice=Voice or MSRS.poptions.gcloud.voice
self.AccessKey=AccessKey or MSRS.poptions.gcloud.key
end
self.AwacsSRS:SetVoice(self.Voice)
return self
end
function AWACS:SetSRSVoiceCAP(Gender,Culture,Voice)
self:T(self.lid.."SetSRSVoiceCAP")
self.CAPGender=Gender or"male"
self.CAPCulture=Culture or"en-US"
self.CAPVoice=Voice or"en-GB-Standard-B"
return self
end
function AWACS:SetAICAPDetails(Callsign,MaxAICap,TOS,Speed)
self:T(self.lid.."SetAICAPDetails")
self.CapSpeedBase=Speed or 270
self.CAPTimeOnStation=TOS or 4
self.MaxAIonCAP=MaxAICap or 4
self.AICAPCAllName=Callsign or CALLSIGN.Aircraft.Colt
return self
end
function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance)
self:T(self.lid.."SetEscort")
if EscortNumber and EscortNumber>0 then
self.HasEscorts=true
self.EscortNumber=EscortNumber
else
self.HasEscorts=false
self.EscortNumber=0
end
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)
self:T(self.lid.."_MessageVector")
local managedgroup=self.ManagedGrps[GID]
local Tag=Tag or""
if managedgroup and Coordinate then
local tocallsign=managedgroup.CallSign or"Ghost 1"
local group=managedgroup.Group
local groupposition=group:GetCoordinate()
local BRtext,BRtextTTS=self:_ToStringBR(groupposition,Coordinate)
local vector=self.gettext:GetEntry("VECTORTO",self.locale)
local vectortts=self.gettext:GetEntry("VECTORTOTTS",self.locale)
local angelstxt=self.gettext:GetEntry("ANGELS",self.locale)
local text=string.format(vectortts,tocallsign,self.callsigntxt,Tag,BRtextTTS)
local textScreen=string.format(vector,tocallsign,self.callsigntxt,Tag,BRtext)
if Angels then
text=text..angelstxt..tostring(Angels).."."
textScreen=textScreen..angelstxt..tostring(Angels).."."
end
self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false)
end
return self
end
function AWACS:_StartEscorts(Shiftchange)
self:T(self.lid.."_StartEscorts")
local AwacsFG=self.AwacsFG
local group=AwacsFG:GetGroup()
local timeonstation=(self.EscortsTimeOnStation+self.ShiftChangeTime)*3600
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=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
self.EscortMissionReplacement[i]=escort
else
self.EscortMission[i]=escort
end
end
return self
end
function AWACS:_StartSettings(FlightGroup,Mission)
self:T(self.lid.."_StartSettings")
local Mission=Mission
local AwacsFG=FlightGroup
if self.AwacsMission:GetName()==Mission:GetName()then
self:T("Setting up Awacs")
AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false)
AwacsFG:SwitchRadio(self.Frequency,self.Modulation)
AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000)
AwacsFG:SetHomebase(self.Airbase)
AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo)
AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold)
AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN)
AwacsFG:SetDefaultEPLRS(self.ModernEra)
AwacsFG:SetDespawnAfterLanding()
AwacsFG:SetFuelLowRTB(true)
AwacsFG:SetFuelLowThreshold(20)
local group=AwacsFG:GetGroup()
group:SetCommandInvisible(self.invisible)
group:SetCommandImmortal(self.immortal)
group:CommandSetCallsign(self.CallSign,self.CallSignNo,2)
group:CommandEPLRS(self.ModernEra,5)
self.AwacsFG=AwacsFG
self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign])
self:__CheckRadioQueue(10)
if self.HasEscorts then
self:_StartEscorts()
end
self.AwacsTimeStamp=timer.getTime()
self.EscortsTimeStamp=timer.getTime()
self.PictureTimeStamp=timer.getTime()+10*60
self.AwacsReady=true
self:Started()
elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName()==Mission:GetName()then
self:T("Setting up Awacs Replacement")
AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false)
AwacsFG:SwitchRadio(self.Frequency,self.Modulation)
AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000)
AwacsFG:SetHomebase(self.Airbase)
self.CallSignNo=self.CallSignNo+1
AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo)
AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold)
AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN)
AwacsFG:SetDefaultEPLRS(self.ModernEra)
AwacsFG:SetDespawnAfterLanding()
AwacsFG:SetFuelLowRTB(true)
AwacsFG:SetFuelLowThreshold(20)
local group=AwacsFG:GetGroup()
group:SetCommandInvisible(self.invisible)
group:SetCommandImmortal(self.immortal)
group:CommandSetCallsign(self.CallSign,self.CallSignNo,2)
self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign])
local shifting=self.gettext:GetEntry("SHIFTCHANGE",self.locale)
local text=string.format(shifting,self.callsigntxt,self.AOName or"Rock")
self:T(self.lid..text)
AwacsFG:RadioTransmission(text,1,false)
self.AwacsFG=AwacsFG
if self.HasEscorts then
self:_StartEscorts(true)
end
self.AwacsTimeStamp=timer.getTime()
self.EscortsTimeStamp=timer.getTime()
self.AwacsReady=true
end
return self
end
function AWACS:_ToStringBULLS(Coordinate,ssml,TTS)
self:T(self.lid.."_ToStringBULLS")
local bullseyename=self.AOName or"Rock"
local BullsCoordinate=self.AOCoordinate
local DirectionVec3=BullsCoordinate:GetDirectionVec3(Coordinate)
local AngleRadians=Coordinate:GetAngleRadians(DirectionVec3)
local Distance=Coordinate:Get2DDistance(BullsCoordinate)
local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0)
local Bearing=string.format('%03d',AngleDegrees)
local Distance=UTILS.Round(UTILS.MetersToNM(Distance),0)
if ssml then
return string.format("%s <say-as interpret-as='characters'>%03d</say-as>, %d",bullseyename,Bearing,Distance)
end
if TTS then
Bearing=self:_ToStringBullsTTS(Bearing)
local zero=self.gettext:GetEntry("ZERO",self.locale)
local BearingTTS=string.gsub(Bearing,"0",zero)
return string.format("%s %s, %d",bullseyename,BearingTTS,Distance)
else
return string.format("%s %s, %d",bullseyename,Bearing,Distance)
end
end
function AWACS:_ToStringBullsTTS(Text)
local text=Text
text=string.gsub(text,"Bullseye","Bulls eye")
text=string.gsub(text,"%d","%1 ")
text=string.gsub(text," ,",".")
text=string.gsub(text," $","")
return text
end
function AWACS:_GetManagedGrpID(Group)
if not Group or not Group:IsAlive()then
self:T(self.lid.."_GetManagedGrpID - Requested Group is not alive!")
return 0,false,""
end
self:T(self.lid.."_GetManagedGrpID for "..Group:GetName())
local GID=0
local Outcome=false
local CallSign="Ghost 1"
local nametocheck=Group:GetName()
local managedgrps=self.ManagedGrps or{}
for _,_managed in pairs(managedgrps)do
local managed=_managed
if managed.GroupName==nametocheck then
GID=managed.GID
Outcome=true
CallSign=managed.CallSign
end
end
return GID,Outcome,CallSign
end
function AWACS:_GetCallSign(Group,GID,IsPlayer)
self:T(self.lid.."_GetCallSign - GID "..tostring(GID))
if GID and type(GID)=="number"and GID>0 then
local managedgroup=self.ManagedGrps[GID]
self:T("Saved Callsign for TTS = "..tostring(managedgroup.CallSign))
return managedgroup.CallSign
end
local callsign="Ghost 1"
if Group and Group:IsAlive()then
callsign=Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations,self.callsignCustomFunc,self.callsignCustomArgs)
end
return callsign
end
function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
if not ShortCallsign or ShortCallsign==false then
self.callsignshort=false
else
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)
self:T(self.lid.."_UpdateContactFromCluster CID="..CID)
local existingcontact=self.Contacts:PullByID(CID)
local ContactTable=existingcontact.Cluster.Contacts or{}
local function GetFirstAliveContact(table)
for _,_contact in pairs(table)do
local contact=_contact
if contact and contact.group and contact.group:IsAlive()then
return contact
end
end
return nil
end
local NewContact=GetFirstAliveContact(ContactTable)
if NewContact then
existingcontact.Contact=NewContact
self.Contacts:Push(existingcontact,existingcontact.CID)
end
return self
end
function AWACS:_CheckMerges()
self:T(self.lid.."_CheckMerges")
for _id,_pilot in pairs(self.ManagedGrps)do
local pilot=_pilot
if pilot.Group and pilot.Group:IsAlive()then
local ppos=pilot.Group:GetCoordinate()
local pcallsign=pilot.CallSign
self:T(self.lid.."Checking for "..pcallsign)
if ppos then
self.Contacts:ForEach(
function(Contact)
local contact=Contact
local cpos=contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate()
local dist=ppos:Get2DDistance(cpos)
local distnm=UTILS.Round(UTILS.MetersToNM(dist),0)
if(pilot.IsPlayer or self.debug)and distnm<=5 then
self:T(self.lid.."Merged")
self:_MergedCall(_id)
end
if(pilot.IsPlayer or self.debug)and distnm>5 and distnm<=self.ThreatDistance then
self:_ThreatRangeCall(_id,Contact)
end
if(pilot.IsPlayer or self.debug)and distnm>self.ThreatDistance and distnm<=self.MeldDistance then
self:_MeldRangeCall(_id,Contact)
end
if(pilot.IsPlayer or self.debug)and distnm>self.MeldDistance and distnm<=self.TacDistance then
self:_TACRangeCall(_id,Contact)
end
end
)
end
end
end
return self
end
function AWACS:_CleanUpContacts()
self:T(self.lid.."_CleanUpContacts")
if self.Contacts:Count()>0 then
local deadcontacts=FIFO:New()
self.Contacts:ForEach(
function(Contact)
local contact=Contact
if not contact.Contact.group:IsAlive()or contact.Target:IsDead()or contact.Target:IsDestroyed()or contact.Target:CountTargets()==0 then
deadcontacts:Push(contact,contact.CID)
self:T("DEAD contact CID="..contact.CID)
end
end
)
if deadcontacts:Count()>0 and(not self.NoGroupTags)then
self:T("DEAD count="..deadcontacts:Count())
deadcontacts:ForEach(
function(Contact)
local contact=Contact
local vanished=self.gettext:GetEntry("VANISHED",self.locale)
local vanishedtts=self.gettext:GetEntry("VANISHEDTTS",self.locale)
local text=string.format(vanishedtts,self.callsigntxt,contact.TargetGroupNaming)
local textScreen=string.format(vanished,self.callsigntxt,contact.TargetGroupNaming)
self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true)
self.Contacts:PullByID(contact.CID)
end
)
end
if self.Contacts:Count()>0 then
self.Contacts:ForEach(
function(Contact)
local contact=Contact
self:_UpdateContactFromCluster(contact.CID)
end
)
end
deadcontacts:Clear()
end
return self
end
function AWACS:_GetIdlePilots()
self:T(self.lid.."_GetIdlePilots")
local AIPilots={}
local HumanPilots={}
for _name,_entry in pairs(self.ManagedGrps)do
local entry=_entry
self:T("Looking at entry "..entry.GID.." Name "..entry.GroupName)
local managedtask=self:_ReadAssignedTaskFromGID(entry.GID)
local overridetask=false
if managedtask then
self:T("Current task = "..(managedtask.ToDo or"Unknown"))
if managedtask.ToDo==AWACS.TaskDescription.ANCHOR then
overridetask=true
end
end
if entry.IsAI then
if entry.FlightGroup:IsAirborne()and((not entry.HasAssignedTask)or overridetask)then
self:T("Adding AI with Callsign: "..entry.CallSign)
AIPilots[#AIPilots+1]=_entry
end
elseif entry.IsPlayer and(not entry.Blocked)and(not entry.Group:IsHelicopter())then
if(not entry.HasAssignedTask)or overridetask then
local TNow=timer.getTime()
if entry.LastTasking and(TNow-entry.LastTasking>self.ReassignTime)then
self:T("Adding Human with Callsign: "..entry.CallSign)
HumanPilots[#HumanPilots+1]=_entry
end
end
end
end
return AIPilots,HumanPilots
end
function AWACS:_TargetSelectionProcess(Untargeted)
self:T(self.lid.."_TargetSelectionProcess")
local maxtargets=3
local contactstable=self.Contacts:GetDataTable()
local targettable=FIFO:New()
local sortedtargets=FIFO:New()
local prefiltered=FIFO:New()
local HaveTargets=false
self:T(self.lid.."Initial count: "..self.Contacts:Count())
if Untargeted then
self.Contacts:ForEach(
function(Contact)
local contact=Contact
if contact.Contact.group:IsAlive()and(contact.Status==AWACS.TaskStatus.IDLE or contact.Status==AWACS.TaskStatus.UNASSIGNED)then
if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then
if not(contact.IFF==AWACS.IFF.FRIENDLY or contact.IFF==AWACS.IFF.NEUTRAL)then
prefiltered:Push(contact,contact.CID)
end
else
prefiltered:Push(contact,contact.CID)
end
end
end
)
contactstable=prefiltered:GetDataTable()
self:T(self.lid.."Untargeted: "..prefiltered:Count())
end
for _,_contact in pairs(contactstable)do
local contact=_contact
local checked=false
local contactname=contact.TargetGroupNaming or"ZETA"
local typename=contact.ReportingName or"Unknown"
self:T(self.lid..string.format("Looking at group %s type %s",contactname,typename))
local contactcoord=contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate()
local contactvec2=contactcoord:GetVec2()
if self.RejectZone then
local isinrejzone=self.RejectZone:IsVec2InZone(contactvec2)
if isinrejzone then
self:T(self.lid.."Across Border = YES - ignore")
checked=true
end
end
if not self.GCI then
local HVTCoordinate=self.OrbitZone:GetCoordinate()
local distance=UTILS.NMToMeters(200)
if contactcoord then
distance=HVTCoordinate:Get2DDistance(contactcoord)
end
self:T(self.lid.."HVT Distance = "..UTILS.Round(UTILS.MetersToNM(distance),0))
if UTILS.MetersToNM(distance)<=45 and not checked then
self:T(self.lid.."In HVT Distance = YES")
targettable:Push(contact,distance)
checked=true
end
end
local isinopszone=self.OpsZone:IsVec2InZone(contactvec2)
local distance=self.OpsZone:Get2DDistance(contactcoord)
if isinopszone and not checked then
self:T(self.lid.."In FEZ = YES")
targettable:Push(contact,distance)
checked=true
end
local isinopszone=self.ControlZone:IsVec2InZone(contactvec2)
if isinopszone and not checked then
self:T(self.lid.."In Radar Zone = YES")
local distance=self.AOCoordinate:Get2DDistance(contactcoord)
local AOdist=UTILS.Round(UTILS.MetersToNM(distance),0)
if not contactcoord.Heading then
contactcoord.Heading=self.intel:CalcClusterDirection(contact.Cluster)
end
local aspect=contactcoord:ToStringAspect(self.ControlZone:GetCoordinate())
local sizing=contact.Cluster.size or self.intel:ClusterCountUnits(contact.Cluster)or 1
sizing=math.fmod((sizing*0.1),1)
local AOdist2=(AOdist/2)*sizing
AOdist2=UTILS.Round((AOdist/2)+((AOdist/2)-AOdist2),0)
self:T(self.lid.."Aspect = "..aspect.." | Size = "..sizing)
if(AOdist2<75)or(aspect=="Hot")then
local text=string.format("In AO(Adj) dist = %d(%d) NM",AOdist,AOdist2)
self:T(self.lid..text)
targettable:Push(contact,distance)
checked=true
end
end
if self.BorderZone then
local isinborderzone=self.BorderZone:IsVec2InZone(contactvec2)
if isinborderzone and not checked then
self:T(self.lid.."In BorderZone = YES")
targettable:Push(contact,distance)
checked=true
end
end
end
self:T(self.lid.."Post filter count: "..targettable:Count())
if targettable:Count()>maxtargets then
local targets=targettable:GetSortedDataTable()
targettable:Clear()
for i=1,maxtargets do
targettable:Push(targets[i])
end
end
sortedtargets:Clear()
prefiltered:Clear()
if targettable:Count()>0 then
HaveTargets=true
end
return HaveTargets,targettable
end
function AWACS:_CreatePicture(AO,Callsign,GID,MaxEntries,IsGeneral)
self:T(self.lid.."_CreatePicture AO="..tostring(AO).." for "..Callsign.." GID "..GID)
local managedgroup=nil
local group=nil
local groupcoord=nil
if not IsGeneral then
managedgroup=self.ManagedGrps[GID]
group=managedgroup.Group
groupcoord=group:GetCoordinate()
end
local fifo=self.PictureAO
local maxentries=self.maxspeakentries or 3
if MaxEntries and MaxEntries>0 and MaxEntries<=3 then
maxentries=MaxEntries
end
local counter=0
if not AO then
end
local entries=fifo:GetSize()
if entries<maxentries then maxentries=entries end
local text=""
local textScreen=""
while counter<maxentries do
counter=counter+1
local contact=fifo:Pull()
self:T({contact})
if contact and contact.Contact.group and contact.Contact.group:IsAlive()then
local coordinate=contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate()
if not coordinate then
self:E(self.lid.."NO Coordinate for this cluster! CID="..contact.CID)
self:E({contact})
break
end
if not coordinate.Heading then
coordinate.Heading=contact.Contact.heading or contact.Contact.group:GetHeading()
end
local refBRAA=""
local refBRAATTS=""
if self.NoGroupTags then
local grouptxt=self.gettext:GetEntry("GROUPCAP",self.locale)
text=grouptxt.."."
textScreen=grouptxt..","
else
local grouptxt=self.gettext:GetEntry("GROUP",self.locale)
text=contact.TargetGroupNaming.." "..grouptxt.."."
textScreen=contact.TargetGroupNaming.." "..grouptxt..","
end
if IsGeneral or not self.PlayerGuidance then
local milestxt=self.gettext:GetEntry("MILES",self.locale)
local thsdtxt=self.gettext:GetEntry("THOUSAND",self.locale)
refBRAA=self:_ToStringBULLS(coordinate)
refBRAATTS=self:_ToStringBULLS(coordinate,false,true)
local alt=contact.Contact.group:GetAltitude()or 8000
alt=UTILS.Round(UTILS.MetersToFeet(alt)/1000,0)
text=string.format("%s %s %s, %d %s.",text,refBRAATTS,milestxt,alt,thsdtxt)
textScreen=string.format("%s %s %s, %d %s.",textScreen,refBRAA,milestxt,alt,thsdtxt)
else
refBRAA=coordinate:ToStringBRAANATO(groupcoord,true,true)
refBRAATTS=string.gsub(refBRAA,"BRAA","brah")
refBRAATTS=string.gsub(refBRAATTS,"BRA","brah")
if self.PathToGoogleKey then
refBRAATTS=coordinate:ToStringBRAANATO(groupcoord,true,true,true,false,true)
end
if contact.IFF~=AWACS.IFF.BOGEY then
local bogey=self.gettext:GetEntry("BOGEY",self.locale)
refBRAA=string.gsub(refBRAA,bogey,contact.IFF)
refBRAATTS=string.gsub(refBRAATTS,bogey,contact.IFF)
end
text=text.." "..refBRAATTS
textScreen=textScreen.." "..refBRAA
end
local aspect=""
local size=contact.Contact.group:CountAliveUnits()
local threatsize,threatsizetext=self:_GetBlurredSize(size)
if threatsize>1 then
text=text.." "..threatsizetext.."."
textScreen=textScreen.." "..threatsizetext.."."
end
if contact.EngagementTag then
text=text.." "..contact.EngagementTag
textScreen=textScreen.." "..contact.EngagementTag
end
local RadioEntry_IsGroup=false
local RadioEntry_ToScreen=self.debug
if managedgroup and not IsGeneral then
RadioEntry_IsGroup=managedgroup.IsPlayer
RadioEntry_ToScreen=managedgroup.IsPlayer
end
self:_NewRadioEntry(text,textScreen,GID,RadioEntry_IsGroup,RadioEntry_ToScreen,true,false)
end
end
fifo:Clear()
return self
end
function AWACS:_CreateBogeyDope(Callsign,GID,Tactical)
self:T(self.lid.."_CreateBogeyDope for "..Callsign.." GID "..GID)
local managedgroup=self.ManagedGrps[GID]
local group=managedgroup.Group
local groupcoord=group:GetCoordinate()
local fifo=self.ContactsAO
local maxentries=1
local counter=0
local entries=fifo:GetSize()
if entries<maxentries then maxentries=entries end
local sortedIDs=fifo:GetIDStackSorted()
while counter<maxentries do
counter=counter+1
local contact=fifo:PullByID(sortedIDs[counter])
self:T({contact})
local position=contact.Cluster.coordinate or contact.Contact.position
if contact and position then
local tag=contact.TargetGroupNaming
local reportingname=contact.ReportingName
self:_AnnounceContact(contact,false,group,true,tag,false,reportingname,Tactical)
end
end
fifo:Clear()
return self
end
function AWACS:_Picture(Group,IsGeneral)
self:T(self.lid.."_Picture")
local text=""
local textScreen=text
local general=IsGeneral
local GID,Outcome,gcallsign=self:_GetManagedGrpID(Group)
if general then
local allst=self.gettext:GetEntry("ALLSTATIONS",self.locale)
gcallsign=allst
end
if Group and Outcome then
general=false
end
if not self.intel then
local picclean=self.gettext:GetEntry("PICCLEAN",self.locale)
text=string.format(picclean,gcallsign,self.callsigntxt)
textScreen=text
self:_NewRadioEntry(text,text,GID,false,true,true,false)
return self
end
if Outcome or general then
local contactstable=self.Contacts:GetDataTable()
for _,_contact in pairs(contactstable)do
local contact=_contact
local coordVec2=contact.Contact.position:GetVec2()
if self.OpsZone:IsVec2InZone(coordVec2)then
self.PictureAO:Push(contact)
elseif self.OrbitZone and self.OrbitZone:IsVec2InZone(coordVec2)then
self.PictureAO:Push(contact)
elseif self.ControlZone:IsVec2InZone(coordVec2)then
local distance=math.floor((contact.Contact.position:Get2DDistance(self.ControlZone:GetCoordinate())/1000)+1)
self.PictureEWR:Push(contact,distance)
end
end
local clustersAO=self.PictureAO:GetSize()
local clustersEWR=self.PictureEWR:GetSize()
if clustersAO<3 and clustersEWR>0 then
local IDstack=self.PictureEWR:GetSortedDataTable()
local weneed=3-clustersAO
self:T(string.format("Picture - adding %d/%d contacts from EWR",weneed,clustersEWR))
if weneed>clustersEWR then
weneed=clustersEWR
end
for i=1,weneed do
self.PictureAO:Push(IDstack[i])
end
end
clustersAO=self.PictureAO:GetSize()
if clustersAO==0 and clustersEWR==0 then
local picclean=self.gettext:GetEntry("PICCLEAN",self.locale)
text=string.format(picclean,gcallsign,self.callsigntxt)
textScreen=text
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
else
if clustersAO>0 then
local picture=self.gettext:GetEntry("PICTURE",self.locale)
text=string.format("%s, %s. %s. ",gcallsign,self.callsigntxt,picture)
textScreen=string.format("%s, %s. %s. ",gcallsign,self.callsigntxt,picture)
local onetxt=self.gettext:GetEntry("ONE",self.locale)
local grptxt=self.gettext:GetEntry("GROUP",self.locale)
local groupstxt=self.gettext:GetEntry("GROUPMULTI",self.locale)
if clustersAO==1 then
text=string.format("%s%s %s. ",text,onetxt,grptxt)
textScreen=string.format("%s%s %s.\n",textScreen,onetxt,grptxt)
else
text=string.format("%s%d %s. ",text,clustersAO,groupstxt)
textScreen=string.format("%s%d %s.\n",textScreen,clustersAO,groupstxt)
end
self:_NewRadioEntry(text,textScreen,GID,Outcome,true,true,false)
self:_CreatePicture(true,gcallsign,GID,3,general)
self.PictureAO:Clear()
self.PictureEWR:Clear()
end
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,gcallsign,self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_BogeyDope(Group,Tactical)
self:T(self.lid.."_BogeyDope")
local text=""
local textScreen=""
local GID,Outcome=self:_GetManagedGrpID(Group)
local gcallsign=self:_GetCallSign(Group,GID)or"Ghost 1"
if not self.intel then
local clean=self.gettext:GetEntry("CLEAN",self.locale)
text=string.format(clean,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,0,false,true,true,false,true,Tactical)
return self
end
if Outcome then
local managedgroup=self.ManagedGrps[GID]
local pilotgroup=managedgroup.Group
local pilotcoord=managedgroup.Group:GetCoordinate()
local contactstable=self.Contacts:GetDataTable()
for _,_contact in pairs(contactstable)do
local managedcontact=_contact
local contactposition=managedcontact.Cluster.coordinate or managedcontact.Contact.position
local coordVec2=contactposition:GetVec2()
local dist=pilotcoord:Get2DDistance(contactposition)
if self.ControlZone:IsVec2InZone(coordVec2)then
self.ContactsAO:Push(managedcontact,dist)
elseif self.BorderZone and self.BorderZone:IsVec2InZone(coordVec2)then
self.ContactsAO:Push(managedcontact,dist)
else
if self.OrbitZone then
local distance=contactposition:Get2DDistance(self.OrbitZone:GetCoordinate())
if(distance<=UTILS.NMToMeters(45))then
self.ContactsAO:Push(managedcontact,distance)
end
end
end
end
local contactsAO=self.ContactsAO:GetSize()
if contactsAO==0 then
local clean=self.gettext:GetEntry("CLEAN",self.locale)
text=string.format(clean,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,Outcome,true,false,true,Tactical)
else
if contactsAO>0 then
local dope=self.gettext:GetEntry("DOPE",self.locale)
text=string.format(dope,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
textScreen=string.format(dope,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
local onetxt=self.gettext:GetEntry("ONE",self.locale)
local grptxt=self.gettext:GetEntry("GROUP",self.locale)
local groupstxt=self.gettext:GetEntry("GROUPMULTI",self.locale)
if contactsAO==1 then
text=string.format("%s%s %s. ",text,onetxt,grptxt)
textScreen=string.format("%s%s %s.\n",textScreen,onetxt,grptxt)
else
text=string.format("%s%d %s. ",text,contactsAO,groupstxt)
textScreen=string.format("%s%d %s.\n",textScreen,contactsAO,groupstxt)
end
self:_NewRadioEntry(text,textScreen,GID,Outcome,true,true,false,true,Tactical)
self:_CreateBogeyDope(self:_GetCallSign(Group,GID)or"Ghost 1",GID,Tactical)
end
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,Tactical)
end
return self
end
function AWACS:_ShowAwacsInfo(Group)
self:T(self.lid.."_ShowAwacsInfo")
local report=REPORT:New("Info")
local STN=self.STN
report:Add("====================")
report:Add(string.format("AWACS %s",self.callsigntxt))
report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation)))
if STN then
report:Add(string.format("Link-16 STN: %s",STN))
end
report:Add(string.format("Bulls Alias: %s",self.AOName))
report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM()))
report:Add("====================")
report:Add(string.format("Assignment Distance: %d NM",self.maxassigndistance))
report:Add(string.format("TAC Distance: %d NM",self.TacDistance))
report:Add(string.format("MELD Distance: %d NM",self.MeldDistance))
report:Add(string.format("THREAT Distance: %d NM",self.ThreatDistance))
report:Add("====================")
report:Add(string.format("ROE/ROT: %s, %s",self.AwacsROE,self.AwacsROT))
MESSAGE:New(report:Text(),45,"AWACS"):ToGroup(Group)
return self
end
function AWACS:_VID(Group,Declaration)
self:T(self.lid.."_VID")
local GID,Outcome,Callsign=self:_GetManagedGrpID(Group)
local text=""
local TextTTS=""
if Outcome then
local managedgroup=self.ManagedGrps[GID]
local group=managedgroup.Group
local position=group:GetCoordinate()
local radius=UTILS.NMToMeters(self.DeclareRadius)or UTILS.NMToMeters(5)
local TID=managedgroup.CurrentTask or 0
if TID>0 then
local task=self.ManagedTasks:ReadByID(TID)
if task.ToDo~=AWACS.TaskDescription.VID then
return self
end
if task.Status~=AWACS.TaskStatus.ASSIGNED then
return self
end
local CID=task.Cluster.CID
local cluster=self.Contacts:ReadByID(CID)
if cluster then
local gposition=cluster.Contact.group:GetCoordinate()
local cposition=gposition or cluster.Cluster.coordinate or cluster.Contact.position
local distance=cposition:Get2DDistance(position)
distance=UTILS.Round(distance,0)+1
if distance<=radius or self.debug then
self:T("Contact VID as "..Declaration)
cluster.IFF=Declaration
task.Status=AWACS.TaskStatus.SUCCESS
self.ManagedTasks:PullByID(TID)
self.ManagedTasks:Push(task,TID)
self.Contacts:PullByID(CID)
self.Contacts:Push(cluster,CID)
local vidpos=self.gettext:GetEntry("VIDPOS",self.locale)
text=string.format(vidpos,Callsign,self.callsigntxt,Declaration)
self:T(text)
else
self:T("Contact VID not close enough")
local vidneg=self.gettext:GetEntry("VIDNEG",self.locale)
text=string.format(vidneg,Callsign,self.callsigntxt)
self:T(text)
end
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
end
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_Declare(Group)
self:T(self.lid.."_Declare")
local GID,Outcome,Callsign=self:_GetManagedGrpID(Group)
local text=""
local TextTTS=""
if Outcome then
local managedgroup=self.ManagedGrps[GID]
local group=managedgroup.Group
local position=group:GetCoordinate()
local radius=UTILS.NMToMeters(self.DeclareRadius)or UTILS.NMToMeters(5)
local groupzone=ZONE_GROUP:New(group:GetName(),group,radius)
local Coalitions={"red","neutral"}
if self.coalition==coalition.side.NEUTRAL then
Coalitions={"red","blue"}
elseif self.coalition==coalition.side.RED then
Coalitions={"blue","neutral"}
end
local contactset=SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(Coalitions):FilterZones({groupzone}):FilterOnce()
local numbercontacts=contactset:CountAlive()or 0
local foundcontacts={}
if numbercontacts>0 then
contactset:ForEach(
function(airpl)
local distance=position:Get2DDistance(airpl:GetCoordinate())
distance=UTILS.Round(distance,0)+1
foundcontacts[distance]=airpl
end
,{}
)
for _dist,_contact in UTILS.spairs(foundcontacts)do
local distanz=_dist
local contact=_contact
local ccoalition=contact:GetCoalition()
local ctypename=contact:GetTypeName()
local ffneutral=self.gettext:GetEntry("FFNEUTRAL",self.locale)
local fffriend=self.gettext:GetEntry("FFFRIEND",self.locale)
local ffhostile=self.gettext:GetEntry("FFHOSTILE",self.locale)
local ffspades=self.gettext:GetEntry("FFSPADES",self.locale)
local friendorfoe=ffneutral
if self.self.ModernEra then
if ccoalition==self.coalition then
friendorfoe=fffriend
elseif ccoalition==coalition.side.NEUTRAL then
friendorfoe=ffneutral
elseif ccoalition~=self.coalition then
friendorfoe=ffhostile
end
else
friendorfoe=ffspades
end
self:T(string.format("Distance %d ContactName %s Coalition %d (%s) TypeName %s",distanz,contact:GetName(),ccoalition,friendorfoe,ctypename))
text=string.format("%s. %s. %s.",Callsign,self.callsigntxt,friendorfoe)
TextTTS=text
if self.ModernEra then
text=string.format("%s %s.",text,ctypename)
end
break
end
else
local ffclean=self.gettext:GetEntry("FFCLEAN",self.locale)
text=string.format("%s. %s. %s.",Callsign,self.callsigntxt,ffclean)
TextTTS=text
end
self:_NewRadioEntry(TextTTS,text,GID,Outcome,true,true,false,true)
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_Commit(Group)
self:T(self.lid.."_Commit")
local GID,Outcome=self:_GetManagedGrpID(Group)
local text=""
if Outcome then
local Pilot=self.ManagedGrps[GID]
local currtaskid=Pilot.CurrentTask
local managedtask=self.ManagedTasks:ReadByID(currtaskid)
self:T(string.format("TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status))
if managedtask then
if managedtask.Status==AWACS.TaskStatus.REQUESTED then
managedtask=self.ManagedTasks:PullByID(currtaskid)
managedtask.Status=AWACS.TaskStatus.ASSIGNED
self.ManagedTasks:Push(managedtask,currtaskid)
self:T(string.format("COMMITTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status))
Pilot.HasAssignedTask=true
Pilot.CurrentTask=currtaskid
self.ManagedGrps[GID]=Pilot
local copy=self.gettext:GetEntry("COPY",self.locale)
local targetedby=self.gettext:GetEntry("TARGETEDBY",self.locale)
text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
local EngagementTag=string.format(targetedby,Pilot.CallSign)
self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.ASSIGNED)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
else
self:E(self.lid.."Cannot find REQUESTED managed task with TID="..currtaskid.." for GID="..GID)
end
else
self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID)
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_Judy(Group)
self:T(self.lid.."_Judy")
local GID,Outcome=self:_GetManagedGrpID(Group)
local text=""
if Outcome then
local Pilot=self.ManagedGrps[GID]
local currtaskid=Pilot.CurrentTask
local managedtask=self.ManagedTasks:ReadByID(currtaskid)
if managedtask then
if managedtask.Status==AWACS.TaskStatus.REQUESTED or managedtask.Status==AWACS.TaskStatus.UNASSIGNED then
managedtask=self.ManagedTasks:PullByID(currtaskid)
managedtask.Status=AWACS.TaskStatus.ASSIGNED
self.ManagedTasks:Push(managedtask,currtaskid)
local copy=self.gettext:GetEntry("COPY",self.locale)
local targetedby=self.gettext:GetEntry("TARGETEDBY",self.locale)
text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
local EngagementTag=string.format(targetedby,Pilot.CallSign)
self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.ASSIGNED)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
else
self:E(self.lid.."Cannot find REQUESTED or UNASSIGNED managed task with TID="..currtaskid.." for GID="..GID)
end
else
self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID)
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_Unable(Group)
self:T(self.lid.."_Unable")
local GID,Outcome=self:_GetManagedGrpID(Group)
local text=""
if Outcome then
local Pilot=self.ManagedGrps[GID]
local currtaskid=Pilot.CurrentTask
local managedtask=self.ManagedTasks:ReadByID(currtaskid)
self:T(string.format("UNABLE for TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status))
if managedtask then
if managedtask.Status==AWACS.TaskStatus.REQUESTED then
managedtask=self.ManagedTasks:PullByID(currtaskid)
managedtask.IsUnassigned=true
managedtask.Status=AWACS.TaskStatus.FAILED
self.ManagedTasks:Push(managedtask,currtaskid)
self:T(string.format("REJECTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status))
Pilot.HasAssignedTask=false
Pilot.CurrentTask=0
Pilot.LastTasking=timer.getTime()
self.ManagedGrps[GID]=Pilot
local copy=self.gettext:GetEntry("COPY",self.locale)
text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
local EngagementTag=""
self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.UNASSIGNED)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
else
self:E(self.lid.."Cannot find REQUESTED managed task with TID="..currtaskid.." for GID="..GID)
end
else
self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID)
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_TaskAbort(Group)
self:T(self.lid.."_TaskAbort")
local Outcome,GID=self:_GetGIDFromGroupOrName(Group)
local text=""
if Outcome then
local Pilot=self.ManagedGrps[GID]
self:T({Pilot})
local currtaskid=Pilot.CurrentTask
local managedtask=self.ManagedTasks:ReadByID(currtaskid)
if managedtask then
self:T(string.format("ABORT for TID %d(%d) | ToDo %s | Status %s",currtaskid,managedtask.TID,managedtask.ToDo,managedtask.Status))
if managedtask.Status==AWACS.TaskStatus.ASSIGNED then
managedtask=self.ManagedTasks:PullByID(currtaskid)
managedtask.Status=AWACS.TaskStatus.FAILED
managedtask.IsUnassigned=true
self.ManagedTasks:Push(managedtask,currtaskid)
self:T(string.format("ABORTED - TID %d(%d) for GID %d | ToDo %s | Status %s",currtaskid,GID,managedtask.TID,managedtask.ToDo,managedtask.Status))
Pilot.HasAssignedTask=false
Pilot.CurrentTask=0
Pilot.LastTasking=timer.getTime()
self.ManagedGrps[GID]=Pilot
local copy=self.gettext:GetEntry("COPY",self.locale)
text=string.format(copy,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
local EngagementTag=""
self:_UpdateContactEngagementTag(Pilot.ContactCID,EngagementTag,false,false,AWACS.TaskStatus.UNASSIGNED)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true)
else
self:E(self.lid.."Cannot find ASSIGNED managed task with TID="..currtaskid.." for GID="..GID)
end
else
self:E(self.lid.."Cannot find managed task with TID="..currtaskid.." for GID="..GID)
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_Showtask(Group)
self:T(self.lid.."_Showtask")
local GID,Outcome,Callsign=self:_GetManagedGrpID(Group)
local text=""
if Outcome then
local managedgroup=self.ManagedGrps[GID]
if managedgroup.IsPlayer then
if managedgroup.CurrentTask>0 and self.ManagedTasks:HasUniqueID(managedgroup.CurrentTask)then
local currenttask=self.ManagedTasks:ReadByID(managedgroup.CurrentTask)
if currenttask then
local status=currenttask.Status
local targettype=currenttask.Target:GetCategory()
local targetstatus=currenttask.Target:GetState()
local ToDo=currenttask.ToDo
local description=currenttask.ScreenText
local descTTS=currenttask.ScreenText
local callsign=Callsign
if self.debug then
local taskreport=REPORT:New("AWACS Tasking Display")
taskreport:Add("===============")
taskreport:Add(string.format("Task for Callsign: %s",Callsign))
taskreport:Add(string.format("Task: %s with Status: %s",ToDo,status))
taskreport:Add(string.format("Target of Type: %s",targettype))
taskreport:Add(string.format("Target in State: %s",targetstatus))
taskreport:Add("===============")
self:I(taskreport:Text())
end
local pposition=managedgroup.Group:GetCoordinate()or managedgroup.LastKnownPosition
if currenttask.ToDo==AWACS.TaskDescription.INTERCEPT or currenttask.ToDo==AWACS.TaskDescription.VID then
local targetpos=currenttask.Target:GetCoordinate()
if pposition and targetpos then
local alti=currenttask.Cluster.altitude or currenttask.Contact.altitude or currenttask.Contact.group:GetAltitude()
local direction,direcTTS=self:_ToStringBRA(pposition,targetpos,alti)
description=description.."\nBRA "..direction
descTTS=descTTS..";BRA "..direcTTS
end
elseif currenttask.ToDo==AWACS.TaskDescription.ANCHOR or currenttask.ToDo==AWACS.TaskDescription.REANCHOR then
local targetpos=currenttask.Target:GetCoordinate()
local direction,direcTTS=self:_ToStringBR(pposition,targetpos)
description=description.."\nBR "..direction
descTTS=descTTS..";BR "..direcTTS
end
local statustxt=self.gettext:GetEntry("STATUS",self.locale)
local text=string.format("%s\n%s %s",description,statustxt,status)
local ttstext=string.format("%s. %s. %s",managedgroup.CallSign,self.callsigntxt,descTTS)
ttstext=string.gsub(ttstext,"\\n",";")
ttstext=string.gsub(ttstext,"VID","V I D")
self:_NewRadioEntry(ttstext,text,GID,true,true,false,false,true)
end
end
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:_NewRadioEntry(text,text,GID,Outcome,true,true,false)
end
return self
end
function AWACS:_CheckIn(Group)
self:T(self.lid.."_CheckIn "..Group:GetName())
local GID,Outcome=self:_GetManagedGrpID(Group)
local text=""
local textTTS=""
if not Outcome then
self.ManagedGrpID=self.ManagedGrpID+1
local managedgroup={}
managedgroup.Group=Group
managedgroup.GroupName=Group:GetName()
managedgroup.IsPlayer=true
managedgroup.IsAI=false
managedgroup.CallSign=self:_GetCallSign(Group,GID,true)or"Ghost 1"
managedgroup.CurrentAuftrag=0
managedgroup.CurrentTask=0
managedgroup.HasAssignedTask=true
managedgroup.Blocked=true
managedgroup.GID=self.ManagedGrpID
managedgroup.LastKnownPosition=Group:GetCoordinate()
managedgroup.LastTasking=timer.getTime()
GID=managedgroup.GID
self.ManagedGrps[self.ManagedGrpID]=managedgroup
local alphacheckbulls=self:_ToStringBULLS(Group:GetCoordinate())
local alphacheckbullstts=self:_ToStringBULLS(Group:GetCoordinate(),false,true)
local alpha=self.gettext:GetEntry("ALPHACHECK",self.locale)
text=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls)
textTTS=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbullstts)
self:__CheckedIn(1,managedgroup.GID)
if self.PlayerStationName then
self:__AssignAnchor(5,managedgroup.GID,true,self.PlayerStationName)
else
self:__AssignAnchor(5,managedgroup.GID)
end
elseif self.AwacsFG then
local nocheckin=self.gettext:GetEntry("ALREADYCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
textTTS=text
end
self:_NewRadioEntry(textTTS,text,GID,Outcome,true,true,false)
return self
end
function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr)
self:T(self.lid.."_CheckInAI "..Group:GetName().." to Auftrag Nr "..AuftragsNr)
local GID,Outcome=self:_GetManagedGrpID(Group)
local text=""
if not Outcome then
self.ManagedGrpID=self.ManagedGrpID+1
local managedgroup={}
managedgroup.Group=Group
managedgroup.GroupName=Group:GetName()
managedgroup.FlightGroup=FlightGroup
managedgroup.IsPlayer=false
managedgroup.IsAI=true
local callsignstring=UTILS.GetCallsignName(self.AICAPCAllName)
if self.callsignTranslations and self.callsignTranslations[callsignstring]then
callsignstring=self.callsignTranslations[callsignstring]
end
local callsignmajor=math.fmod(self.AICAPCAllNumber,9)
local callsign=string.format("%s %d 1",callsignstring,callsignmajor)
if self.callsignshort then
callsign=string.format("%s %d",callsignstring,callsignmajor)
end
self:T("Assigned Callsign: "..callsign)
managedgroup.CallSign=callsign
managedgroup.CurrentAuftrag=AuftragsNr
managedgroup.HasAssignedTask=false
managedgroup.GID=self.ManagedGrpID
managedgroup.LastKnownPosition=Group:GetCoordinate()
self.ManagedGrps[self.ManagedGrpID]=managedgroup
FlightGroup:SetDefaultRadio(self.Frequency,self.Modulation,false)
FlightGroup:SwitchRadio(self.Frequency,self.Modulation)
local CAPVoice=self.CAPVoice
if self.PathToGoogleKey then
CAPVoice=self.CapVoices[math.floor(math.random(1,10))]
end
FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,CAPVoice,self.Port,self.PathToGoogleKey,"FLIGHT",1)
local checkai=self.gettext:GetEntry("CHECKINAI",self.locale)
text=string.format(checkai,self.callsigntxt,managedgroup.CallSign,self.CAPTimeOnStation,self.AOName)
self:_NewRadioEntry(text,text,managedgroup.GID,Outcome,false,true,true)
local alphacheckbulls=self:_ToStringBULLS(Group:GetCoordinate(),false,true)
local alpha=self.gettext:GetEntry("ALPHACHECK",self.locale)
text=string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls)
self:__CheckedIn(1,managedgroup.GID)
local AW=FlightGroup.legion
if AW.HasOwnStation then
self:__AssignAnchor(5,managedgroup.GID,AW.HasOwnStation,AW.StationName)
else
self:__AssignAnchor(5,managedgroup.GID)
end
else
local nocheckin=self.gettext:GetEntry("ALREADYCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
end
self:_NewRadioEntry(text,text,GID,Outcome,false,true,false)
return self
end
function AWACS:_CheckOut(Group,GID,dead)
self:T(self.lid.."_CheckOut")
local GID,Outcome=self:_GetManagedGrpID(Group)
local text=""
if Outcome then
local safeflight=self.gettext:GetEntry("SAFEFLIGHT",self.locale)
text=string.format(safeflight,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
self:T(text)
local managedgroup=self.ManagedGrps[GID]
local Stack=managedgroup.AnchorStackNo
local Angels=managedgroup.AnchorStackAngels
local GroupName=managedgroup.GroupName
if managedgroup.IsPlayer then
if self.clientmenus:HasUniqueID(GroupName)then
local menus=self.clientmenus:PullByID(GroupName)
menus.basemenu:Remove()
if self.TacticalSubscribers[GroupName]then
local Freq=self.TacticalSubscribers[GroupName]
self.TacticalFrequencies[Freq]=Freq
self.TacticalSubscribers[GroupName]=nil
end
end
end
if managedgroup.CurrentTask and managedgroup.CurrentTask>0 then
self.ManagedTasks:PullByID(managedgroup.CurrentTask)
self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false)
end
self.ManagedGrps[GID]=nil
self:__CheckedOut(1,GID,Stack,Angels)
else
if not dead then
local nocheckin=self.gettext:GetEntry("NOTCHECKEDIN",self.locale)
text=string.format(nocheckin,self:_GetCallSign(Group,GID)or"Ghost 1",self.callsigntxt)
end
end
if not dead then
self:_NewRadioEntry(text,text,GID,Outcome,false,true,false)
end
return self
end
function AWACS:_SetClientMenus()
self:T(self.lid.."_SetClientMenus")
local clientset=self.clientset
local aliveset=clientset:GetSetObjects()or{}
local clientcount=0
local clientcheckedin=0
for _,_group in pairs(aliveset)do
local grp=_group
local cgrp=grp:GetGroup()
local cgrpname=nil
if cgrp and cgrp:IsAlive()then
cgrpname=cgrp:GetName()
self:T(cgrpname)
end
if self.MenuStrict then
if cgrp and cgrp:IsAlive()then
clientcount=clientcount+1
local GID,checkedin=self:_GetManagedGrpID(cgrp)
if checkedin then
clientcheckedin=clientcheckedin+1
local hasclientmenu=self.clientmenus:ReadByID(cgrpname)
local basemenu=hasclientmenu.basemenu
if hasclientmenu and(not hasclientmenu.menuset)then
self:T(self.lid.."Setting Menus for "..cgrpname)
basemenu:RemoveSubMenus()
local bogeydope=MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp)
local picture=MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp)
local declare=MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp)
local tasking=MENU_GROUP:New(cgrp,"Tasking",basemenu)
local showtask=MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp)
local commit
local unable
local abort
if self.PlayerCapAssignment then
commit=MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp)
unable=MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp)
abort=MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp)
end
if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then
local vid=MENU_GROUP:New(cgrp,"VID as",tasking)
local hostile=MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY)
local neutral=MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL)
local friendly=MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY)
end
local tactical
if self.TacticalMenu then
tactical=MENU_GROUP:New(cgrp,"Tactical Radio",basemenu)
if self.TacticalSubscribers[cgrpname]then
local entry=MENU_GROUP_COMMAND:New(cgrp,"Unsubscribe",tactical,self._UnsubScribeTactRadio,self,cgrp)
else
for _,_freq in UTILS.spairs(self.TacticalFrequencies)do
local modu=UTILS.GetModulationName(self.TacticalModulation)
local text=string.format("Subscribe to %.3f %s",_freq,modu)
local entry=MENU_GROUP_COMMAND:New(cgrp,text,tactical,self._SubScribeTactRadio,self,cgrp,_freq)
end
end
end
local ainfo=MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp)
local checkout=MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp)
local menus={
groupname=cgrpname,
menuset=true,
basemenu=basemenu,
checkout=checkout,
picture=picture,
bogeydope=bogeydope,
declare=declare,
tasking=tasking,
showtask=showtask,
unable=unable,
abort=abort,
commit=commit,
tactical=tactical,
}
self.clientmenus:PullByID(cgrpname)
self.clientmenus:Push(menus,cgrpname)
end
elseif not self.clientmenus:HasUniqueID(cgrpname)then
local basemenu=MENU_GROUP:New(cgrp,self.Name,nil)
local checkin=MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp)
checkin:SetTag(cgrp:GetName())
basemenu:Refresh()
local menus={
groupname=cgrpname,
menuset=false,
basemenu=basemenu,
checkin=checkin,
}
self.clientmenus:Push(menus,cgrpname)
local GID,hasentry=self:_GetManagedGrpID(cgrp)
if hasentry then
self:_CheckOut(cgrp,GID,true)
end
end
end
else
if cgrp and cgrp:IsAlive()and not self.clientmenus:HasUniqueID(cgrpname)then
local basemenu=MENU_GROUP:New(cgrp,self.Name,nil)
local picture=MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp)
local bogeydope=MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp)
local declare=MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp)
local tasking=MENU_GROUP:New(cgrp,"Tasking",basemenu)
local showtask=MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp)
local commit=MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp)
local unable=MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp)
local abort=MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp)
if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then
local vid=MENU_GROUP:New(cgrp,"VID as",tasking)
local hostile=MENU_GROUP_COMMAND:New(cgrp,"Hostile",vid,self._VID,self,cgrp,AWACS.IFF.ENEMY)
local neutral=MENU_GROUP_COMMAND:New(cgrp,"Neutral",vid,self._VID,self,cgrp,AWACS.IFF.NEUTRAL)
local friendly=MENU_GROUP_COMMAND:New(cgrp,"Friendly",vid,self._VID,self,cgrp,AWACS.IFF.FRIENDLY)
end
local ainfo=MENU_GROUP_COMMAND:New(cgrp,"Awacs Info",basemenu,self._ShowAwacsInfo,self,cgrp)
local checkin=MENU_GROUP_COMMAND:New(cgrp,"Check In",basemenu,self._CheckIn,self,cgrp)
local checkout=MENU_GROUP_COMMAND:New(cgrp,"Check Out",basemenu,self._CheckOut,self,cgrp)
basemenu:Refresh()
local menus={
groupname=cgrpname,
menuset=true,
basemenu=basemenu,
checkin=checkin,
checkout=checkout,
picture=picture,
bogeydope=bogeydope,
declare=declare,
showtask=showtask,
tasking=tasking,
unable=unable,
abort=abort,
commit=commit,
}
self.clientmenus:Push(menus,cgrpname)
end
end
end
self.MonitoringData.Players=clientcount or 0
self.MonitoringData.PlayersCheckedin=clientcheckedin or 0
return self
end
function AWACS:_DeleteAnchorStackFromMarker(Name,Coord)
self:T(self.lid.."_DeleteAnchorStackFromMarker")
if self.AnchorStacks:HasUniqueID(Name)and self.PlayerStationName==Name then
local stack=self.AnchorStacks:ReadByID(Name)
local marker=stack.AnchorMarker
if stack.AnchorAssignedID:Count()==0 then
marker:Remove()
if self.debug then
stack.StationZone:UndrawZone()
end
self.AnchorStacks:PullByID(Name)
self.PlayerStationName=nil
else
if self.debug then
self:I(self.lid.."**** Cannot delete station, there are CAPs assigned!")
local text=marker:GetText()
marker:TextUpdate(text.."\nMarked for deletion")
end
end
end
return self
end
function AWACS:_MoveAnchorStackFromMarker(Name,Coord)
self:T(self.lid.."_MoveAnchorStackFromMarker")
if self.AnchorStacks:HasUniqueID(Name)and self.PlayerStationName==Name then
local station=self.AnchorStacks:PullByID(Name)
local stationtag=string.format("Station: %s\nCoordinate: %s",Name,Coord:ToStringLLDDM())
local marker=station.AnchorMarker
local zone=station.StationZone
if self.debug then
zone:UndrawZone()
end
local radius=self.StationZone:GetRadius()
if radius<10000 then radius=10000 end
station.StationZone=ZONE_RADIUS:New(Name,Coord:GetVec2(),radius)
marker:UpdateCoordinate(Coord)
marker:UpdateText(stationtag)
station.AnchorMarker=marker
if self.debug then
station.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
end
self.AnchorStacks:Push(station,Name)
end
return self
end
function AWACS:_CreateAnchorStackFromMarker(Name,Coord)
self:T(self.lid.."_CreateAnchorStackFromMarker")
local AnchorStackOne={}
AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels
AnchorStackOne.Anchors=FIFO:New()
AnchorStackOne.AnchorAssignedID=FIFO:New()
local newname=Name
for i=1,self.AnchorMaxStacks do
AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels)
end
local radius=self.StationZone:GetRadius()
if radius<10000 then radius=10000 end
AnchorStackOne.StationZone=ZONE_RADIUS:New(newname,Coord:GetVec2(),radius)
AnchorStackOne.StationZoneCoordinate=Coord
AnchorStackOne.StationZoneCoordinateText=Coord:ToStringLLDDM()
AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
self.AnchorStacks:Push(AnchorStackOne,newname)
self.PlayerStationName=newname
return self
end
function AWACS:_CreateAnchorStack()
self:T(self.lid.."_CreateAnchorStack")
local stackscreated=self.AnchorStacks:GetSize()
if stackscreated==self.AnchorMaxAnchors then
return false,0
end
local AnchorStackOne={}
AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels
AnchorStackOne.Anchors=FIFO:New()
AnchorStackOne.AnchorAssignedID=FIFO:New()
local newname=self.StationZone:GetName()
for i=1,self.AnchorMaxStacks do
AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels)
end
if stackscreated==0 then
local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1)
newname=self.StationZone:GetName().."-"..newsubname
AnchorStackOne.StationZone=self.StationZone
AnchorStackOne.StationZoneCoordinate=self.StationZone:GetCoordinate()
AnchorStackOne.StationZoneCoordinateText=self.StationZone:GetCoordinate():ToStringLLDDM()
AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
self.AnchorStacks:Push(AnchorStackOne,newname)
else
local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1)
newname=self.StationZone:GetName().."-"..newsubname
local anchorbasecoord=self.OpsZone:GetCoordinate()
local anchorradius=anchorbasecoord:Get2DDistance(self.StationZone:GetCoordinate())
local angel=self.StationZone:GetCoordinate():GetAngleDegrees(self.OpsZone:GetVec3())
self:T("Angel Radians= "..angel)
local turn=math.fmod(self.AnchorTurn*stackscreated,360)
if self.AnchorTurn<0 then turn=-turn end
local newanchorbasecoord=anchorbasecoord:Translate(anchorradius,turn+angel)
local radius=self.StationZone:GetRadius()
if radius<10000 then radius=10000 end
AnchorStackOne.StationZone=ZONE_RADIUS:New(newname,newanchorbasecoord:GetVec2(),radius)
AnchorStackOne.StationZoneCoordinate=newanchorbasecoord
AnchorStackOne.StationZoneCoordinateText=newanchorbasecoord:ToStringLLDDM()
AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
self.AnchorStacks:Push(AnchorStackOne,newname)
end
return true,self.AnchorStacks:GetSize()
end
function AWACS:_GetFreeAnchorStack()
self:T(self.lid.."_GetFreeAnchorStack")
local AnchorStackNo,Free=0,false
local availablestacks=self.AnchorStacks:GetPointerStack()or{}
for _id,_entry in pairs(availablestacks)do
local entry=_entry
local data=entry.data
if data.Anchors:IsNotEmpty()then
AnchorStackNo=_id
Free=true
break
end
end
if not Free then
local created,number=self:_CreateAnchorStack()
if created then
self:_GetFreeAnchorStack()
end
end
return AnchorStackNo,Free
end
function AWACS:_AssignAnchorToID(GID,HasOwnStation,StationName)
self:T(self.lid.."_AssignAnchorToID")
if not HasOwnStation then
local AnchorStackNo,Free=self:_GetFreeAnchorStack()
if Free then
local Anchor=self.AnchorStacks:PullByPointer(AnchorStackNo)
local freeangels=Anchor.Anchors:Pull()
Anchor.AnchorAssignedID:Push(GID)
self.AnchorStacks:Push(Anchor,Anchor.StationName)
self:T({Anchor,freeangels})
self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels)
else
self:E(self.lid.."Cannot assign free anchor stack to GID "..GID.." Trying again in 10secs.")
self:__AssignAnchor(10,GID)
end
else
local Anchor=self.AnchorStacks:PullByID(StationName)
local freeangels=Anchor.Anchors:Pull()or 25
Anchor.AnchorAssignedID:Push(GID)
self.AnchorStacks:Push(Anchor,StationName)
self:T({Anchor,freeangels})
local StackNo=self.AnchorStacks.stackbyid[StationName].pointer
self:__AssignedAnchor(5,GID,Anchor,StackNo,freeangels)
end
return self
end
function AWACS:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels)
local gid=GID or 0
local stack=AnchorStackNo or 0
local angels=Angels or 0
local debugstring=string.format("%s_RemoveIDFromAnchor for GID=%d Stack=%d Angels=%d",self.lid,gid,stack,angels)
self:T(debugstring)
if stack>0 and angels>0 then
local AnchorStackNo=AnchorStackNo or 1
local Anchor=self.AnchorStacks:ReadByPointer(AnchorStackNo)
local removedID=Anchor.AnchorAssignedID:PullByID(GID)
Anchor.Anchors:Push(Angels)
end
return self
end
function AWACS:_StartIntel(awacs)
self:T(self.lid.."_StartIntel")
if self.intelstarted then return self end
self.DetectionSet:AddGroup(awacs)
local intel=INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt)
intel:SetClusterAnalysis(true,false,false)
local acceptzoneset=SET_ZONE:New()
acceptzoneset:AddZone(self.ControlZone)
acceptzoneset:AddZone(self.OpsZone)
if not self.GCI then
self.OrbitZone:SetRadius(UTILS.NMToMeters(55))
acceptzoneset:AddZone(self.OrbitZone)
end
if self.BorderZone then
acceptzoneset:AddZone(self.BorderZone)
end
intel:SetAcceptZones(acceptzoneset)
if self.NoHelos then
intel:SetFilterCategory({Unit.Category.AIRPLANE})
else
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
end
local function NewCluster(Cluster)
self:__NewCluster(5,Cluster)
end
function intel:OnAfterNewCluster(From,Event,To,Cluster)
NewCluster(Cluster)
end
local function NewContact(Contact)
self:__NewContact(5,Contact)
end
function intel:OnAfterNewContact(From,Event,To,Contact)
NewContact(Contact)
end
local function LostContact(Contact)
self:__LostContact(5,Contact)
end
function intel:OnAfterLostContact(From,Event,To,Contact)
LostContact(Contact)
end
local function LostCluster(Cluster,Mission)
self:__LostCluster(5,Cluster,Mission)
end
function intel:OnAfterLostCluster(From,Event,To,Cluster,Mission)
LostCluster(Cluster,Mission)
end
self.intelstarted=true
intel.statusupdate=-30
intel:__Start(5)
self.intel=intel
return self
end
function AWACS:_GetBlurredSize(size)
self:T(self.lid.."_GetBlurredSize")
local threatsize=0
local blur=self.RadarBlur
local blurmin=100-blur
local blurmax=100+blur
local actblur=math.random(blurmin,blurmax)/100
threatsize=math.floor(size*actblur)
if threatsize==0 then threatsize=1 end
if threatsize then end
local threatsizetext=AWACS.Shipsize[1]
if threatsize==2 then
threatsizetext=AWACS.Shipsize[2]
elseif threatsize==3 then
threatsizetext=AWACS.Shipsize[3]
elseif threatsize>3 then
threatsizetext=AWACS.Shipsize[4]
end
return threatsize,threatsizetext
end
function AWACS:_GetThreatLevelText(threatlevel)
self:T(self.lid.."_GetThreatLevelText")
local threattext="GREEN"
if threatlevel<=AWACS.THREATLEVEL.GREEN then
threattext="GREEN"
elseif threatlevel<=AWACS.THREATLEVEL.AMBER then
threattext="AMBER"
else
threattext="RED"
end
return threattext
end
function AWACS:_ToStringBR(FromCoordinate,ToCoordinate)
self:T(self.lid.."_ToStringBR")
local BRText=""
local BRTextTTS=""
local DirectionVec3=FromCoordinate:GetDirectionVec3(ToCoordinate)
local AngleRadians=FromCoordinate:GetAngleRadians(DirectionVec3)
local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0)
local AngleDegText=string.format("%03d",AngleDegrees)
local AngleDegTextTTS=""
local zero=self.gettext:GetEntry("ZERO",self.locale)
local miles=self.gettext:GetEntry("MILES",self.locale)
AngleDegText=string.gsub(AngleDegText,"%d","%1 ")
AngleDegText=string.gsub(AngleDegText," $","")
AngleDegTextTTS=string.gsub(AngleDegText,"0",zero)
local Distance=ToCoordinate:Get2DDistance(FromCoordinate)
local distancenm=UTILS.Round(UTILS.MetersToNM(Distance),0)
BRText=string.format("%03d, %d %s",AngleDegrees,distancenm,miles)
BRTextTTS=string.format("%s, %d %s",AngleDegText,distancenm,miles)
if self.PathToGoogleKey then
BRTextTTS=string.format("%s, %d %s",AngleDegTextTTS,distancenm,miles)
end
self:T(BRText,BRTextTTS)
return BRText,BRTextTTS
end
function AWACS:_ToStringBRA(FromCoordinate,ToCoordinate,Altitude)
self:T(self.lid.."_ToStringBRA")
local BRText=""
local BRTextTTS=""
local altitude=UTILS.Round(UTILS.MetersToFeet(Altitude)/1000,0)
local DirectionVec3=FromCoordinate:GetDirectionVec3(ToCoordinate)
local AngleRadians=FromCoordinate:GetAngleRadians(DirectionVec3)
local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),0)
local AngleDegText=string.format("%03d",AngleDegrees)
AngleDegText=string.gsub(AngleDegText,"%d","%1 ")
AngleDegText=string.gsub(AngleDegText," $","")
local AngleDegTextTTS=string.gsub(AngleDegText,"0","zero")
local Distance=ToCoordinate:Get2DDistance(FromCoordinate)
local distancenm=UTILS.Round(UTILS.MetersToNM(Distance),0)
local zero=self.gettext:GetEntry("ZERO",self.locale)
local miles=self.gettext:GetEntry("MILES",self.locale)
local thsd=self.gettext:GetEntry("THOUSAND",self.locale)
local vlow=self.gettext:GetEntry("VERYLOW",self.locale)
if altitude>=1 then
BRText=string.format("%03d, %d %s, %d %s",AngleDegrees,distancenm,miles,altitude,thsd)
BRTextTTS=string.format("%s, %d %s, %d %s",AngleDegText,distancenm,miles,altitude,thsd)
if self.PathToGoogleKey then
BRTextTTS=string.format("%s, %d %s, %d %s",AngleDegTextTTS,distancenm,miles,altitude,thsd)
end
else
BRText=string.format("%03d, %d %s, %s",AngleDegrees,distancenm,miles,vlow)
BRTextTTS=string.format("%s, %d %s, %s",AngleDegText,distancenm,miles,vlow)
if self.PathToGoogleKey then
BRTextTTS=string.format("%s, %d %s, %s",AngleDegTextTTS,distancenm,miles,vlow)
end
end
self:T(BRText,BRTextTTS)
return BRText,BRTextTTS
end
function AWACS:_GetBRAfromBullsOrAO(clustercoordinate)
self:T(self.lid.."_GetBRAfromBullsOrAO")
local refcoord=self.AOCoordinate
local BRAText=""
local BRATextTTS=""
local bullsname=self.AOName or"Rock"
local stringbr,stringbrtts=self:_ToStringBR(refcoord,clustercoordinate)
BRAText=string.format("%s %s",bullsname,stringbr)
BRATextTTS=string.format("%s %s",bullsname,stringbrtts)
self:T(BRAText,BRATextTTS)
return BRAText,BRATextTTS
end
function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object,TaskStatus,Auftrag,Cluster,Contact)
self:T(self.lid.."_CreateTaskForGroup "..GroupID.." Description: "..Description)
local managedgroup=self.ManagedGrps[GroupID]
local task={}
self.ManagedTaskID=self.ManagedTaskID+1
task.TID=self.ManagedTaskID
task.AssignedGroupID=GroupID
task.Status=TaskStatus or AWACS.TaskStatus.ASSIGNED
task.ToDo=Description
task.Auftrag=Auftrag
task.Cluster=Cluster
task.Contact=Contact
task.IsPlayerTask=managedgroup.IsPlayer
task.IsUnassigned=TaskStatus==AWACS.TaskStatus.UNASSIGNED and false or true
if Object and Object:IsInstanceOf("TARGET")then
task.Target=Object
else
task.Target=TARGET:New(Object)
end
task.ScreenText=ScreenText
if Description==AWACS.TaskDescription.ANCHOR or Description==AWACS.TaskDescription.REANCHOR then
task.Target.Type=TARGET.ObjectType.ZONE
end
task.RequestedTimestamp=timer.getTime()
self.ManagedTasks:Push(task,task.TID)
managedgroup.HasAssignedTask=true
managedgroup.CurrentTask=task.TID
self:T({managedgroup})
self.ManagedGrps[GroupID]=managedgroup
return task.TID
end
function AWACS:_ReadAssignedTaskFromGID(GroupID)
self:T(self.lid.."_GetAssignedTaskFromGID "..GroupID)
local managedgroup=self.ManagedGrps[GroupID]
if managedgroup and managedgroup.HasAssignedTask and managedgroup.CurrentTask~=0 then
local TaskID=managedgroup.CurrentTask
if self.ManagedTasks:HasUniqueID(TaskID)then
return self.ManagedTasks:ReadByID(TaskID)
end
end
return nil
end
function AWACS:_ReadAssignedGroupFromTID(TaskID)
self:T(self.lid.."_ReadAssignedGroupFromTID "..TaskID)
if self.ManagedTasks:HasUniqueID(TaskID)then
local task=self.ManagedTasks:ReadByID(TaskID)
if task and task.AssignedGroupID and task.AssignedGroupID>0 then
return self.ManagedGrps[task.AssignedGroupID]
end
end
return nil
end
function AWACS:_MessageAIReadyForTasking(GID)
self:T(self.lid.."_MessageAIReadyForTasking")
if GID>0 and self.ManagedGrps[GID]then
local managedgroup=self.ManagedGrps[GID]
local GFCallsign=self:_GetCallSign(managedgroup.Group)
local aionst=self.gettext:GetEntry("AIONSTATION",self.locale)
local TextTTS=string.format(aionst,GFCallsign,self.callsigntxt,managedgroup.AnchorStackNo or 1,managedgroup.AnchorStackAngels or 25)
self:_NewRadioEntry(TextTTS,TextTTS,GID,false,false,true,true)
end
return self
end
function AWACS:_UpdateContactEngagementTag(CID,Text,TAC,MELD,TaskStatus)
self:T(self.lid.."_UpdateContactEngagementTag")
local text=Text or""
local contact=self.Contacts:PullByID(CID)
if contact then
contact.EngagementTag=text
contact.TACCallDone=TAC or false
contact.MeldCallDone=MELD or false
contact.Status=TaskStatus or AWACS.TaskStatus.UNASSIGNED
self.Contacts:Push(contact,CID)
end
return self
end
function AWACS:_CheckTaskQueue()
self:T(self.lid.."_CheckTaskQueue")
local opentasks=0
local assignedtasks=0
for _id,_managedgroup in pairs(self.ManagedGrps)do
local group=_managedgroup
if group.Group and group.Group:IsAlive()then
local coordinate=group.Group:GetCoordinate()
if coordinate then
local NewCoordinate=COORDINATE:New(0,0,0)
group.LastKnownPosition=group.LastKnownPosition:UpdateFromCoordinate(coordinate)
self.ManagedGrps[_id]=group
end
end
end
if self.ManagedTasks:IsNotEmpty()then
opentasks=self.ManagedTasks:GetSize()
self:T("Assigned Tasks: "..opentasks)
local taskstack=self.ManagedTasks:GetPointerStack()
for _id,_entry in pairs(taskstack)do
local data=_entry
local entry=data.data
local target=entry.Target
local description=entry.ToDo
if description==AWACS.TaskDescription.ANCHOR or description==AWACS.TaskDescription.REANCHOR then
self:T("Open Task ANCHOR/REANCHOR")
local managedgroup=self.ManagedGrps[entry.AssignedGroupID]
if managedgroup then
local group=managedgroup.Group
if group and group:IsAlive()then
local groupcoord=group:GetCoordinate()
local zone=target:GetObject()
self:T({zone})
if group:IsInZone(zone)then
self:T("Open Task ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID)
target:Stop()
if managedgroup.IsAI then
self:_MessageAIReadyForTasking(managedgroup.GID)
end
managedgroup.HasAssignedTask=false
self.ManagedGrps[entry.AssignedGroupID]=managedgroup
self.ManagedTasks:PullByID(entry.TID)
else
self:T("Open Task ANCHOR/REANCHOR executing for GroupID "..entry.AssignedGroupID)
end
else
self.ManagedTasks:PullByID(entry.TID)
end
end
elseif description==AWACS.TaskDescription.INTERCEPT then
self:T("Open Tasks INTERCEPT")
local taskstatus=entry.Status
local targetstatus=entry.Target:GetState()
if taskstatus==AWACS.TaskStatus.UNASSIGNED then
self.ManagedTasks:PullByID(entry.TID)
break
end
local managedgroup=self.ManagedGrps[entry.AssignedGroupID]
local auftrag=entry.Auftrag
local auftragstatus="Not Known"
if auftrag then
auftragstatus=auftrag:GetState()
end
local text=string.format("ID=%d | Status=%s | TargetState=%s | AuftragState=%s",entry.TID,taskstatus,targetstatus,auftragstatus)
self:T(text)
if auftrag then
if auftrag:IsExecuting()then
entry.Status=AWACS.TaskStatus.EXECUTING
elseif auftrag:IsSuccess()then
entry.Status=AWACS.TaskStatus.SUCCESS
elseif auftrag:GetState()==AUFTRAG.Status.FAILED then
entry.Status=AWACS.TaskStatus.FAILED
end
if targetstatus=="Dead"then
entry.Status=AWACS.TaskStatus.SUCCESS
elseif targetstatus=="Alive"and auftrag:IsOver()then
entry.Status=AWACS.TaskStatus.FAILED
end
elseif entry.IsPlayerTask then
if entry.Target:IsDead()or entry.Target:IsDestroyed()or entry.Target:CountTargets()==0 then
entry.Status=AWACS.TaskStatus.SUCCESS
elseif entry.Target:IsAlive()then
local targetpos=entry.Target:GetCoordinate()
local outofzones=false
self.RejectZoneSet:ForEachZone(
function(Zone,Position)
local zone=Zone
local pos=Position
if pos and zone:IsVec2InZone(pos)then
outofzones=true
end
end,
targetpos:GetVec2()
)
if not outofzones then
outofzones=true
self.ZoneSet:ForEachZone(
function(Zone,Position)
local zone=Zone
local pos=Position
if pos and zone:IsVec2InZone(pos)then
outofzones=false
end
end,
targetpos:GetVec2()
)
end
if outofzones then
entry.Status=AWACS.TaskStatus.SUCCESS
end
end
end
if entry.Status==AWACS.TaskStatus.SUCCESS then
self:T("Open Tasks INTERCEPT success for GroupID "..entry.AssignedGroupID)
if managedgroup then
self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",true,true,AWACS.TaskStatus.SUCCESS)
managedgroup.HasAssignedTask=false
managedgroup.ContactCID=0
managedgroup.LastTasking=timer.getTime()
if managedgroup.IsAI then
managedgroup.CurrentAuftrag=0
else
managedgroup.CurrentTask=0
end
self.ManagedGrps[entry.AssignedGroupID]=managedgroup
self.ManagedTasks:PullByID(entry.TID)
self:__InterceptSuccess(1)
self:__ReAnchor(5,managedgroup.GID)
end
elseif entry.Status==AWACS.TaskStatus.FAILED then
self:T("Open Tasks INTERCEPT failed for GroupID "..entry.AssignedGroupID)
if managedgroup then
managedgroup.HasAssignedTask=false
self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED)
managedgroup.ContactCID=0
managedgroup.LastTasking=timer.getTime()
if managedgroup.IsAI then
managedgroup.CurrentAuftrag=0
else
managedgroup.CurrentTask=0
end
if managedgroup.IsPlayer then
entry.IsPlayerTask=false
end
self.ManagedGrps[entry.AssignedGroupID]=managedgroup
if managedgroup.Group:IsAlive()or(managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive())then
self:__ReAnchor(5,managedgroup.GID)
end
end
self.ManagedTasks:PullByID(entry.TID)
self:__InterceptFailure(1)
elseif entry.Status==AWACS.TaskStatus.REQUESTED then
self:T("Open Tasks INTERCEPT REQUESTED for GroupID "..entry.AssignedGroupID)
local created=entry.RequestedTimestamp or timer.getTime()-120
local Tnow=timer.getTime()
local Trunning=(Tnow-created)/60
local text=string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning)
if Trunning>self.ReassignmentPause then
entry.Status=AWACS.TaskStatus.UNASSIGNED
self.ManagedTasks:PullByID(entry.TID)
end
self:T(text)
end
elseif description==AWACS.TaskDescription.VID then
local managedgroup=self.ManagedGrps[entry.AssignedGroupID]
if(not managedgroup)or(not managedgroup.Group:IsAlive())then
self.ManagedTasks:PullByID(entry.TID)
return self
end
if entry.Target:IsDead()or entry.Target:IsDestroyed()or entry.Target:CountTargets()==0 then
entry.Status=AWACS.TaskStatus.SUCCESS
elseif entry.Target:IsAlive()then
self:T("Checking VID target out of bounds")
local targetpos=entry.Target:GetCoordinate()
local outofzones=false
self.RejectZoneSet:ForEachZone(
function(Zone,Position)
local zone=Zone
local pos=Position
if pos and zone:IsVec2InZone(pos)then
outofzones=true
end
end,
targetpos:GetVec2()
)
if not outofzones then
outofzones=true
self.ZoneSet:ForEachZone(
function(Zone,Position)
local zone=Zone
local pos=Position
if pos and zone:IsVec2InZone(pos)then
outofzones=false
end
end,
targetpos:GetVec2()
)
end
if outofzones then
entry.Status=AWACS.TaskStatus.SUCCESS
self:T("Out of bounds - SUCCESS")
end
end
if entry.Status==AWACS.TaskStatus.REQUESTED then
self:T("Open Tasks VID REQUESTED for GroupID "..entry.AssignedGroupID)
local created=entry.RequestedTimestamp or timer.getTime()-120
local Tnow=timer.getTime()
local Trunning=(Tnow-created)/60
local text=string.format("Task TID %s Requested %d minutes ago.",entry.TID,Trunning)
if Trunning>self.ReassignmentPause then
entry.Status=AWACS.TaskStatus.UNASSIGNED
self.ManagedTasks:PullByID(entry.TID)
end
self:T(text)
elseif entry.Status==AWACS.TaskStatus.ASSIGNED then
self:T("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID)
elseif entry.Status==AWACS.TaskStatus.SUCCESS then
self:T("Open Tasks VID success for GroupID "..entry.AssignedGroupID)
self.ManagedTasks:PullByID(entry.TID)
local Contact=self.Contacts:ReadByID(entry.Contact.CID)
if Contact and(Contact.IFF==AWACS.IFF.FRIENDLY or Contact.IFF==AWACS.IFF.NEUTRAL)then
self:T("IFF outcome friendly/neutral for GroupID "..entry.AssignedGroupID)
if managedgroup then
managedgroup.HasAssignedTask=false
self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED)
managedgroup.ContactCID=0
managedgroup.LastTasking=timer.getTime()
if managedgroup.IsAI then
managedgroup.CurrentAuftrag=0
else
managedgroup.CurrentTask=0
end
if managedgroup.IsPlayer then
entry.IsPlayerTask=false
end
self.ManagedGrps[entry.AssignedGroupID]=managedgroup
self:__ReAnchor(5,managedgroup.GID)
end
elseif Contact and Contact.IFF==AWACS.IFF.ENEMY then
self:T("IFF outcome hostile for GroupID "..entry.AssignedGroupID)
entry.ToDo=AWACS.TaskDescription.INTERCEPT
entry.Status=AWACS.TaskStatus.ASSIGNED
local cname=Contact.TargetGroupNaming
entry.ScreenText=string.format("Engage hostile %s group.",cname)
self.ManagedTasks:Push(entry,entry.TID)
local TextTTS=string.format("%s, %s. Engage hostile target!",managedgroup.CallSign,self.callsigntxt)
self:_NewRadioEntry(TextTTS,TextTTS,managedgroup.GID,true,self.debug,true,false,true)
elseif not Contact then
self:T("IFF outcome target DEAD for GroupID "..entry.AssignedGroupID)
if managedgroup then
managedgroup.HasAssignedTask=false
self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED)
managedgroup.ContactCID=0
managedgroup.LastTasking=timer.getTime()
if managedgroup.IsAI then
managedgroup.CurrentAuftrag=0
else
managedgroup.CurrentTask=0
end
if managedgroup.IsPlayer then
entry.IsPlayerTask=false
end
self.ManagedGrps[entry.AssignedGroupID]=managedgroup
if managedgroup.Group:IsAlive()or managedgroup.FlightGroup:IsAlive()then
self:__ReAnchor(5,managedgroup.GID)
end
end
end
elseif entry.Status==AWACS.TaskStatus.FAILED then
self:T("Open Tasks VID failed for GroupID "..entry.AssignedGroupID)
if managedgroup then
managedgroup.HasAssignedTask=false
self:_UpdateContactEngagementTag(managedgroup.ContactCID,"",false,false,AWACS.TaskStatus.UNASSIGNED)
managedgroup.ContactCID=0
managedgroup.LastTasking=timer.getTime()
if managedgroup.IsAI then
managedgroup.CurrentAuftrag=0
else
managedgroup.CurrentTask=0
end
if managedgroup.IsPlayer then
entry.IsPlayerTask=false
end
self.ManagedGrps[entry.AssignedGroupID]=managedgroup
if managedgroup.Group:IsAlive()or managedgroup.FlightGroup:IsAlive()then
self:__ReAnchor(5,managedgroup.GID)
end
end
self.ManagedTasks:PullByID(entry.TID)
self:__InterceptFailure(1)
end
end
end
end
return self
end
function AWACS:_LogStatistics()
self:T(self.lid.."_LogStatistics")
local text=string.gsub(UTILS.OneLineSerialize(self.MonitoringData),",","\n")
local text=string.gsub(text,"{","\n")
local text=string.gsub(text,"}","")
local text=string.gsub(text,"="," = ")
self:T(text)
if self.MonitoringOn then
MESSAGE:New(text,20,"AWACS",false):ToAll()
end
return self
end
function AWACS:AddCAPAirWing(AirWing,Zone)
self:T(self.lid.."AddCAPAirWing")
if AirWing then
AirWing:SetUsingOpsAwacs(self)
local distance=self.AOCoordinate:Get2DDistance(AirWing:GetCoordinate())
if Zone then
local stackscreated=self.AnchorStacks:GetSize()
if stackscreated==self.AnchorMaxAnchors then
self:E(self.lid.."Max number of stacks already created!")
else
local AnchorStackOne={}
AnchorStackOne.AnchorBaseAngels=self.AnchorBaseAngels
AnchorStackOne.Anchors=FIFO:New()
AnchorStackOne.AnchorAssignedID=FIFO:New()
local newname=Zone:GetName()
for i=1,self.AnchorMaxStacks do
AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels)
end
local newsubname=AWACS.AnchorNames[stackscreated+1]or tostring(stackscreated+1)
newname=Zone:GetName().."-"..newsubname
AnchorStackOne.StationZone=Zone
AnchorStackOne.StationZoneCoordinate=Zone:GetCoordinate()
AnchorStackOne.StationZoneCoordinateText=Zone:GetCoordinate():ToStringLLDDM()
AnchorStackOne.StationName=newname
if self.debug then
AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
else
local stationtag=string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM())
AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
self.AnchorStacks:Push(AnchorStackOne,newname)
AirWing.HasOwnStation=true
AirWing.StationName=newname
end
end
self.CAPAirwings:Push(AirWing,distance)
end
return self
end
function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,ReportingName,Tactical)
self:T(self.lid.."_AnnounceContact")
local tag=""
local Tag=Tag
local CID=0
if not Tag then
CID=Contact.CID or 0
Tag=Contact.TargetGroupNaming or""
end
if self.NoGroupTags then
Tag=nil
end
local isGroup=false
local GID=0
local grpcallsign="Ghost 1"
if Group and Group:IsAlive()then
GID,isGroup,grpcallsign=self:_GetManagedGrpID(Group)
self:T("GID="..GID.." CheckedIn = "..tostring(isGroup))
end
local cluster=Contact.Cluster
local intel=self.intel
local size=self.intel:ClusterCountUnits(cluster)
local threatsize,threatsizetext=self:_GetBlurredSize(size)
local clustercoordinate=Contact.Cluster.coordinate or Contact.Contact.position
local heading=Contact.Contact.group:GetHeading()or self.intel:CalcClusterDirection(cluster)
clustercoordinate:SetHeading(Contact.Contact.group:GetHeading())
local BRAfromBulls,BRAfromBullsTTS=self:_GetBRAfromBullsOrAO(clustercoordinate)
self:T(BRAfromBulls)
self:T(BRAfromBullsTTS)
BRAfromBulls=BRAfromBulls.."."
BRAfromBullsTTS=BRAfromBullsTTS.."."
if isGroup then
BRAfromBulls=clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),true,true)
BRAfromBullsTTS=string.gsub(BRAfromBulls,"BRAA","brah")
BRAfromBullsTTS=string.gsub(BRAfromBullsTTS,"BRA","brah")
if self.PathToGoogleKey then
BRAfromBullsTTS=clustercoordinate:ToStringBRAANATO(Group:GetCoordinate(),true,true,true,false,true)
end
end
local BRAText=""
local TextScreen=""
if isGroup then
BRAText=string.format("%s, %s.",grpcallsign,self.callsigntxt)
TextScreen=string.format("%s, %s.",grpcallsign,self.callsigntxt)
else
BRAText=string.format("%s.",self.callsigntxt)
TextScreen=string.format("%s.",self.callsigntxt)
end
local newgrp=self.gettext:GetEntry("NEWGROUP",self.locale)
local grptxt=self.gettext:GetEntry("GROUP",self.locale)
local GRPtxt=self.gettext:GetEntry("GROUPCAP",self.locale)
local popup=self.gettext:GetEntry("POPUP",self.locale)
if IsNew and self.PlayerGuidance then
BRAText=string.format("%s %s.",BRAText,newgrp)
TextScreen=string.format("%s %s.",TextScreen,newgrp)
elseif IsPopup then
BRAText=string.format("%s %s %s.",BRAText,popup,grptxt)
TextScreen=string.format("%s %s %s.",TextScreen,popup,grptxt)
elseif IsBogeyDope and Tag and Tag~=""then
BRAText=string.format("%s %s %s.",BRAText,Tag,grptxt)
TextScreen=string.format("%s %s %s.",TextScreen,Tag,grptxt)
else
BRAText=string.format("%s %s.",BRAText,GRPtxt)
TextScreen=string.format("%s %s.",TextScreen,GRPtxt)
end
if not IsBogeyDope then
if Tag and Tag~=""then
BRAText=BRAText.." "..Tag.."."
TextScreen=TextScreen.." "..Tag.."."
end
end
if threatsize>1 then
BRAText=BRAText.." "..BRAfromBullsTTS.." "..threatsizetext.."."
TextScreen=TextScreen.." "..BRAfromBulls.." "..threatsizetext.."."
else
BRAText=BRAText.." "..BRAfromBullsTTS
TextScreen=TextScreen.." "..BRAfromBulls
end
if self.ModernEra then
local high=self.gettext:GetEntry("HIGH",self.locale)
local vfast=self.gettext:GetEntry("VERYFAST",self.locale)
local fast=self.gettext:GetEntry("FAST",self.locale)
if ReportingName and ReportingName~="Bogey"then
ReportingName=string.gsub(ReportingName,"_"," ")
BRAText=BRAText.." "..ReportingName.."."
TextScreen=TextScreen.." "..ReportingName.."."
end
local height=Contact.Contact.group:GetHeight()
local height=UTILS.Round(UTILS.MetersToFeet(height)/1000,0)
if height>=40 then
BRAText=BRAText..high
TextScreen=TextScreen..high
end
local speed=Contact.Contact.group:GetVelocityKNOTS()
if speed>900 then
BRAText=BRAText..vfast
TextScreen=TextScreen..vfast
elseif speed>=600 and speed<=900 then
BRAText=BRAText..fast
TextScreen=TextScreen..fast
end
end
BRAText=string.gsub(BRAText,"BRAA","brah")
BRAText=string.gsub(BRAText,"BRA","brah")
local prio=IsNew or IsBogeyDope
self:_NewRadioEntry(BRAText,TextScreen,GID,isGroup,true,IsNew,false,prio,Tactical)
return self
end
function AWACS:_GetAliveOpsGroupFromTable(OpsGroups)
self:T(self.lid.."_GetAliveOpsGroupFromTable")
local handback=nil
for _,_OG in pairs(OpsGroups or{})do
local OG=_OG
if OG and OG:IsAlive()then
handback=OG
break
end
end
return handback
end
function AWACS:_CleanUpAIMissionStack()
self:T(self.lid.."_CleanUpAIMissionStack")
local CAPMissions=0
local Alert5Missions=0
local InterceptMissions=0
local MissionStack=FIFO:New()
self:T("Checking MissionStack")
for _,_mission in pairs(self.CatchAllMissions)do
local mission=_mission
local type=mission:GetType()
if type==AUFTRAG.Type.ALERT5 and mission:IsNotOver()then
MissionStack:Push(mission,mission.auftragsnummer)
Alert5Missions=Alert5Missions+1
elseif type==AUFTRAG.Type.CAP and mission:IsNotOver()then
MissionStack:Push(mission,mission.auftragsnummer)
CAPMissions=CAPMissions+1
elseif type==AUFTRAG.Type.INTERCEPT and mission:IsNotOver()then
MissionStack:Push(mission,mission.auftragsnummer)
InterceptMissions=InterceptMissions+1
end
end
self.AICAPMissions=nil
self.AICAPMissions=MissionStack
return CAPMissions,Alert5Missions,InterceptMissions
end
function AWACS:_ConsistencyCheck()
self:T(self.lid.."_ConsistencyCheck")
if self.debug then
self:T("CatchAllMissions")
local catchallm={}
local report1=REPORT:New("CatchAll")
report1:Add("====================")
report1:Add("CatchAllMissions")
report1:Add("====================")
for _,_mission in pairs(self.CatchAllMissions)do
local mission=_mission
local nummer=mission.auftragsnummer or 0
local type=mission:GetType()
local state=mission:GetState()
local FG=mission:GetOpsGroups()
local OG=self:_GetAliveOpsGroupFromTable(FG)
local OGName="UnknownFromMission"
if OG then
OGName=OG:GetName()
end
report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName))
if mission:IsNotOver()then
catchallm[#catchallm+1]=mission
end
end
self.CatchAllMissions=nil
self.CatchAllMissions=catchallm
local catchallfg={}
self:T("CatchAllFGs")
report1:Add("====================")
report1:Add("CatchAllFGs")
report1:Add("====================")
for _,_fg in pairs(self.CatchAllFGs)do
local FG=_fg
local mission=FG:GetMissionCurrent()
local OGName=FG:GetName()or"UnknownFromFG"
local nummer=0
local type="No Type"
local state="None"
if mission then
type=mission:GetType()
nummer=mission.auftragsnummer or 0
state=mission:GetState()
end
report1:Add(string.format("Auftrag Nr %d Type %s State %s FlightGroup %s",nummer,type,state,OGName))
if FG:IsAlive()then
catchallfg[#catchallfg+1]=FG
end
end
report1:Add("====================")
self:T(report1:Text())
self.CatchAllFGs=nil
self.CatchAllFGs=catchallfg
end
return self
end
function AWACS:_CheckAICAPOnStation()
self:T(self.lid.."_CheckAICAPOnStation")
self:_ConsistencyCheck()
local capmissions,alert5missions,interceptmissions=self:_CleanUpAIMissionStack()
self:T("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested)
if self.MaxAIonCAP>0 then
local onstation=capmissions+alert5missions
if capmissions>self.MaxAIonCAP then
self:T(string.format("*** Onstation %d > MaxAIOnCAP %d",onstation,self.MaxAIonCAP))
local mission=self.AICAPMissions:Pull()
local Groups=mission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(Groups)
local GID,checkedin=self:_GetManagedGrpID(OpsGroup)
mission:__Cancel(30)
self.AIRequested=self.AIRequested-1
if checkedin then
self:_CheckOut(OpsGroup,GID)
end
end
if capmissions<self.MaxAIonCAP and alert5missions<self.MaxAIonCAP+2 then
local AnchorStackNo,free=self:_GetFreeAnchorStack()
if free then
local mission=AUFTRAG:NewALERT5(AUFTRAG.Type.CAP)
self.CatchAllMissions[#self.CatchAllMissions+1]=mission
local availableAWS=self.CAPAirwings:Count()
local AWS=self.CAPAirwings:GetDataTable()
self.AIRequested=self.AIRequested+1
local selectedAW=AWS[(((self.AIRequested-1)%availableAWS)+1)]
selectedAW:AddMission(mission)
self:T("CAP="..capmissions.." ALERT5="..alert5missions.." Requested="..self.AIRequested)
end
end
if onstation>0 and capmissions<self.MaxAIonCAP then
local missions=self.AICAPMissions:GetDataTable()
for _,_Mission in pairs(missions)do
local mission=_Mission
self:T("Looking at AuftragsNr "..mission.auftragsnummer)
local type=mission:GetType()
local state=mission:GetState()
if type==AUFTRAG.Type.ALERT5 then
local OpsGroups=mission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups)
local FGstate=mission:GetGroupStatus(OpsGroup)
if OpsGroup then
FGstate=OpsGroup:GetState()
self:T("FG Object in state: "..FGstate)
end
if OpsGroup and(FGstate=="Parking"or FGstate=="Cruising")then
local GID,CheckedInAlready=self:_GetManagedGrpID(OpsGroup:GetGroup())
if not CheckedInAlready then
self:_SetAIROE(OpsGroup,OpsGroup:GetGroup())
self:_CheckInAI(OpsGroup,OpsGroup:GetGroup(),mission.auftragsnummer)
end
end
end
end
end
if onstation>0 then
local report=REPORT:New("CAP Mission Status")
report:Add("===============")
local missions=self.AICAPMissions:GetDataTable()
local i=1
for _,_Mission in pairs(missions)do
local mission=_Mission
if mission then
i=i+1
report:Add(string.format("Entry %d",i))
report:Add(string.format("Mission No %d",mission.auftragsnummer))
report:Add(string.format("Mission Type %s",mission:GetType()))
report:Add(string.format("Mission State %s",mission:GetState()))
local OpsGroups=mission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups)
if OpsGroup then
local OpsName=OpsGroup:GetName()or"Unknown"
local found,GID,OpsCallSign=self:_GetGIDFromGroupOrName(OpsGroup)
report:Add(string.format("Mission FG %s",OpsName))
report:Add(string.format("Callsign %s",OpsCallSign))
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
else
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end
report:Add(string.format("Target Type %s",mission:GetTargetType()))
end
report:Add("===============")
end
if self.debug then
self:I(report:Text())
end
end
end
return self
end
function AWACS:_SetAIROE(FlightGroup,Group)
self:T(self.lid.."_SetAIROE")
local ROE=self.AwacsROE or AWACS.ROE.POLICE
local ROT=self.AwacsROT or AWACS.ROT.PASSIVE
Group:OptionAlarmStateGreen()
Group:OptionECM_OnlyLockByRadar()
Group:OptionROEHoldFire()
Group:OptionROTEvadeFire()
Group:OptionRTBBingoFuel(true)
Group:OptionKeepWeaponsOnThreat()
local callname=self.AICAPCAllName or CALLSIGN.Aircraft.Colt
self.AICAPCAllNumber=self.AICAPCAllNumber+1
Group:CommandSetCallsign(callname,math.fmod(self.AICAPCAllNumber,9))
FlightGroup:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN)
FlightGroup:SetDefaultCallsign(callname,math.fmod(self.AICAPCAllNumber,9))
if ROE==AWACS.ROE.POLICE or ROE==AWACS.ROE.VID then
FlightGroup:SetDefaultROE(ENUMS.ROE.WeaponHold)
elseif ROE==AWACS.ROE.IFF then
FlightGroup:SetDefaultROE(ENUMS.ROE.ReturnFire)
elseif ROE==AWACS.ROE.BVR then
FlightGroup:SetDefaultROE(ENUMS.ROE.OpenFire)
end
if ROT==AWACS.ROT.BYPASSESCAPE or ROT==AWACS.ROT.PASSIVE then
FlightGroup:SetDefaultROT(ENUMS.ROT.PassiveDefense)
elseif ROT==AWACS.ROT.OPENFIRE or ROT==AWACS.ROT.RETURNFIRE then
FlightGroup:SetDefaultROT(ENUMS.ROT.BypassAndEscape)
elseif ROT==AWACS.ROT.EVADE then
FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire)
end
FlightGroup:SetFuelLowRTB(true)
FlightGroup:SetFuelLowThreshold(0.2)
FlightGroup:SetEngageDetectedOff()
FlightGroup:SetOutOfAAMRTB(true)
return self
end
function AWACS:_TACRangeCall(GID,Contact)
self:T(self.lid.."_TACRangeCall")
if not Contact then return self end
local pilotcallsign=self:_GetCallSign(nil,GID)
local managedgroup=self.ManagedGrps[GID]
local contact=Contact.Contact
local contacttag=Contact.TargetGroupNaming
local name=managedgroup.GroupName
if contact then
local position=contact.position
if position then
local distance=position:Get2DDistance(managedgroup.Group:GetCoordinate())
distance=UTILS.Round(UTILS.MetersToNM(distance))
local grptxt=self.gettext:GetEntry("GROUP",self.locale)
local miles=self.gettext:GetEntry("MILES",self.locale)
local text=string.format("%s. %s. %s %s, %d %s.",self.callsigntxt,pilotcallsign,contacttag,grptxt,distance,miles)
if not self.TacticalSubscribers[name]then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
end
self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING)
if GID and GID~=0 then
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then
if self.TacticalSubscribers[name]then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
end
end
end
end
return self
end
function AWACS:_MeldRangeCall(GID,Contact)
self:T(self.lid.."_MeldRangeCall")
if not Contact then return self end
local pilotcallsign=self:_GetCallSign(nil,GID)
local managedgroup=self.ManagedGrps[GID]
local flightpos=managedgroup.Group:GetCoordinate()
local contact=Contact.Contact
local contacttag=Contact.TargetGroupNaming or"Bogey"
local name=managedgroup.GroupName
if contact then
local position=contact.position
if position then
local BRATExt=""
if self.PathToGoogleKey then
BRATExt=position:ToStringBRAANATO(flightpos,false,false,true,false,true)
else
BRATExt=position:ToStringBRAANATO(flightpos,false,false)
end
local grptxt=self.gettext:GetEntry("GROUP",self.locale)
local text=string.format("%s. %s. %s %s, %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,BRATExt)
if not self.TacticalSubscribers[name]then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
end
self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,true,AWACS.TaskStatus.EXECUTING)
if GID and GID~=0 then
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then
local name=managedgroup.GroupName
if self.TacticalSubscribers[name]then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
end
end
end
end
return self
end
function AWACS:_ThreatRangeCall(GID,Contact)
self:T(self.lid.."_ThreatRangeCall")
if not Contact then return self end
local pilotcallsign=self:_GetCallSign(nil,GID)
local managedgroup=self.ManagedGrps[GID]
local flightpos=managedgroup.Group:GetCoordinate()or managedgroup.LastKnownPosition
local contact=Contact.Contact
local contacttag=Contact.TargetGroupNaming or"Bogey"
local name=managedgroup.GroupName
local IsSub=self.TacticalSubscribers[name]and true or false
if contact then
local position=contact.position or contact.group:GetCoordinate()
if position then
local BRATExt=""
if self.PathToGoogleKey then
BRATExt=position:ToStringBRAANATO(flightpos,false,false,true,false,true)
else
BRATExt=position:ToStringBRAANATO(flightpos,false,false)
end
local grptxt=self.gettext:GetEntry("GROUP",self.locale)
local thrt=self.gettext:GetEntry("THREAT",self.locale)
local text=string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,thrt,BRATExt)
if 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
if GID and GID~=0 then
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then
local name=managedgroup.GroupName
if self.TacticalSubscribers[name]then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
end
end
end
end
return self
end
function AWACS:_MergedCall(GID)
self:T(self.lid.."_MergedCall")
local pilotcallsign=self:_GetCallSign(nil,GID)
local merge=self.gettext:GetEntry("MERGED",self.locale)
local text=string.format("%s. %s. %s.",self.callsigntxt,pilotcallsign,merge)
local managedgroup=self.ManagedGrps[GID]
local name
if managedgroup then
name=managedgroup.GroupName or"none"
end
if not self.TacticalSubscribers[name]then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
end
if GID and GID~=0 then
local managedgroup=self.ManagedGrps[GID]
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then
if self.TacticalSubscribers[name]then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
end
end
return self
end
function AWACS:_AssignPilotToTarget(Pilots,Targets)
self:T(self.lid.."_AssignPilotToTarget")
local inreach=false
local Pilot=nil
local closest=UTILS.NMToMeters(self.maxassigndistance+1)
local targets=Targets:GetDataTable()
local Target=nil
for _,_target in pairs(targets)do
local targetgroupcoord=_target.Contact.position
for _,_Pilot in pairs(Pilots)do
local pilotcoord=_Pilot.Group:GetCoordinate()
local targetdist=targetgroupcoord:Get2DDistance(pilotcoord)
if UTILS.MetersToNM(targetdist)<self.maxassigndistance and targetdist<closest then
self:T(string.format("%sTarget distance %d! Assignment %s!",self.lid,UTILS.Round(UTILS.MetersToNM(targetdist),0),_Pilot.CallSign))
inreach=true
closest=targetdist
Pilot=_Pilot
Target=_target
Targets:PullByID(_target.CID)
break
else
self:T(self.lid.."Target distance > "..self.maxassigndistance.."NM! No Assignment!")
end
end
end
if inreach and Pilot and Pilot.IsPlayer then
local callsign=Pilot.CallSign
self.ManagedTasks:PullByID(Pilot.CurrentTask)
Pilot.HasAssignedTask=true
local TargetPosition=Target.Target:GetCoordinate()
local PlayerPositon=Pilot.LastKnownPosition
local TargetAlt=Target.Contact.altitude or Target.Cluster.altitude or Target.Contact.group:GetAltitude()
local TargetDirections,TargetDirectionsTTS=self:_ToStringBRA(PlayerPositon,TargetPosition,TargetAlt)
local ScreenText=""
local TaskType=AWACS.TaskDescription.INTERCEPT
if self.AwacsROE==AWACS.ROE.POLICE or self.AwacsROE==AWACS.ROE.VID then
local interc=self.gettext:GetEntry("SCREENVID",self.locale)
ScreenText=string.format(interc,Target.TargetGroupNaming)
TaskType=AWACS.TaskDescription.VID
else
local interc=self.gettext:GetEntry("SCREENINTER",self.locale)
ScreenText=string.format(interc,Target.TargetGroupNaming)
end
Pilot.CurrentTask=self:_CreateTaskForGroup(Pilot.GID,TaskType,ScreenText,Target.Target,AWACS.TaskStatus.REQUESTED,nil,Target.Cluster,Target.Contact)
Pilot.ContactCID=Target.CID
self.ManagedGrps[Pilot.GID]=Pilot
Target.LinkedTask=Pilot.CurrentTask
Target.LinkedGroup=Pilot.GID
Target.Status=AWACS.TaskStatus.REQUESTED
local targeted=self.gettext:GetEntry("ENGAGETAG",self.locale)
Target.EngagementTag=string.format(targeted,Pilot.CallSign)
self.Contacts:PullByID(Target.CID)
self.Contacts:Push(Target,Target.CID)
local reqcomm=self.gettext:GetEntry("REQCOMMIT",self.locale)
local text=string.format(reqcomm,self.callsigntxt,Target.TargetGroupNaming,TargetDirectionsTTS,Pilot.CallSign)
local textScreen=string.format(reqcomm,self.callsigntxt,Target.TargetGroupNaming,TargetDirections,Pilot.CallSign)
self:_NewRadioEntry(text,textScreen,Pilot.GID,true,self.debug,true,false,true)
elseif inreach and Pilot and Pilot.IsAI then
local callsign=Pilot.CallSign
local FGStatus=Pilot.FlightGroup:GetState()
self:T("Pilot AI Callsign: "..callsign)
self:T("Pilot FG State: "..FGStatus)
local targetstatus=Target.Target:GetState()
self:T("Target State: "..targetstatus)
local currmission=Pilot.FlightGroup:GetMissionCurrent()
if currmission then
self:T("Current Mission: "..currmission:GetType())
end
local ZoneSet=self.ZoneSet
local RejectZoneSet=self.RejectZoneSet
local intercept=AUFTRAG:NewINTERCEPT(Target.Target)
intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL)
intercept:SetWeaponType(ENUMS.WeaponFlag.Auto)
intercept:SetMissionRange(self.MaxMissionRange)
intercept:AddConditionSuccess(
function(target,zoneset,rzoneset)
local success=true
local target=target
if target:IsDestroyed()or target:IsDead()or target:CountTargets()==0 then return true end
local tgtcoord=target:GetCoordinate()
local tgtvec2=nil
if tgtcoord then
tgtvec2=tgtcoord:GetVec2()
end
local zones=zoneset
local rzones=rzoneset
if tgtvec2 then
zones:ForEachZone(
function(zone)
if zone:IsVec2InZone(tgtvec2)then
success=false
end
end
)
rzones:ForEachZone(
function(zone)
if zone:IsVec2InZone(tgtvec2)then
success=true
end
end
)
end
return success
end,
Target.Target,
ZoneSet,
RejectZoneSet
)
Pilot.FlightGroup:AddMission(intercept)
local Angels=Pilot.AnchorStackAngels or 25
Angels=Angels*1000
local AnchorSpeed=self.CapSpeedBase or 270
AnchorSpeed=UTILS.KnotsToAltKIAS(AnchorSpeed,Angels)
local Anchor=self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo)
local capauftrag=AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{})
capauftrag:SetMissionRange(self.MaxMissionRange)
capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
Pilot.FlightGroup:AddMission(capauftrag)
if currmission then
currmission:__Cancel(3)
end
self.CatchAllMissions[#self.CatchAllMissions+1]=intercept
self.CatchAllMissions[#self.CatchAllMissions+1]=capauftrag
self.ManagedTasks:PullByID(Pilot.CurrentTask)
Pilot.HasAssignedTask=true
Pilot.CurrentTask=self:_CreateTaskForGroup(Pilot.GID,AWACS.TaskDescription.INTERCEPT,"Intercept Task",Target.Target,AWACS.TaskStatus.ASSIGNED,intercept,Target.Cluster,Target.Contact)
Pilot.CurrentAuftrag=intercept.auftragsnummer
Pilot.ContactCID=Target.CID
self.ManagedGrps[Pilot.GID]=Pilot
Target.LinkedTask=Pilot.CurrentTask
Target.LinkedGroup=Pilot.GID
Target.Status=AWACS.TaskStatus.ASSIGNED
local targeted=self.gettext:GetEntry("ENGAGETAG",self.locale)
Target.EngagementTag=string.format(targeted,Pilot.CallSign)
self.Contacts:PullByID(Target.CID)
self.Contacts:Push(Target,Target.CID)
local altitude=Target.Contact.altitude or Target.Contact.group:GetAltitude()
local position=Target.Cluster.coordinate or Target.Contact.position
if not position then
self.intel:GetClusterCoordinate(Target.Cluster,true)
end
local bratext,bratexttts=self:_ToStringBRA(Pilot.Group:GetCoordinate(),position,altitude or 8000)
local aicomm=self.gettext:GetEntry("AICOMMIT",self.locale)
local text=string.format(aicomm,self.callsigntxt,Target.TargetGroupNaming,bratexttts,Pilot.CallSign)
local textScreen=string.format(aicomm,self.callsigntxt,Target.TargetGroupNaming,bratext,Pilot.CallSign)
self:_NewRadioEntry(text,textScreen,Pilot.GID,true,self.debug,true,false,true)
local comm=self.gettext:GetEntry("COMMIT",self.locale)
local text=string.format("%s. %s.",Pilot.CallSign,comm)
self:_NewRadioEntry(text,text,Pilot.GID,true,self.debug,true,true,true)
self:__Intercept(2)
end
return self
end
function AWACS:onbeforeStart(From,Event,To)
self:T({From,Event,To})
if self.IncludeHelicopters then
self.clientset:FilterCategories("helicopter")
end
return self
end
function AWACS:onafterStart(From,Event,To)
self:T({From,Event,To})
local controlzonename="FEZ-"..self.AOName
self.ControlZone=ZONE_RADIUS:New(controlzonename,self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius))
if self.debug then
self.ControlZone:DrawZone(self.coalition,{0,1,0},1,{1,0,0},0.05,3,true)
self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true)
local AOCoordString=self.AOCoordinate:ToStringLLDDM()
local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString)
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
self.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true)
local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM())
if not self.GCI then
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true)
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
end
else
local AOCoordString=self.AOCoordinate:ToStringLLDDM()
local Rocktag=string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString)
MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition)
if not self.GCI then
MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition)
end
local stationtag=string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM())
MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition)
end
if not self.GCI then
local AwacsAW=self.AirWing
local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
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
AwacsAW:AddMission(mission)
self.AwacsMission=mission
self.AwacsInZone=false
self.AwacsReady=false
else
self.AwacsInZone=true
self.AwacsReady=true
self:_StartIntel(self.GCIGroup)
if self.GCIGroup:IsGround()then
self.AwacsFG=ARMYGROUP:New(self.GCIGroup)
self.AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation)
self.AwacsFG:SwitchRadio(self.Frequency,self.Modulation)
elseif self.GCIGroup:IsShip()then
self.AwacsFG=NAVYGROUP:New(self.GCIGroup)
self.AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation)
self.AwacsFG:SwitchRadio(self.Frequency,self.Modulation)
else
self:E(self.lid.."**** Group unsuitable for GCI ops! Needs to be a GROUND or SHIP type group!")
self:Stop()
return self
end
self.callsigntxt=string.format("%s",self.CallSignClear[self.CallSign])
self:__CheckRadioQueue(-10)
local sunrise=self.gettext:GetEntry("SUNRISE",self.locale)
local text=string.format(sunrise,self.callsigntxt,self.callsigntxt)
self:_NewRadioEntry(text,text,0,false,false,false,false,true)
self:T(self.lid..text)
self.sunrisedone=true
end
local ZoneSet=SET_ZONE:New()
ZoneSet:AddZone(self.ControlZone)
if not self.GCI then
ZoneSet:AddZone(self.OrbitZone)
end
if self.BorderZone then
ZoneSet:AddZone(self.BorderZone)
end
local RejectZoneSet=SET_ZONE:New()
if self.RejectZone then
RejectZoneSet:AddZone(self.RejectZone)
end
self.ZoneSet=ZoneSet
self.RejectZoneSet=RejectZoneSet
if self.AllowMarkers then
local MarkerOps=MARKEROPS_BASE:New("AWACS",{"Station","Delete","Move"})
local function Handler(Keywords,Coord,Text)
self:T(Text)
for _,_word in pairs(Keywords)do
if string.lower(_word)=="station"then
local Name=string.match(Text," ([%a]+)$")
self:_CreateAnchorStackFromMarker(Name,Coord)
break
elseif string.lower(_word)=="delete"then
local Name=string.match(Text," ([%a]+)$")
self:_DeleteAnchorStackFromMarker(Name,Coord)
break
elseif string.lower(_word)=="move"then
local Name=string.match(Text," ([%a]+)$")
self:_MoveAnchorStackFromMarker(Name,Coord)
break
end
end
end
function MarkerOps:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord)
Handler(Keywords,Coord,Text)
end
function MarkerOps:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord)
Handler(Keywords,Coord,Text)
end
function MarkerOps:OnAfterMarkDeleted(From,Event,To)
end
self.MarkerOps=MarkerOps
end
if self.GCI then
self:__Started(-5)
end
if self.TacticalMenu then
self:__CheckTacticalQueue(55)
end
self:__Status(-30)
return self
end
function AWACS:_CheckAwacsStatus()
self:T(self.lid.."_CheckAwacsStatus")
local awacs=nil
if self.AwacsFG then
awacs=self.AwacsFG:GetGroup()
local unit=awacs:GetUnit(1)
if unit then
self.STN=tostring(unit:GetSTN())
end
end
local monitoringdata=self.MonitoringData
if not self.GCI then
if awacs and awacs:IsAlive()and not self.AwacsInZone then
local orbitzone=self.OrbitZone
if awacs:IsInZone(orbitzone)then
self.AwacsInZone=true
self:T(self.lid.."Arrived in Orbit Zone: "..orbitzone:GetName())
local onstationtxt=self.gettext:GetEntry("AWONSTATION",self.locale)
local text=string.format(onstationtxt,self.callsigntxt,self.AOName or"Rock")
local textScreen=text
self:_NewRadioEntry(text,textScreen,0,false,true,true,false,true)
end
end
end
if(awacs and awacs:IsAlive())then
if not self.intelstarted then
local alt=UTILS.Round(UTILS.MetersToFeet(awacs:GetAltitude())/1000,0)
if alt>=10 then
self:_StartIntel(awacs)
end
end
if self.intelstarted and not self.sunrisedone then
local alt=UTILS.Round(UTILS.MetersToFeet(awacs:GetAltitude())/1000,0)
if alt>=10 then
local sunrise=self.gettext:GetEntry("SUNRISE",self.locale)
local text=string.format(sunrise,self.callsigntxt,self.callsigntxt)
self:_NewRadioEntry(text,text,0,false,false,false,false,true)
self:T(self.lid..text)
self.sunrisedone=true
end
end
local AWmission=self.AwacsMission
local awstatus=AWmission:GetState()
local AWmissiontime=(timer.getTime()-self.AwacsTimeStamp)
local AWTOSLeft=UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600)-AWmissiontime),0)
AWTOSLeft=UTILS.Round(AWTOSLeft/60,0)
local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0)
local Changedue="No"
if not self.ShiftChangeAwacsFlag and(AWTOSLeft<=ChangeTime or AWmission:IsOver())then
Changedue="Yes"
self.ShiftChangeAwacsFlag=true
self:__AwacsShiftChange(2)
end
local report=REPORT:New("AWACS:")
report:Add("====================")
report:Add("AWACS:")
report:Add(string.format("Auftrag Status: %s",awstatus))
report:Add(string.format("TOS Left: %d min",AWTOSLeft))
report:Add(string.format("Needs ShiftChange: %s",Changedue))
local OpsGroups=AWmission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups)
if OpsGroup then
local OpsName=OpsGroup:GetName()or"Unknown"
local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown"
report:Add(string.format("Mission FG %s",OpsName))
report:Add(string.format("Callsign %s",OpsCallSign))
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
else
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end
if self.ShiftChangeAwacsFlag and self.ShiftChangeAwacsRequested then
AWmission=self.AwacsMissionReplacement
local esstatus=AWmission:GetState()
local ESmissiontime=(timer.getTime()-self.AwacsTimeStamp)
local ESTOSLeft=UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0)
ESTOSLeft=UTILS.Round(ESTOSLeft/60,0)
local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0)
report:Add("AWACS REPLACEMENT:")
report:Add(string.format("Auftrag Status: %s",esstatus))
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
local OpsGroups=AWmission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups)
if OpsGroup then
local OpsName=OpsGroup:GetName()or"Unknown"
local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown"
report:Add(string.format("Mission FG %s",OpsName))
report:Add(string.format("Callsign %s",OpsCallSign))
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
else
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end
if AWmission:IsExecuting()then
self.ShiftChangeAwacsFlag=false
self.ShiftChangeAwacsRequested=false
self.sunrisedone=false
if self.AwacsMission and self.AwacsMission:IsNotOver()then
self.AwacsMission:Cancel()
end
self.AwacsMission=self.AwacsMissionReplacement
self.AwacsMissionReplacement=nil
self.AwacsTimeStamp=timer.getTime()
report:Add("*** Replacement DONE ***")
end
report:Add("====================")
end
if self.HasEscorts then
for i=1,self.EscortNumber do
local ESmission=self.EscortMission[i]
if not ESmission then break end
local esstatus=ESmission:GetState()
local ESmissiontime=(timer.getTime()-self.EscortsTimeStamp)
local ESTOSLeft=UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600)-ESmissiontime),0)
ESTOSLeft=UTILS.Round(ESTOSLeft/60,0)
local ChangeTime=UTILS.Round(((self.ShiftChangeTime*3600)/60),0)
local Changedue="No"
if(ESTOSLeft<=ChangeTime and not self.ShiftChangeEscortsFlag)or(ESmission:IsOver()and not self.ShiftChangeEscortsFlag)then
Changedue="Yes"
self.ShiftChangeEscortsFlag=true
self:__EscortShiftChange(2)
end
report:Add("====================")
report:Add("ESCORTS:")
report:Add(string.format("Auftrag Status: %s",esstatus))
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
report:Add(string.format("Needs ShiftChange: %s",Changedue))
local OpsGroups=ESmission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups)
if OpsGroup then
local OpsName=OpsGroup:GetName()or"Unknown"
local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown"
report:Add(string.format("Mission FG %s",OpsName))
report:Add(string.format("Callsign %s",OpsCallSign))
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
monitoringdata.EscortsStateMission[i]=esstatus
monitoringdata.EscortsStateFG[i]=OpsGroup:GetState()
else
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end
report:Add("====================")
local RESMission
if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then
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=RESMission:GetOpsGroups()
local OpsGroup=self:_GetAliveOpsGroupFromTable(OpsGroups)
if OpsGroup then
local OpsName=OpsGroup:GetName()or"Unknown"
local OpsCallSign=OpsGroup:GetCallsignName()or"Unknown"
report:Add(string.format("Mission FG %s",OpsName))
report:Add(string.format("Callsign %s",OpsCallSign))
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
else
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
end
if RESMission and RESMission:IsExecuting()then
self.ShiftChangeEscortsFlag=false
self.ShiftChangeEscortsRequested=false
if ESmission and ESmission:IsNotOver()then
ESmission:__Cancel(1)
end
self.EscortMission[i]=self.EscortMissionReplacement[i]
self.EscortMissionReplacement[i]=nil
self.EscortsTimeStamp=timer.getTime()
report:Add("*** Replacement DONE ***")
end
report:Add("====================")
end
end
end
if self.debug then
self:T(report:Text())
end
else
local AWmission=self.AwacsMission
local awstatus=AWmission:GetState()
if AWmission:IsOver()then
self:I(self.lid.."*****AWACS is dead!*****")
self.ShiftChangeAwacsFlag=true
self:__AwacsShiftChange(2)
end
end
return monitoringdata
end
function AWACS:onafterStatus(From,Event,To)
self:T({From,Event,To})
self:_SetClientMenus()
local monitoringdata=self.MonitoringData
if not self.GCI then
monitoringdata=self:_CheckAwacsStatus()
end
local awacsalive=false
if self.AwacsFG then
local awacs=self.AwacsFG:GetGroup()
if awacs and awacs:IsAlive()then
awacsalive=true
end
end
if self:Is("Running")and(awacsalive or self.AwacsInZone)then
if self.AwacsSRS then
self.AwacsSRS:SetCoordinate(self.AwacsFG:GetCoordinate())
if self.TacticalSRS then
self.TacticalSRS:SetCoordinate(self.AwacsFG:GetCoordinate())
end
end
self:_CheckAICAPOnStation()
self:_CleanUpContacts()
self:_CheckMerges()
self:_CheckSubscribers()
local outcome,targets=self:_TargetSelectionProcess(true)
self:_CheckTaskQueue()
local AI,Humans=self:_GetIdlePilots()
if outcome and#Humans>0 and self.PlayerCapAssignment then
self:_AssignPilotToTarget(Humans,targets)
end
if outcome and#AI>0 then
self:_AssignPilotToTarget(AI,targets)
end
end
if not self.GCI then
monitoringdata.AwacsShiftChange=self.ShiftChangeAwacsFlag
if self.AwacsFG then
monitoringdata.AwacsStateFG=self.AwacsFG:GetState()
end
monitoringdata.AwacsStateMission=self.AwacsMission:GetState()
monitoringdata.EscortsShiftChange=self.ShiftChangeEscortsFlag
end
monitoringdata.AICAPCurrent=self.AICAPMissions:Count()
monitoringdata.AICAPMax=self.MaxAIonCAP
monitoringdata.Airwings=self.CAPAirwings:Count()
self.MonitoringData=monitoringdata
if self.debug then
self:_LogStatistics()
end
local picturetime=timer.getTime()-self.PictureTimeStamp
if self.AwacsInZone and picturetime>self.PictureInterval then
self.PictureTimeStamp=timer.getTime()
self:_Picture(nil,true)
end
self:__Status(30)
return self
end
function AWACS:onafterStop(From,Event,To)
self:T({From,Event,To})
self.intel:Stop()
local AWFiFo=self.CAPAirwings
local AWStack=AWFiFo:GetPointerStack()
for _ID,_AWID in pairs(AWStack)do
local SubAW=self.CAPAirwings:ReadByPointer(_ID)
if SubAW then
SubAW:RemoveUsingOpsAwacs()
end
end
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.UnitLost)
self:UnHandleEvent(EVENTS.BDA)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.Shot)
return self
end
function AWACS:onafterAssignAnchor(From,Event,To,GID,HasOwnStation,StationName)
self:T({From,Event,To,"GID = "..GID})
self:_AssignAnchorToID(GID,HasOwnStation,StationName)
return self
end
function AWACS:onafterCheckedOut(From,Event,To,GID,AnchorStackNo,Angels)
self:T({From,Event,To,"GID = "..GID})
self:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels)
return self
end
function AWACS:onafterAssignedAnchor(From,Event,To,GID,Anchor,AnchorStackNo,AnchorAngels)
self:T({From,Event,To,"GID="..GID,"Stack="..AnchorStackNo})
local managedgroup=self.ManagedGrps[GID]
if not managedgroup then
self:E(self.lid.."**** GID "..GID.." Not Registered!")
return self
end
managedgroup.AnchorStackNo=AnchorStackNo
managedgroup.AnchorStackAngels=AnchorAngels
managedgroup.Blocked=false
local isPlayer=managedgroup.IsPlayer
local isAI=managedgroup.IsAI
local Group=managedgroup.Group
local CallSign=managedgroup.CallSign or"Ghost 1"
local AnchorName=Anchor.StationName or"unknown"
local AnchorCoordTxt=Anchor.StationZoneCoordinateText or"unknown"
local Angels=AnchorAngels or 25
local AnchorSpeed=self.CapSpeedBase or 270
local AuftragsNr=managedgroup.CurrentAuftrag
local textTTS=""
if self.PikesSpecialSwitch then
local stationtxt=self.gettext:GetEntry("STATIONAT",self.locale)
textTTS=string.format(stationtxt,CallSign,self.callsigntxt,AnchorName,Angels)
else
local stationtxt=self.gettext:GetEntry("STATIONATLONG",self.locale)
textTTS=string.format(stationtxt,CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed)
end
local ROEROT=self.AwacsROE..", "..self.AwacsROT
local stationtxtsc=self.gettext:GetEntry("STATIONSCREEN",self.locale)
local stationtxtta=self.gettext:GetEntry("STATIONTASK",self.locale)
local textScreen=string.format(stationtxtsc,CallSign,self.callsigntxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT)
local TextTasking=string.format(stationtxtta,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT)
self:_NewRadioEntry(textTTS,textScreen,GID,isPlayer,isPlayer,true,false)
managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.StationZone)
if isAI then
local auftrag=managedgroup.FlightGroup:GetMissionCurrent()
if auftrag then
local auftragtype=auftrag:GetType()
if auftragtype==AUFTRAG.Type.ALERT5 then
local capauftrag=AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{})
capauftrag:SetMissionRange(self.MaxMissionRange)
capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
capauftrag:AddAsset(managedgroup.FlightGroup)
self.CatchAllMissions[#self.CatchAllMissions+1]=capauftrag
managedgroup.FlightGroup:AddMission(capauftrag)
auftrag:Cancel()
else
self:E("**** AssignedAnchor but Auftrag NOT ALERT5!")
end
else
self:E("**** AssignedAnchor but NO Auftrag!")
end
end
self.ManagedGrps[GID]=managedgroup
return self
end
function AWACS:onafterNewCluster(From,Event,To,Cluster)
self:T({From,Event,To,Cluster.index})
self.CID=self.CID+1
self.Countactcounter=self.Countactcounter+1
local ContactTable=Cluster.Contacts or{}
local function GetFirstAliveContact(table)
for _,_contact in pairs(table)do
local contact=_contact
if contact and contact.group and contact.group:IsAlive()then
return contact,contact.group
end
end
return nil
end
local Contact,Group=GetFirstAliveContact(ContactTable)
if not Contact then return self end
if Group and not Group:IsAirborne()then
return self
end
local targetset=SET_GROUP:New()
for _,_grp in pairs(ContactTable)do
local grp=_grp
targetset:AddGroup(grp.group,true)
end
local managedcontact={}
managedcontact.CID=self.CID
managedcontact.Contact=Contact
managedcontact.Cluster=Cluster
managedcontact.IFF=AWACS.IFF.BOGEY
managedcontact.Target=TARGET:New(targetset)
managedcontact.LinkedGroup=0
managedcontact.LinkedTask=0
managedcontact.Status=AWACS.TaskStatus.IDLE
local phoneid=math.fmod(self.Countactcounter,27)
if phoneid==0 then phoneid=1 end
managedcontact.TargetGroupNaming=AWACS.Phonetic[phoneid]
managedcontact.ReportingName=Contact.group:GetNatoReportingName()
managedcontact.TACCallDone=false
managedcontact.MeldCallDone=false
managedcontact.EngagementTag=""
local IsPopup=false
if self.OpsZone:IsVec2InZone(Contact.position:GetVec2())then
IsPopup=true
end
Contact.CID=managedcontact.CID
Contact.TargetGroupNaming=managedcontact.TargetGroupNaming
Cluster.CID=managedcontact.CID
Cluster.TargetGroupNaming=managedcontact.TargetGroupNaming
self.Contacts:Push(managedcontact,self.CID)
local ContactCoordinate=Contact.position:GetVec2()
local incontrolzone=self.ControlZone:IsVec2InZone(ContactCoordinate)
local distance=1000000
if not self.GCI then
distance=Contact.position:Get2DDistance(self.OrbitZone:GetCoordinate())
end
local inborderzone=false
if self.BorderZone then
inborderzone=self.BorderZone:IsVec2InZone(ContactCoordinate)
end
if incontrolzone or inborderzone or(distance<=UTILS.NMToMeters(55))or IsPopup then
self:_AnnounceContact(managedcontact,true,nil,false,managedcontact.TargetGroupNaming,IsPopup,managedcontact.ReportingName)
end
return self
end
function AWACS:onafterNewContact(From,Event,To,Contact)
self:T({From,Event,To,Contact})
local tdist=self.ThreatDistance
for _gid,_mgroup in pairs(self.ManagedGrps)do
local managedgroup=_mgroup
local group=managedgroup.Group
if group and group:IsAlive()and group:IsAirborne()then
local cpos=Contact.position or Contact.group:GetCoordinate()
local mpos=group:GetCoordinate()
local dist=cpos:Get2DDistance(mpos)
dist=UTILS.Round(UTILS.MetersToNM(dist),0)
if dist<=tdist then
self:_ThreatRangeCall(_gid,Contact)
end
end
end
return self
end
function AWACS:onafterLostContact(From,Event,To,Contact)
self:T({From,Event,To,Contact})
return self
end
function AWACS:onafterLostCluster(From,Event,To,Cluster,Mission)
self:T({From,Event,To})
return self
end
function AWACS:onafterCheckTacticalQueue(From,Event,To)
self:T({From,Event,To})
if self.clientset:CountAlive()==0 then
self:T(self.lid.."No player connected.")
self:__CheckTacticalQueue(-5)
return self
end
for _name,_freq in pairs(self.TacticalSubscribers)do
local Group=nil
if _name then
Group=GROUP:FindByName(_name)
end
if Group and Group:IsAlive()then
self:_BogeyDope(Group,true)
end
end
if(self.TacticalQueue:IsNotEmpty())then
while self.TacticalQueue:Count()>0 do
local RadioEntry=self.TacticalQueue:Pull()
self:T({RadioEntry})
local frequency=self.TacticalBaseFreq
if RadioEntry.GroupID and RadioEntry.GroupID~=0 then
local managedgroup=self.ManagedGrps[RadioEntry.GroupID]
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then
local name=managedgroup.GroupName
frequency=self.TacticalSubscribers[name]
end
end
local gtext=RadioEntry.TextTTS
if self.PathToGoogleKey then
gtext=string.format("<speak><prosody rate='medium'>%s</prosody></speak>",gtext)
end
self.TacticalSRSQ:NewTransmission(gtext,nil,self.TacticalSRS,nil,0.5,nil,nil,nil,frequency,self.TacticalModulation)
self:T(RadioEntry.TextTTS)
if RadioEntry.ToScreen and RadioEntry.TextScreen and(not self.SuppressScreenOutput)then
if RadioEntry.GroupID and RadioEntry.GroupID~=0 then
local managedgroup=self.ManagedGrps[RadioEntry.GroupID]
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then
MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group)
self:T(RadioEntry.TextScreen)
end
else
MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition)
end
end
end
end
if not self:Is("Stopped")then
self:__CheckTacticalQueue(-self.TacticalInterval)
end
return self
end
function AWACS:onafterCheckRadioQueue(From,Event,To)
self:T({From,Event,To})
local nextcall=10
if(self.RadioQueue:IsNotEmpty()or self.PrioRadioQueue:IsNotEmpty())then
local RadioEntry=nil
if self.PrioRadioQueue:IsNotEmpty()then
RadioEntry=self.PrioRadioQueue:Pull()
else
RadioEntry=self.RadioQueue:Pull()
end
self:T({RadioEntry})
if self.clientset:CountAlive()==0 then
self:T(self.lid.."No player connected.")
self:__CheckRadioQueue(-5)
return self
end
if not RadioEntry.FromAI then
if self.PathToGoogleKey then
local gtext=RadioEntry.TextTTS
gtext=string.format("<speak><prosody rate='medium'>%s</prosody></speak>",gtext)
self.AwacsSRS:PlayTextExt(gtext,nil,self.MultiFrequency,self.MultiModulation,self.Gender,self.Culture,self.Voice,self.Volume,"AWACS")
else
self.AwacsSRS:PlayTextExt(RadioEntry.TextTTS,nil,self.MultiFrequency,self.MultiModulation,self.Gender,self.Culture,self.Voice,self.Volume,"AWACS")
end
self:T(RadioEntry.TextTTS)
else
if RadioEntry.GroupID and RadioEntry.GroupID~=0 then
local managedgroup=self.ManagedGrps[RadioEntry.GroupID]
if managedgroup and managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive()then
if self.PathToGoogleKey then
local gtext=RadioEntry.TextTTS
gtext=string.format("<speak><prosody rate='medium'>%s</prosody></speak>",gtext)
managedgroup.FlightGroup:RadioTransmission(gtext,1,false)
else
managedgroup.FlightGroup:RadioTransmission(RadioEntry.TextTTS,1,false)
end
self:T(RadioEntry.TextTTS)
end
end
end
if RadioEntry.Duration then nextcall=RadioEntry.Duration end
if RadioEntry.ToScreen and RadioEntry.TextScreen and(not self.SuppressScreenOutput)then
if RadioEntry.GroupID and RadioEntry.GroupID~=0 then
local managedgroup=self.ManagedGrps[RadioEntry.GroupID]
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive()then
MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group)
self:T(RadioEntry.TextScreen)
end
else
MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition)
end
end
end
if self:Is("Running")then
if self.PathToGoogleKey then
nextcall=nextcall+self.GoogleTTSPadding
else
nextcall=nextcall+self.WindowsTTSPadding
end
self:__CheckRadioQueue(-nextcall)
end
return self
end
function AWACS:onafterEscortShiftChange(From,Event,To)
self:T({From,Event,To})
if self.AwacsFG and self.ShiftChangeEscortsFlag and not self.ShiftChangeEscortsRequested then
local awacs=self.AwacsFG:GetGroup()
if awacs and awacs:IsAlive()then
self.ShiftChangeEscortsRequested=true
self.EscortsTimeStamp=timer.getTime()
self:_StartEscorts(true)
else
self:E("**** AWACS group dead at onafterEscortShiftChange!")
end
end
return self
end
function AWACS:onafterAwacsShiftChange(From,Event,To)
self:T({From,Event,To})
if self.AwacsFG and self.ShiftChangeAwacsFlag and not self.ShiftChangeAwacsRequested then
self.ShiftChangeAwacsRequested=true
self.AwacsTimeStamp=timer.getTime()
local AwacsAW=self.AirWing
local mission=AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
self.CatchAllMissions[#self.CatchAllMissions+1]=mission
local timeonstation=(self.AwacsTimeOnStation+self.ShiftChangeTime)*3600
mission:SetTime(nil,timeonstation)
mission:SetMissionRange(self.MaxMissionRange)
AwacsAW:AddMission(mission)
self.AwacsMissionReplacement=mission
end
return self
end
function AWACS:onafterFlightOnMission(From,Event,To,FlightGroup,Mission)
self:T({From,Event,To})
self:T("FlightGroup "..FlightGroup:GetName().." Mission "..Mission:GetName().." Type "..Mission:GetType())
self.CatchAllFGs[#self.CatchAllFGs+1]=FlightGroup
if not self:Is("Stopped")then
if not self.AwacsReady or self.ShiftChangeAwacsFlag or self.ShiftChangeEscortsFlag then
self:_StartSettings(FlightGroup,Mission)
elseif Mission and(Mission:GetType()==AUFTRAG.Type.CAP or Mission:GetType()==AUFTRAG.Type.ALERT5 or Mission:GetType()==AUFTRAG.Type.ORBIT)then
if not self.FlightGroups:HasUniqueID(FlightGroup:GetName())then
self:T("Pushing FG "..FlightGroup:GetName().." to stack!")
self.FlightGroups:Push(FlightGroup,FlightGroup:GetName())
end
end
end
return self
end
function AWACS:onafterReAnchor(From,Event,To,GID)
self:T({From,Event,To,GID})
local managedgroup=self.ManagedGrps[GID]
if managedgroup then
if managedgroup.IsAI then
local AIFG=managedgroup.FlightGroup
if AIFG and AIFG:IsAlive()then
if AIFG:IsFuelLow()or AIFG:IsOutOfMissiles()or AIFG:IsOutOfAmmo()then
local destbase=AIFG.homebase
if not destbase then destbase=self.Airbase end
AIFG:RTB(destbase)
self:_CheckOut(AIFG:GetGroup(),GID)
self.AIRequested=self.AIRequested-1
else
local Anchor=self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo)
local StationZone=Anchor.StationZone
managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,"Re-Station AI",StationZone)
managedgroup.HasAssignedTask=true
local mission=AIFG:GetMissionCurrent()
if mission then
managedgroup.CurrentAuftrag=mission.auftragsnummer or 0
else
managedgroup.CurrentAuftrag=0
end
managedgroup.ContactCID=0
self.ManagedGrps[GID]=managedgroup
local tostation=self.gettext:GetEntry("VECTORSTATION",self.locale)
self:_MessageVector(GID,tostation,Anchor.StationZoneCoordinate,managedgroup.AnchorStackAngels)
end
else
local savedcallsign=managedgroup.CallSign
local textoptions={}
textoptions[1]=self.gettext:GetEntry("TEXTOPTIONS1",self.locale)
textoptions[2]=self.gettext:GetEntry("TEXTOPTIONS2",self.locale)
textoptions[3]=self.gettext:GetEntry("TEXTOPTIONS3",self.locale)
textoptions[4]=self.gettext:GetEntry("TEXTOPTIONS4",self.locale)
local allstations=self.gettext:GetEntry("ALLSTATIONS",self.locale)
local milestxt=self.gettext:GetEntry("MILES",self.locale)
if managedgroup.LastKnownPosition then
local lastknown=UTILS.DeepCopy(managedgroup.LastKnownPosition)
local faded=textoptions[math.random(1,4)]
local text=string.format("%s. %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign)
local textScreen=string.format("%s, %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign)
local brtext=self:_ToStringBULLS(lastknown)
local brtexttts=self:_ToStringBULLS(lastknown,false,true)
text=text.." "..brtexttts.." "..milestxt.."."
textScreen=textScreen.." "..brtext.." "..milestxt.."."
self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true)
end
self.ManagedGrps[GID]=nil
end
elseif managedgroup.IsPlayer then
local PLFG=managedgroup.Group
if PLFG and PLFG:IsAlive()then
local Anchor=self.AnchorStacks:ReadByPointer(managedgroup.AnchorStackNo)
local AnchorName=Anchor.StationName or"unknown"
local AnchorCoordTxt=Anchor.StationZoneCoordinateText or"unknown"
local Angels=managedgroup.AnchorStackAngels or 25
local AnchorSpeed=self.CapSpeedBase or 270
local StationZone=Anchor.StationZone
local ROEROT=self.AwacsROE.." "..self.AwacsROT
local stationtxt=self.gettext:GetEntry("STATIONTASK",self.locale)
local TextTasking=string.format(stationtxt,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT)
managedgroup.CurrentTask=self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,StationZone)
managedgroup.HasAssignedTask=true
managedgroup.ContactCID=0
self.ManagedGrps[GID]=managedgroup
local vectortxt=self.gettext:GetEntry("VECTORSTATION",self.locale)
self:_MessageVector(GID,vectortxt,Anchor.StationZoneCoordinate,managedgroup.AnchorStackAngels)
else
local savedcallsign=managedgroup.CallSign
local textoptions={}
textoptions[1]=self.gettext:GetEntry("TEXTOPTIONS1",self.locale)
textoptions[2]=self.gettext:GetEntry("TEXTOPTIONS2",self.locale)
textoptions[3]=self.gettext:GetEntry("TEXTOPTIONS3",self.locale)
textoptions[4]=self.gettext:GetEntry("TEXTOPTIONS4",self.locale)
local allstations=self.gettext:GetEntry("ALLSTATIONS",self.locale)
local milestxt=self.gettext:GetEntry("MILES",self.locale)
local faded=textoptions[math.random(1,4)]
local text=string.format("%s. %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign)
local textScreen=string.format("%s, %s. %s %s.",allstations,self.callsigntxt,faded,savedcallsign)
if managedgroup.LastKnownPosition then
local lastknown=UTILS.DeepCopy(managedgroup.LastKnownPosition)
local brtext=self:_ToStringBULLS(lastknown)
local brtexttts=self:_ToStringBULLS(lastknown,false,true)
text=text.." "..brtexttts.." "..milestxt.."."
textScreen=textScreen.." "..brtext.." "..milestxt.."."
self:_NewRadioEntry(text,textScreen,0,false,self.debug,true,false,true)
end
self.ManagedGrps[GID]=nil
end
end
end
end
end
BRIGADE={
ClassName="BRIGADE",
verbose=0,
rearmingZones={},
refuellingZones={},
}
BRIGADE.version="0.1.1"
function BRIGADE:New(WarehouseName,BrigadeName)
local self=BASE:Inherit(self,LEGION:New(WarehouseName,BrigadeName))
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!",WarehouseName))
return nil
end
self.lid=string.format("BRIGADE %s | ",self.alias)
self:SetRetreatZones()
if self:IsShip()then
local wh=self.warehouse
local group=wh:GetGroup()
self.warehouseOpsGroup=NAVYGROUP:New(group)
self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName())
end
self:AddTransition("*","ArmyOnMission","*")
return self
end
function BRIGADE:AddPlatoon(Platoon)
table.insert(self.cohorts,Platoon)
self:AddAssetToPlatoon(Platoon,Platoon.Ngroups)
Platoon:SetBrigade(self)
if Platoon:IsStopped()then
Platoon:Start()
end
return self
end
function BRIGADE:AddAssetToPlatoon(Platoon,Nassets)
if Platoon then
local Group=GROUP:FindByName(Platoon.templatename)
if Group then
local text=string.format("Adding asset %s to platoon %s",Group:GetName(),Platoon.name)
self:T(self.lid..text)
self:AddAsset(Group,Nassets,nil,nil,nil,nil,Platoon.skill,Platoon.livery,Platoon.name)
else
self:E(self.lid.."ERROR: Group does not exist!")
end
else
self:E(self.lid.."ERROR: Platoon does not exit!")
end
return self
end
function BRIGADE:SetRetreatZones(RetreatZoneSet)
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
return self
end
function BRIGADE:AddRetreatZone(RetreatZone)
self.retreatZones:AddZone(RetreatZone)
return self
end
function BRIGADE:GetRetreatZones()
return self.retreatZones
end
function BRIGADE:AddRearmingZone(RearmingZone)
local rearmingzone={}
rearmingzone.zone=RearmingZone
rearmingzone.mission=nil
rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(),"Rearming Zone"):ToCoalition(self:GetCoalition())
table.insert(self.rearmingZones,rearmingzone)
return rearmingzone
end
function BRIGADE:AddRefuellingZone(RefuellingZone)
local supplyzone={}
supplyzone.zone=RefuellingZone
supplyzone.mission=nil
supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(),"Refuelling Zone"):ToCoalition(self:GetCoalition())
table.insert(self.refuellingZones,supplyzone)
return supplyzone
end
function BRIGADE:GetPlatoon(PlatoonName)
local platoon=self:_GetCohort(PlatoonName)
return platoon
end
function BRIGADE:GetPlatoonOfAsset(Asset)
local platoon=self:GetPlatoon(Asset.squadname)
return platoon
end
function BRIGADE:RemoveAssetFromPlatoon(Asset)
local platoon=self:GetPlatoonOfAsset(Asset)
if platoon then
platoon:DelAsset(Asset)
end
end
function BRIGADE:LoadBackAssetInPosition(Templatename,Position)
self:T(self.lid.."LoadBackAssetInPosition: "..tostring(Templatename))
local nametbl=UTILS.Split(Templatename,"_")
local name=nametbl[1]
self:T(string.format("*** Target Platoon = %s ***",name))
local cohorts=self.cohorts or{}
local thisasset=nil
local found=false
for _,_cohort in pairs(cohorts)do
local asset=_cohort:GetName()
self:T(string.format("*** Looking at Platoon = %s ***",asset))
if asset==name then
self:T("**** Found Platoon ****")
local cohassets=_cohort.assets or{}
for _,_zug in pairs(cohassets)do
local zug=_zug
if zug.assignment==name and zug.requested==false then
self:T("**** Found Asset ****")
found=true
thisasset=zug
break
end
end
end
end
if found then
thisasset.rid=thisasset.uid
thisasset.requested=false
thisasset.score=100
thisasset.missionTask="CAS"
thisasset.spawned=true
local template=thisasset.templatename
local alias=thisasset.spawngroupname
local spawnasset=SPAWN:NewWithAlias(template,alias)
:InitDelayOff()
:SpawnFromCoordinate(Position)
local request={}
request.assignment=name
request.warehouse=self
request.assets={thisasset}
request.ntransporthome=0
request.ndelivered=0
request.ntransport=0
request.cargoattribute=thisasset.attribute
request.category=thisasset.category
request.cargoassets={thisasset}
request.assetdesc=WAREHOUSE.Descriptor.ASSETLIST
request.cargocategory=thisasset.category
request.toself=true
request.transporttype=WAREHOUSE.TransportType.SELFPROPELLED
request.assetproblem={}
request.born=true
request.prio=50
request.uid=thisasset.uid
request.airbase=nil
request.timestamp=timer.getAbsTime()
request.assetdescval={thisasset}
request.nasset=1
request.cargogroupset=SET_GROUP:New()
request.cargogroupset:AddGroup(spawnasset)
request.iscargo=true
self:__AssetSpawned(2,spawnasset,thisasset,request)
end
return self
end
function BRIGADE:onafterStart(From,Event,To)
self:GetParent(self,BRIGADE).onafterStart(self,From,Event,To)
self:I(self.lid..string.format("Starting BRIGADE v%s",BRIGADE.version))
end
function BRIGADE:onafterStatus(From,Event,To)
self:GetParent(self).onafterStatus(self,From,Event,To)
local fsmstate=self:GetState()
self:CheckTransportQueue()
self:CheckMissionQueue()
for _,_rearmingzone in pairs(self.rearmingZones)do
local rearmingzone=_rearmingzone
if(not rearmingzone.mission)or rearmingzone.mission:IsOver()then
rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone)
self:AddMission(rearmingzone.mission)
end
end
for _,_supplyzone in pairs(self.refuellingZones)do
local supplyzone=_supplyzone
if(not supplyzone.mission)or supplyzone.mission:IsOver()then
supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone)
self:AddMission(supplyzone.mission)
end
end
self:_TacticalOverview()
if self.verbose>=1 then
local Nmissions=self:CountMissionsInQueue()
local Npq,Np,Nq=self:CountAssetsOnMission()
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]",self:CountAssets(),Npq,Np,Nq)
local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s",fsmstate,Nmissions,#self.cohorts,assets)
self:I(self.lid..text)
end
if self.verbose>=2 then
local text=string.format("Missions Total=%d:",#self.missionqueue)
for i,_mission in pairs(self.missionqueue)do
local mission=_mission
local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end
local assets=string.format("%d/%d",mission:CountOpsGroups(),mission.Nassets or 0)
local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage())
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mission.status,prio,assets,target)
end
self:I(self.lid..text)
end
if self.verbose>=2 then
local text=string.format("Transports Total=%d:",#self.transportqueue)
for i,_transport in pairs(self.transportqueue)do
local transport=_transport
local prio=string.format("%d/%s",transport.prio,tostring(transport.importance));if transport.urgent then prio=prio.." (!)"end
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d",transport.Ncargo,transport.Ndelivered,transport.Ncarrier)
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s",i,transport.uid,transport:GetState(),prio,carriers)
end
self:I(self.lid..text)
end
if self.verbose>=3 then
local text="Platoons:"
for i,_platoon in pairs(self.cohorts)do
local platoon=_platoon
local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName)or"N/A"
local modex=platoon.modex and platoon.modex or-1
local skill=platoon.skill and tostring(platoon.skill)or"N/A"
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",platoon.name,platoon:GetState(),platoon.aircrafttype,platoon:CountAssets(true),#platoon.assets,callsign,modex,skill)
end
self:I(self.lid..text)
end
if self.verbose>=4 then
local text="Rearming Zones:"
for i,_rearmingzone in pairs(self.rearmingZones)do
local rearmingzone=_rearmingzone
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d",rearmingzone.zone:GetName(),rearmingzone.mission:GetState(),rearmingzone.mission:CountOpsGroups())
end
self:I(self.lid..text)
end
if self.verbose>=4 then
local text="Refuelling Zones:"
for i,_refuellingzone in pairs(self.refuellingZones)do
local refuellingzone=_refuellingzone
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d",refuellingzone.zone:GetName(),refuellingzone.mission:GetState(),refuellingzone.mission:CountOpsGroups())
end
self:I(self.lid..text)
end
if self.verbose>=5 then
local text="Assets in stock:"
for i,_asset in pairs(self.stock)do
local asset=_asset
text=text..string.format("\n* %s: spawned=%s",asset.spawngroupname,tostring(asset.spawned))
end
self:I(self.lid..text)
end
end
function BRIGADE:onafterArmyOnMission(From,Event,To,ArmyGroup,Mission)
self:T(self.lid..string.format("Group %s on %s mission %s",ArmyGroup:GetName(),Mission:GetType(),Mission:GetName()))
end
CHIEF={
ClassName="CHIEF",
verbose=0,
lid=nil,
targetqueue={},
zonequeue={},
borderzoneset=nil,
yellowzoneset=nil,
engagezoneset=nil,
tacview=false,
Nsuccess=0,
Nfailure=0,
}
CHIEF.DEFCON={
GREEN="Green",
YELLOW="Yellow",
RED="Red",
}
CHIEF.Strategy={
PASSIVE="Passive",
DEFENSIVE="Defensive",
OFFENSIVE="Offensive",
AGGRESSIVE="Aggressive",
TOTALWAR="Total War"
}
CHIEF.version="0.6.1"
function CHIEF:New(Coalition,AgentSet,Alias)
Alias=Alias or"CHIEF"
if type(Coalition)=="string"then
if string.lower(Coalition)=="blue"then
Coalition=coalition.side.BLUE
elseif string.lower(Coalition)=="red"then
Coalition=coalition.side.RED
else
Coalition=coalition.side.NEUTRAL
end
end
local self=BASE:Inherit(self,INTEL:New(AgentSet,Coalition,Alias))
self:SetBorderZones()
self:SetConflictZones()
self:SetAttackZones()
self:SetThreatLevelRange()
self.Defcon=CHIEF.DEFCON.GREEN
self.strategy=CHIEF.Strategy.DEFENSIVE
self.TransportCategories={Group.Category.HELICOPTER}
self.commander=COMMANDER:New(Coalition)
self:AddTransition("*","MissionAssign","*")
self:AddTransition("*","MissionCancel","*")
self:AddTransition("*","TransportCancel","*")
self:AddTransition("*","OpsOnMission","*")
self:AddTransition("*","ZoneCaptured","*")
self:AddTransition("*","ZoneLost","*")
self:AddTransition("*","ZoneEmpty","*")
self:AddTransition("*","ZoneAttacked","*")
self:AddTransition("*","DefconChange","*")
self:AddTransition("*","StrategyChange","*")
self:AddTransition("*","LegionLost","*")
return self
end
function CHIEF:SetAirToAny()
self:SetFilterCategory({})
return self
end
function CHIEF:SetAirToAir()
self:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
return self
end
function CHIEF:SetAirToGround()
self:SetFilterCategory({Unit.Category.GROUND_UNIT})
return self
end
function CHIEF:SetAirToSea()
self:SetFilterCategory({Unit.Category.SHIP})
return self
end
function CHIEF:SetAirToSurface()
self:SetFilterCategory({Unit.Category.GROUND_UNIT,Unit.Category.SHIP})
return self
end
function CHIEF:SetThreatLevelRange(ThreatLevelMin,ThreatLevelMax)
self.threatLevelMin=ThreatLevelMin or 1
self.threatLevelMax=ThreatLevelMax or 10
return self
end
function CHIEF:SetDefcon(Defcon)
local gotit=false
for _,defcon in pairs(CHIEF.DEFCON)do
if defcon==Defcon then
gotit=true
end
end
if not gotit then
self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s",tostring(Defcon)))
return self
end
if Defcon~=self.Defcon then
self:DefconChange(Defcon)
end
self.Defcon=Defcon
return self
end
function CHIEF:CreateResource(MissionType,Nmin,Nmax,Attributes,Properties,Categories)
local resources={}
local resource=self:AddToResource(resources,MissionType,Nmin,Nmax,Attributes,Properties,Categories)
return resources,resource
end
function CHIEF:AddToResource(Resource,MissionType,Nmin,Nmax,Attributes,Properties,Categories)
local resource={}
resource.MissionType=MissionType
resource.Nmin=Nmin or 1
resource.Nmax=Nmax or Nmin
resource.Attributes=UTILS.EnsureTable(Attributes,true)
resource.Properties=UTILS.EnsureTable(Properties,true)
resource.Categories=UTILS.EnsureTable(Categories,true)
resource.carrierNmin=nil
resource.carrierNmax=nil
resource.carrierAttributes=nil
resource.carrierProperties=nil
resource.carrierCategories=nil
table.insert(Resource,resource)
if self.verbose>10 then
local text="Resource:"
for _,_r in pairs(Resource)do
local r=_r
text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s",r.MissionType,r.Nmin,r.Nmax,tostring(r.Attributes[1]),tostring(r.Properties[1]))
end
self:I(self.lid..text)
end
return resource
end
function CHIEF:AddTransportToResource(Resource,Nmin,Nmax,CarrierAttributes,CarrierProperties,CarrierCategories)
Resource.carrierNmin=Nmin or 1
Resource.carrierNmax=Nmax or Nmin
Resource.carrierCategories=UTILS.EnsureTable(CarrierCategories,true)
Resource.carrierAttributes=UTILS.EnsureTable(CarrierAttributes,true)
Resource.carrierProperties=UTILS.EnsureTable(CarrierProperties,true)
return self
end
function CHIEF:DeleteFromResource(Resource,MissionType)
for i=#Resource,1,-1 do
local resource=Resource[i]
if resource.MissionType==MissionType then
if resource.mission and resource.mission:IsNotOver()then
resource.mission:Cancel()
end
table.remove(Resource,i)
end
end
return self
end
function CHIEF:SetResponseOnTarget(NassetsMin,NassetsMax,ThreatLevel,TargetCategory,MissionType,Nunits,Defcon,Strategy)
local bla={}
bla.nAssetMin=NassetsMin or 1
bla.nAssetMax=NassetsMax or bla.nAssetMin
bla.threatlevel=ThreatLevel or 0
bla.targetCategory=TargetCategory
bla.missionType=MissionType
bla.nUnits=Nunits or 1
bla.defcon=Defcon
bla.strategy=Strategy
self.assetNumbers=self.assetNumbers or{}
table.insert(self.assetNumbers,bla)
end
function CHIEF:_GetAssetsForTarget(Target,MissionType)
local threatlevel=Target:GetThreatLevelMax()
local nUnits=Target.N0
local targetcategory=Target:GetCategory()
self:T(self.lid..string.format("Getting number of assets for target with TL=%d, Category=%s, nUnits=%s, MissionType=%s",threatlevel,targetcategory,nUnits,tostring(MissionType)))
local candidates={}
local threatlevelMatch=nil
for _,_assetnumber in pairs(self.assetNumbers or{})do
local assetnumber=_assetnumber
if(threatlevelMatch==nil and threatlevel>=assetnumber.threatlevel)or(threatlevelMatch~=nil and threatlevelMatch==threatlevel)then
if threatlevelMatch==nil then
threatlevelMatch=threatlevel
end
local nMatch=0
local cand=true
if assetnumber.targetCategory~=nil then
if assetnumber.targetCategory==targetcategory then
nMatch=nMatch+1
else
cand=false
end
end
if MissionType and assetnumber.missionType~=nil then
if assetnumber.missionType==MissionType then
nMatch=nMatch+1
else
cand=false
end
end
if assetnumber.nUnits~=nil then
if assetnumber.nUnits>=nUnits then
nMatch=nMatch+1
else
cand=false
end
end
if assetnumber.defcon~=nil then
if assetnumber.defcon==self.Defcon then
nMatch=nMatch+1
else
cand=false
end
end
if assetnumber.strategy~=nil then
if assetnumber.strategy==self.strategy then
nMatch=nMatch+1
else
cand=false
end
end
if cand then
table.insert(candidates,{assetnumber=assetnumber,nMatch=nMatch})
end
end
end
if#candidates>0 then
local function _sort(a,b)
return a.nMatch>b.nMatch
end
table.sort(candidates,_sort)
local candidate=candidates[1]
local an=candidate.assetnumber
self:T(self.lid..string.format("Picking candidate with %d matches: NassetsMin=%d, NassetsMax=%d, ThreatLevel=%d, TargetCategory=%s, MissionType=%s, Defcon=%s, Strategy=%s",
candidate.nMatch,an.nAssetMin,an.nAssetMax,an.threatlevel,tostring(an.targetCategory),tostring(an.missionType),tostring(an.defcon),tostring(an.strategy)))
return an.nAssetMin,an.nAssetMax
else
return 1,1
end
end
function CHIEF:GetDefcon(Defcon)
return self.Defcon
end
function CHIEF:SetLimitMission(Limit,MissionType)
self.commander:SetLimitMission(Limit,MissionType)
return self
end
function CHIEF:SetTacticalOverviewOn()
self.tacview=true
return self
end
function CHIEF:SetTacticalOverviewOff()
self.tacview=false
return self
end
function CHIEF:SetStrategy(Strategy)
if Strategy~=self.strategy then
self:StrategyChange(Strategy)
end
self.strategy=Strategy
return self
end
function CHIEF:GetStrategy()
return self.strategy
end
function CHIEF:GetDefcon(Defcon)
return self.Defcon
end
function CHIEF:GetCommander()
return self.commander
end
function CHIEF:AddAirwing(Airwing)
self:AddLegion(Airwing)
return self
end
function CHIEF:AddBrigade(Brigade)
self:AddLegion(Brigade)
return self
end
function CHIEF:AddFleet(Fleet)
self:AddLegion(Fleet)
return self
end
function CHIEF:AddLegion(Legion)
Legion.chief=self
self.commander:AddLegion(Legion)
return self
end
function CHIEF:RemoveLegion(Legion)
Legion.chief=nil
self.commander:RemoveLegion(Legion)
return self
end
function CHIEF:AddMission(Mission)
Mission.chief=self
Mission.statusChief=AUFTRAG.Status.PLANNED
self:I(self.lid..string.format("Adding mission #%d",Mission.auftragsnummer))
self.commander:AddMission(Mission)
return self
end
function CHIEF:RemoveMission(Mission)
Mission.chief=nil
self.commander:RemoveMission(Mission)
return self
end
function CHIEF:AddOpsTransport(Transport)
Transport.chief=self
self.commander:AddOpsTransport(Transport)
return self
end
function CHIEF:RemoveTransport(Transport)
Transport.chief=nil
self.commander:RemoveTransport(Transport)
return self
end
function CHIEF:AddTarget(Target)
if not self:IsTarget(Target)then
Target.chief=self
table.insert(self.targetqueue,Target)
end
return self
end
function CHIEF:IsTarget(Target)
for _,_target in pairs(self.targetqueue)do
local target=_target
if target.uid==Target.uid or target:GetName()==Target:GetName()then
return true
end
end
return false
end
function CHIEF:RemoveTarget(Target)
for i,_target in pairs(self.targetqueue)do
local target=_target
if target.uid==Target.uid then
self:T(self.lid..string.format("Removing target %s from queue",Target.name))
table.remove(self.targetqueue,i)
break
end
end
return self
end
function CHIEF:AddStrategicZone(OpsZone,Priority,Importance,ResourceOccupied,ResourceEmpty)
local stratzone={}
stratzone.opszone=OpsZone
stratzone.prio=Priority or 50
stratzone.importance=Importance
stratzone.missions={}
if OpsZone:IsStopped()then
OpsZone:Start()
end
if ResourceOccupied then
stratzone.resourceOccup=UTILS.DeepCopy(ResourceOccupied)
else
stratzone.resourceOccup=self:CreateResource(AUFTRAG.Type.ARTY,1,2)
self:AddToResource(stratzone.resourceOccup,AUFTRAG.Type.CASENHANCED,1,2)
end
if ResourceEmpty then
stratzone.resourceEmpty=UTILS.DeepCopy(ResourceEmpty)
else
local resourceEmpty,resourceInfantry=self:CreateResource(AUFTRAG.Type.ONGUARD,1,3,GROUP.Attribute.GROUND_INFANTRY)
self:AddToResource(resourceEmpty,AUFTRAG.Type.ONGUARD,0,1,GROUP.Attribute.GROUND_TANK)
self:AddToResource(resourceEmpty,AUFTRAG.Type.ONGUARD,0,1,GROUP.Attribute.GROUND_IFV)
self:AddTransportToResource(resourceInfantry,0,1,{GROUP.Attribute.AIR_TRANSPORTHELO,GROUP.Attribute.GROUND_APC})
stratzone.resourceEmpty=resourceEmpty
end
table.insert(self.zonequeue,stratzone)
OpsZone:_AddChief(self)
return stratzone
end
function CHIEF:SetStrategicZoneResourceEmpty(StrategicZone,Resource,NoCopy)
if NoCopy then
StrategicZone.resourceEmpty=Resource
else
StrategicZone.resourceEmpty=UTILS.DeepCopy(Resource)
end
return self
end
function CHIEF:SetStrategicZoneResourceOccupied(StrategicZone,Resource,NoCopy)
if NoCopy then
StrategicZone.resourceOccup=Resource
else
StrategicZone.resourceOccup=UTILS.DeepCopy(Resource)
end
return self
end
function CHIEF:GetStrategicZoneResourceEmpty(StrategicZone)
return StrategicZone.resourceEmpty
end
function CHIEF:GetStrategicZoneResourceOccupied(StrategicZone)
return StrategicZone.resourceOccup
end
function CHIEF:RemoveStrategicZone(OpsZone,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,CHIEF.RemoveStrategicZone,self,OpsZone)
else
for i=#self.zonequeue,1,-1 do
local stratzone=self.zonequeue[i]
if OpsZone.zoneName==stratzone.opszone.zoneName then
self:T(self.lid..string.format("Removing OPS zone \"%s\" from queue! All running missions will be cancelled",OpsZone.zoneName))
for _,_resource in pairs(stratzone.resourceEmpty)do
local resource=_resource
if resource.mission and resource.mission:IsNotOver()then
resource.mission:Cancel()
end
end
for _,_resource in pairs(stratzone.resourceOccup)do
local resource=_resource
if resource.mission and resource.mission:IsNotOver()then
resource.mission:Cancel()
end
end
table.remove(self.zonequeue,i)
return self
end
end
end
return self
end
function CHIEF:AddRearmingZone(RearmingZone)
local supplyzone=self.commander:AddRearmingZone(RearmingZone)
return supplyzone
end
function CHIEF:AddRefuellingZone(RefuellingZone)
local supplyzone=self.commander:AddRefuellingZone(RefuellingZone)
return supplyzone
end
function CHIEF:AddCapZone(Zone,Altitude,Speed,Heading,Leg)
local zone=self.commander:AddCapZone(Zone,Altitude,Speed,Heading,Leg)
return zone
end
function CHIEF:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg)
local zone=self.commander:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg)
return zone
end
function CHIEF:RemoveGciCapZone(Zone)
local zone=self.commander:RemoveGciCapZone(Zone)
return zone
end
function CHIEF:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg)
local zone=self.commander:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg)
return zone
end
function CHIEF:RemoveAwacsZone(Zone)
local zone=self.commander:RemoveAwacsZone(Zone)
return zone
end
function CHIEF:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem)
local zone=self.commander:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem)
return zone
end
function CHIEF:RemoveTankerZone(Zone)
local zone=self.commander:RemoveTankerZone(Zone)
return zone
end
function CHIEF:SetBorderZones(BorderZoneSet)
self.borderzoneset=BorderZoneSet or SET_ZONE:New()
return self
end
function CHIEF:AddBorderZone(Zone)
self.borderzoneset:AddZone(Zone)
return self
end
function CHIEF:RemoveBorderZone(Zone)
self.borderzoneset:Remove(Zone:GetName())
return self
end
function CHIEF:SetConflictZones(ZoneSet)
self.yellowzoneset=ZoneSet or SET_ZONE:New()
return self
end
function CHIEF:AddConflictZone(Zone)
self.yellowzoneset:AddZone(Zone)
return self
end
function CHIEF:RemoveConflictZone(Zone)
self.yellowzoneset:Remove(Zone:GetName())
return self
end
function CHIEF:SetAttackZones(ZoneSet)
self.engagezoneset=ZoneSet or SET_ZONE:New()
return self
end
function CHIEF:AddAttackZone(Zone)
self.engagezoneset:AddZone(Zone)
return self
end
function CHIEF:RemoveAttackZone(Zone)
self.engagezoneset:Remove(Zone:GetName())
return self
end
function CHIEF:AllowGroundTransport()
env.warning("WARNING: CHIEF:AllowGroundTransport() is deprecated and will be removed in the future!")
self.TransportCategories={Group.Category.GROUND,Group.Category.HELICOPTER}
return self
end
function CHIEF:ForbidGroundTransport()
env.warning("WARNING: CHIEF:ForbidGroundTransport() is deprecated and will be removed in the future!")
self.TransportCategories={Group.Category.HELICOPTER}
return self
end
function CHIEF:IsPassive()
return self.strategy==CHIEF.Strategy.PASSIVE
end
function CHIEF:IsDefensive()
return self.strategy==CHIEF.Strategy.DEFENSIVE
end
function CHIEF:IsOffensive()
return self.strategy==CHIEF.Strategy.OFFENSIVE
end
function CHIEF:IsAgressive()
return self.strategy==CHIEF.Strategy.AGGRESSIVE
end
function CHIEF:IsTotalWar()
return self.strategy==CHIEF.Strategy.TOTALWAR
end
function CHIEF:onafterStart(From,Event,To)
local text=string.format("Starting Chief of Staff")
self:I(self.lid..text)
self:GetParent(self).onafterStart(self,From,Event,To)
if self.commander then
if self.commander:GetState()=="NotReadyYet"then
self.commander:Start()
end
end
end
function CHIEF:onafterStatus(From,Event,To)
self:GetParent(self).onafterStatus(self,From,Event,To)
local fsmstate=self:GetState()
for _,_contact in pairs(self.ContactsLost)do
local contact=_contact
if contact.mission and contact.mission:IsNotOver()then
local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.",contact.groupname,contact.mission.type:upper(),contact.mission.name)
MESSAGE:New(text,120,"CHIEF"):ToAll()
self:T(self.lid..text)
contact.mission:Cancel()
end
if contact.target then
self:RemoveTarget(contact.target)
end
end
self.Nborder=0;self.Nconflict=0;self.Nattack=0
for _,_contact in pairs(self.Contacts)do
local contact=_contact
local group=contact.group
local inred=self:CheckGroupInBorder(group)
if inred then
self.Nborder=self.Nborder+1
end
local inyellow=self:CheckGroupInConflict(group)
if inyellow then
self.Nconflict=self.Nconflict+1
end
local inattack=self:CheckGroupInAttack(group)
if inattack then
self.Nattack=self.Nattack+1
end
if not contact.target then
local Target=TARGET:New(contact.group)
contact.target=Target
Target.contact=contact
self:AddTarget(Target)
end
end
if self.Nborder>0 then
self:SetDefcon(CHIEF.DEFCON.RED)
elseif self.Nconflict>0 then
self:SetDefcon(CHIEF.DEFCON.YELLOW)
else
self:SetDefcon(CHIEF.DEFCON.GREEN)
end
self:CheckTargetQueue()
for _,_target in pairs(self.targetqueue)do
local target=_target
if target and target:IsAlive()and target.chief and target.mission and target.mission:IsNotOver()then
local inborder=self:CheckTargetInZones(target,self.borderzoneset)
local inyellow=self:CheckTargetInZones(target,self.yellowzoneset)
local inattack=self:CheckTargetInZones(target,self.engagezoneset)
if self.strategy==CHIEF.Strategy.PASSIVE then
self:T(self.lid..string.format("Cancelling mission for target %s as strategy is PASSIVE",target:GetName()))
target.mission:Cancel()
elseif self.strategy==CHIEF.Strategy.DEFENSIVE then
if not inborder then
self:T(self.lid..string.format("Cancelling mission for target %s as strategy is DEFENSIVE and not inside border",target:GetName()))
target.mission:Cancel()
end
elseif self.strategy==CHIEF.Strategy.OFFENSIVE then
if not(inborder or inyellow)then
self:T(self.lid..string.format("Cancelling mission for target %s as strategy is OFFENSIVE and not inside border or conflict",target:GetName()))
target.mission:Cancel()
end
elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then
if not(inborder or inyellow or inattack)then
self:T(self.lid..string.format("Cancelling mission for target %s as strategy is AGGRESSIVE and not inside border, conflict or attack",target:GetName()))
target.mission:Cancel()
end
elseif self.strategy==CHIEF.Strategy.TOTALWAR then
end
end
end
self:CheckOpsZoneQueue()
self:_TacticalOverview()
if self.verbose>=1 then
local Nassets=self.commander:CountAssets()
local Ncontacts=#self.Contacts
local Nmissions=#self.commander.missionqueue
local Ntargets=#self.targetqueue
local text=string.format("Defcon=%s Strategy=%s: Assets=%d, Contacts=%d [Border=%d, Conflict=%d, Attack=%d], Targets=%d, Missions=%d",
self.Defcon,self.strategy,Nassets,Ncontacts,self.Nborder,self.Nconflict,self.Nattack,Ntargets,Nmissions)
self:I(self.lid..text)
end
if self.verbose>=2 and#self.Contacts>0 then
local text="Contacts:"
for i,_contact in pairs(self.Contacts)do
local contact=_contact
local mtext="N/A"
if contact.mission then
mtext=string.format("\"%s\" [%s] %s",contact.mission:GetName(),contact.mission:GetType(),contact.mission.status:upper())
end
text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s",i,contact.groupname,contact.categoryname,contact.typename,contact.threatlevel,mtext)
end
self:I(self.lid..text)
end
if self.verbose>=3 and#self.targetqueue>0 then
local text="Targets:"
for i,_target in pairs(self.targetqueue)do
local target=_target
local mtext="N/A"
if target.mission then
mtext=string.format("\"%s\" [%s] %s",target.mission:GetName(),target.mission:GetType(),target.mission.status:upper())
end
text=text..string.format("\n[%d] %s: Category=%s, prio=%d, importance=%d, alive=%s [%.1f/%.1f], Mission=%s",
i,target:GetName(),target.category,target.prio,target.importance or-1,tostring(target:IsAlive()),target:GetLife(),target:GetLife0(),mtext)
end
self:I(self.lid..text)
end
if self.verbose>=4 and#self.commander.missionqueue>0 then
local text="Mission queue:"
for i,_mission in pairs(self.commander.missionqueue)do
local mission=_mission
local target=mission:GetTargetName()or"unknown"
text=text..string.format("\n[%d] %s (%s): status=%s, target=%s",i,mission.name,mission.type,mission.status,target)
end
self:I(self.lid..text)
end
if self.verbose>=4 and#self.zonequeue>0 then
local text="Zone queue:"
for i,_stratzone in pairs(self.zonequeue)do
local stratzone=_stratzone
local opszone=stratzone.opszone
local owner=UTILS.GetCoalitionName(opszone.ownerCurrent)
local prevowner=UTILS.GetCoalitionName(opszone.ownerPrevious)
text=text..string.format("\n[%d] %s [%s]: owner=%s [%s] (prio=%d, importance=%s): Blue=%d, Red=%d, Neutral=%d",
i,opszone.zone:GetName(),opszone:GetState(),owner,prevowner,stratzone.prio,tostring(stratzone.importance),opszone.Nblu,opszone.Nred,opszone.Nnut)
end
self:I(self.lid..text)
end
if self.verbose>=5 then
local text="Assets:"
for _,missiontype in pairs(AUFTRAG.Type)do
local N=self.commander:CountAssets(nil,missiontype)
if N>0 then
text=text..string.format("\n- %s: %d",missiontype,N)
end
end
self:I(self.lid..text)
local text="Assets:"
for _,attribute in pairs(WAREHOUSE.Attribute)do
local N=self.commander:CountAssets(nil,nil,attribute)
if N>0 or self.verbose>=10 then
text=text..string.format("\n- %s: %d",attribute,N)
end
end
self:T(self.lid..text)
end
end
function CHIEF:onafterMissionAssign(From,Event,To,Mission,Legions)
if self.commander then
self:T(self.lid..string.format("Assigning mission %s (%s) to COMMANDER",Mission.name,Mission.type))
Mission.chief=self
Mission.statusChief=AUFTRAG.Status.QUEUED
self.commander:MissionAssign(Mission,Legions)
else
self:E(self.lid..string.format("Mission cannot be assigned as no COMMANDER is defined!"))
end
end
function CHIEF:onafterMissionCancel(From,Event,To,Mission)
self:T(self.lid..string.format("Cancelling mission %s (%s) in status %s",Mission.name,Mission.type,Mission.status))
Mission.statusChief=AUFTRAG.Status.CANCELLED
if Mission:IsPlanned()then
self:RemoveMission(Mission)
else
if Mission.commander then
Mission.commander:MissionCancel(Mission)
end
end
end
function CHIEF:onafterTransportCancel(From,Event,To,Transport)
self:T(self.lid..string.format("Cancelling transport UID=%d in status %s",Transport.uid,Transport:GetState()))
if Transport:IsPlanned()then
self:RemoveTransport(Transport)
else
if Transport.commander then
Transport.commander:TransportCancel(Transport)
end
end
end
function CHIEF:onafterDefconChange(From,Event,To,Defcon)
self:T(self.lid..string.format("Changing Defcon from %s --> %s",self.Defcon,Defcon))
end
function CHIEF:onafterStrategyChange(From,Event,To,Strategy)
self:T(self.lid..string.format("Changing Strategy from %s --> %s",self.strategy,Strategy))
end
function CHIEF:onafterOpsOnMission(From,Event,To,OpsGroup,Mission)
self:T(self.lid..string.format("Group %s on mission %s [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType()))
end
function CHIEF:onafterZoneCaptured(From,Event,To,OpsZone)
self:T(self.lid..string.format("Zone %s captured!",OpsZone:GetName()))
end
function CHIEF:onafterZoneLost(From,Event,To,OpsZone)
self:T(self.lid..string.format("Zone %s lost!",OpsZone:GetName()))
end
function CHIEF:onafterZoneEmpty(From,Event,To,OpsZone)
self:T(self.lid..string.format("Zone %s empty!",OpsZone:GetName()))
end
function CHIEF:onafterZoneAttacked(From,Event,To,OpsZone)
self:T(self.lid..string.format("Zone %s attacked!",OpsZone:GetName()))
end
function CHIEF:_TacticalOverview()
if self.tacview then
local NassetsTotal=self.commander:CountAssets()
local NassetsStock=self.commander:CountAssets(true)
local Ncontacts=#self.Contacts
local NmissionsTotal=#self.commander.missionqueue
local NmissionsRunni=self.commander:CountMissions(AUFTRAG.Type,true)
local Ntargets=#self.targetqueue
local Nzones=#self.zonequeue
local Nagents=self.detectionset:CountAlive()
local text=string.format("Tactical Overview\n")
text=text..string.format("=================\n")
text=text..string.format("Strategy: %s - Defcon: %s - Agents=%s\n",self.strategy,self.Defcon,Nagents)
text=text..string.format("Contacts: %d [Border=%d, Conflict=%d, Attack=%d]\n",Ncontacts,self.Nborder,self.Nconflict,self.Nattack)
text=text..string.format("Assets: %d [Active=%d, Stock=%d]\n",NassetsTotal,NassetsTotal-NassetsStock,NassetsStock)
text=text..string.format("Targets: %d\n",Ntargets)
text=text..string.format("Missions: %d [Running=%d/%d - Success=%d, Failure=%d]\n",NmissionsTotal,NmissionsRunni,self:GetMissionLimit("Total"),self.Nsuccess,self.Nfailure)
for _,mtype in pairs(AUFTRAG.Type)do
local n=self.commander:CountMissions(mtype)
if n>0 then
local N=self.commander:CountMissions(mtype,true)
local limit=self:GetMissionLimit(mtype)
text=text..string.format("  - %s: %d [Running=%d/%d]\n",mtype,n,N,limit)
end
end
text=text..string.format("Strategic Zones: %d\n",Nzones)
for _,_stratzone in pairs(self.zonequeue)do
local stratzone=_stratzone
local owner=stratzone.opszone:GetOwnerName()
text=text..string.format("  - %s: %s - %s [I=%d, P=%d]\n",stratzone.opszone:GetName(),owner,stratzone.opszone:GetState(),stratzone.importance or 0,stratzone.prio or 0)
end
local Ntransports=#self.commander.transportqueue
if Ntransports>0 then
text=text..string.format("Transports: %d\n",Ntransports)
for _,_transport in pairs(self.commander.transportqueue)do
local transport=_transport
text=text..string.format(" - %s",transport:GetState())
end
end
MESSAGE:New(text,60,nil,true):ToCoalition(self.coalition)
if self.verbose>=4 then
self:I(self.lid..text)
end
end
end
function CHIEF:CheckTargetQueue()
local Ntargets=#self.targetqueue
if Ntargets==0 then
return nil
end
local NoLimit=self:_CheckMissionLimit("Total")
if NoLimit==false then
return nil
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.threatlevel0>taskB.threatlevel0)
end
table.sort(self.targetqueue,_sort)
local vip=math.huge
for _,_target in pairs(self.targetqueue)do
local target=_target
if target:IsAlive()and target.importance and target.importance<vip then
vip=target.importance
end
end
for _,_target in pairs(self.targetqueue)do
local target=_target
local isAlive=target:IsAlive()
local isImportant=(target.importance==nil or target.importance<=vip)
local threatlevel=target:GetThreatLevelMax()
local isThreat=threatlevel>=self.threatLevelMin and threatlevel<=self.threatLevelMax
if target.category==TARGET.Category.AIRBASE or target.category==TARGET.Category.ZONE or target.Category==TARGET.Category.COORDINATE then
isThreat=true
end
local text=string.format("Target %s: Alive=%s, Threat=%s, Important=%s",target:GetName(),tostring(isAlive),tostring(isThreat),tostring(isImportant))
if target.mission then
text=text..string.format(", Mission \"%s\" (%s) [%s]",target.mission:GetName(),target.mission:GetState(),target.mission:GetType())
if target.mission:IsOver()then
text=text..string.format(" - DONE ==> removing mission")
target.mission=nil
end
else
text=text..string.format(", NO mission yet")
end
self:T2(self.lid..text)
if isAlive and isThreat and isImportant and not target.mission then
local valid=false
if self.strategy==CHIEF.Strategy.PASSIVE then
valid=false
elseif self.strategy==CHIEF.Strategy.DEFENSIVE then
if self:CheckTargetInZones(target,self.borderzoneset)then
valid=true
end
elseif self.strategy==CHIEF.Strategy.OFFENSIVE then
if self:CheckTargetInZones(target,self.borderzoneset)or self:CheckTargetInZones(target,self.yellowzoneset)then
valid=true
end
elseif self.strategy==CHIEF.Strategy.AGGRESSIVE then
if self:CheckTargetInZones(target,self.borderzoneset)or self:CheckTargetInZones(target,self.yellowzoneset)or self:CheckTargetInZones(target,self.engagezoneset)then
valid=true
end
elseif self.strategy==CHIEF.Strategy.TOTALWAR then
valid=true
end
if valid then
self:T(self.lid..string.format("Got valid target %s: category=%s, threatlevel=%d",target:GetName(),target.category,threatlevel))
local MissionPerformances=self:_GetMissionPerformanceFromTarget(target)
local mission=nil
local Legions=nil
if#MissionPerformances>0 then
for _,_mp in pairs(MissionPerformances)do
local mp=_mp
local notlimited=self:_CheckMissionLimit(mp.MissionType)
if notlimited then
local NassetsMin,NassetsMax=self:_GetAssetsForTarget(target,mp.MissionType)
self:T2(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s",mp.MissionType,mp.Performance,target:GetName()))
local recruited,assets,legions=self.commander:RecruitAssetsForTarget(target,mp.MissionType,NassetsMin,NassetsMax)
if recruited then
self:T(self.lid..string.format("Recruited %d assets for mission type %s [performance=%d] of target %s",#assets,mp.MissionType,mp.Performance,target:GetName()))
mission=AUFTRAG:NewFromTarget(target,mp.MissionType)
if mission then
mission:_AddAssets(assets)
Legions=legions
break
end
else
self:T(self.lid..string.format("Could NOT recruit assets for mission type %s [performance=%d] of target %s",mp.MissionType,mp.Performance,target:GetName()))
end
end
end
end
if mission and Legions then
target.mission=mission
mission.prio=target.prio
mission.importance=target.importance
self:MissionAssign(mission,Legions)
return
end
end
end
end
end
function CHIEF:_CheckMissionLimit(MissionType)
return self.commander:_CheckMissionLimit(MissionType)
end
function CHIEF:GetMissionLimit(MissionType)
local l=self.commander.limitMission[MissionType]
if not l then
l=999
end
return l
end
function CHIEF:CheckOpsZoneQueue()
local Nzones=#self.zonequeue
if Nzones==0 then
return nil
end
for i=Nzones,1,-1 do
local stratzone=self.zonequeue[i]
if stratzone.opszone:IsStopped()then
self:RemoveStrategicZone(stratzone.opszone)
end
end
for _,_startzone in pairs(self.zonequeue)do
local stratzone=_startzone
local ownercoalition=stratzone.opszone:GetOwner()
if ownercoalition==self.coalition or stratzone.opszone:IsEmpty()then
for _,_resource in pairs(stratzone.resourceOccup or{})do
local resource=_resource
if resource.mission then
resource.mission:Cancel()
end
end
end
end
if self:IsPassive()then
return
end
local NoLimit=self:_CheckMissionLimit("Total")
if NoLimit==false then
return nil
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)
end
table.sort(self.zonequeue,_sort)
local vip=math.huge
for _,_stratzone in pairs(self.zonequeue)do
local stratzone=_stratzone
if stratzone.importance and stratzone.importance<vip and stratzone.opszone:GetOwner()~=self.coalition then
vip=stratzone.importance
end
end
for _,_startzone in pairs(self.zonequeue)do
local stratzone=_startzone
local ownercoalition=stratzone.opszone:GetOwner()
local zoneName=stratzone.opszone.zone:GetName()
if(ownercoalition~=self.coalition or(ownercoalition==self.coalition and stratzone.opszone:IsEmpty()))
and(stratzone.importance==nil or stratzone.importance<=vip)
and(not stratzone.opszone:IsStopped())then
self:T(self.lid..string.format("Zone %s [%s] is owned by coalition %d",zoneName,stratzone.opszone:GetState(),ownercoalition))
if stratzone.opszone:IsEmpty()then
for _,_resource in pairs(stratzone.resourceEmpty or{})do
local resource=_resource
local missionType=resource.MissionType
if(not resource.mission)or resource.mission:IsOver()then
self:T2(self.lid..string.format("Zone \"%s\" is empty ==> Recruiting for mission type %s: Nmin=%d, Nmax=%d",zoneName,missionType,resource.Nmin,resource.Nmax))
local recruited=self:RecruitAssetsForZone(stratzone,resource)
if recruited then
self:T(self.lid..string.format("Successfully recruited assets for empty zone \"%s\" [mission type=%s]",zoneName,missionType))
else
self:T(self.lid..string.format("Could not recruited assets for empty zone \"%s\" [mission type=%s]",zoneName,missionType))
end
end
end
else
for _,_resource in pairs(stratzone.resourceOccup or{})do
local resource=_resource
local missionType=resource.MissionType
if(not resource.mission)or resource.mission:IsOver()then
self:T2(self.lid..string.format("Zone %s is NOT empty ==> Recruiting for mission type %s: Nmin=%d, Nmax=%d",zoneName,missionType,resource.Nmin,resource.Nmax))
local recruited=self:RecruitAssetsForZone(stratzone,resource)
if recruited then
self:T(self.lid..string.format("Successfully recruited assets for occupied zone %s, mission type=%s",zoneName,missionType))
else
self:T(self.lid..string.format("Could not recruited assets for occupied zone %s, mission type=%s",zoneName,missionType))
end
end
end
end
end
end
end
function CHIEF:CheckGroupInBorder(group)
local inside=self:CheckGroupInZones(group,self.borderzoneset)
return inside
end
function CHIEF:CheckGroupInConflict(group)
local inside=self:CheckGroupInZones(group,self.yellowzoneset)
return inside
end
function CHIEF:CheckGroupInAttack(group)
local inside=self:CheckGroupInZones(group,self.engagezoneset)
return inside
end
function CHIEF:CheckGroupInZones(group,zoneset)
for _,_zone in pairs(zoneset.Set or{})do
local zone=_zone
if group:IsInZone(zone)then
return true
end
end
return false
end
function CHIEF:CheckTargetInZones(target,zoneset)
for _,_zone in pairs(zoneset.Set or{})do
local zone=_zone
if zone:IsCoordinateInZone(target:GetCoordinate())then
return true
end
end
return false
end
function CHIEF:_CreateMissionPerformance(MissionType,Performance)
local mp={}
mp.MissionType=MissionType
mp.Performance=Performance
return mp
end
function CHIEF:_GetMissionPerformanceFromTarget(Target)
local group=nil
local airbase=nil
local scenery=nil
local static=nil
local coordinate=nil
local target=Target:GetObject()
if target:IsInstanceOf("GROUP")then
group=target
elseif target:IsInstanceOf("UNIT")then
group=target:GetGroup()
elseif target:IsInstanceOf("AIRBASE")then
airbase=target
elseif target:IsInstanceOf("STATIC")then
static=target
elseif target:IsInstanceOf("SCENERY")then
scenery=target
end
local TargetCategory=Target:GetCategory()
local missionperf={}
if group then
local category=group:GetCategory()
local attribute=group:GetAttribute()
if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT,100))
elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then
if attribute==GROUP.Attribute.GROUND_SAM then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
elseif attribute==GROUP.Attribute.GROUND_EWR then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
elseif attribute==GROUP.Attribute.GROUND_AAA then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40))
elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,75))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,70))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
elseif attribute==GROUP.Attribute.GROUND_INFANTRY then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40))
elseif attribute==GROUP.Attribute.GROUND_TANK then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.CAS,90))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.CASENHANCED,90))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARMORATTACK,40))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
else
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.GROUNDATTACK,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
end
elseif category==Group.Category.SHIP then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ANTISHIP,100))
else
self:E(self.lid.."ERROR: Unknown Group category!")
end
elseif airbase then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBRUNWAY,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
elseif static then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
elseif scenery then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.STRIKE,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,70))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
elseif coordinate then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING,100))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET,50))
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY,30))
end
return missionperf
end
function CHIEF:_GetMissionTypeForGroupAttribute(Attribute)
local missionperf={}
if Attribute==GROUP.Attribute.AIR_ATTACKHELO then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.INTERCEPT),100)
elseif Attribute==GROUP.Attribute.GROUND_AAA then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),100)
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBING),80)
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BOMBCARPET),70)
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),30)
elseif Attribute==GROUP.Attribute.GROUND_SAM then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD),100)
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),90)
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),50)
elseif Attribute==GROUP.Attribute.GROUND_EWR then
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.SEAD),100)
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.BAI),100)
table.insert(missionperf,self:_CreateMissionPerformance(AUFTRAG.Type.ARTY),50)
end
return missionperf
end
function CHIEF:RecruitAssetsForZone(StratZone,Resource)
local Cohorts=self.commander:_GetCohorts()
local MissionType=Resource.MissionType
local NassetsMin=Resource.Nmin
local NassetsMax=Resource.Nmax
local Categories=Resource.Categories
local Attributes=Resource.Attributes
local Properties=Resource.Properties
local TargetVec2=StratZone.opszone.zone:GetVec2()
local RangeMax=nil
if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then
RangeMax=UTILS.NMToMeters(250)
end
if MissionType==AUFTRAG.Type.ARMOREDGUARD then
RangeMax=UTILS.NMToMeters(50)
end
self:T(self.lid..string.format("Recruiting assets for zone %s",StratZone.opszone:GetName()))
self:T(self.lid.."Missiontype="..MissionType)
self:T({categories=Categories})
self:T({attributes=Attributes})
self:T({properties=Properties})
local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,MissionType,nil,NassetsMin,NassetsMax,TargetVec2,nil,RangeMax,nil,nil,nil,nil,Categories,Attributes,Properties)
if recruited then
local mission=nil
self:T2(self.lid..string.format("Recruited %d assets for %s mission STRATEGIC zone %s",#assets,MissionType,tostring(StratZone.opszone.zoneName)))
local TargetZone=StratZone.opszone.zone
local TargetCoord=TargetZone:GetCoordinate()
local transport=nil
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
self:T(self.lid..string.format("Could not allocate assets or transport of OPSZONE!"))
LEGION.UnRecruitAssets(assets)
return false
end
self:T2(self.lid..string.format("Recruited %d assets for mission %s",#assets,MissionType))
if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then
if MissionType==AUFTRAG.Type.PATROLZONE then
mission=AUFTRAG:NewPATROLZONE(TargetZone)
elseif MissionType==AUFTRAG.Type.ONGUARD then
mission=AUFTRAG:NewONGUARD(TargetZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND}))
end
mission:SetEngageDetected(25,{"Ground Units","Light armed ships","Helicopters"})
elseif MissionType==AUFTRAG.Type.CAPTUREZONE then
mission=AUFTRAG:NewCAPTUREZONE(StratZone.opszone,self.coalition)
elseif MissionType==AUFTRAG.Type.CASENHANCED then
local height=UTILS.MetersToFeet(TargetCoord:GetLandHeight())+2500
local Speed=200
if assets[1]then
if assets[1].speedmax then
Speed=UTILS.KmphToKnots(assets[1].speedmax*0.7)or 200
end
end
mission=AUFTRAG:NewCASENHANCED(TargetZone,height,Speed)
elseif MissionType==AUFTRAG.Type.CAS then
local height=UTILS.MetersToFeet(TargetCoord:GetLandHeight())+2500
local Speed=200
if assets[1]then
if assets[1].speedmax then
Speed=UTILS.KmphToKnots(assets[1].speedmax*0.7)or 200
end
end
TargetZone=StratZone.opszone.zoneCircular
local Leg=TargetZone:GetRadius()<=10000 and 5 or UTILS.MetersToNM(TargetZone:GetRadius())
mission=AUFTRAG:NewCAS(TargetZone,height,Speed,TargetCoord,math.random(0,359),Leg)
elseif MissionType==AUFTRAG.Type.ARTY then
local Radius=TargetZone:GetRadius()
mission=AUFTRAG:NewARTY(TargetCoord,120,Radius)
elseif MissionType==AUFTRAG.Type.ARMOREDGUARD then
mission=AUFTRAG:NewARMOREDGUARD(TargetCoord)
elseif MissionType==AUFTRAG.Type.BOMBCARPET then
mission=AUFTRAG:NewBOMBCARPET(TargetCoord,nil,1000)
elseif MissionType==AUFTRAG.Type.BOMBING then
local coord=TargetZone:GetRandomCoordinate()
mission=AUFTRAG:NewBOMBING(TargetCoord)
elseif MissionType==AUFTRAG.Type.RECON then
mission=AUFTRAG:NewRECON(TargetZone,nil,5000)
elseif MissionType==AUFTRAG.Type.BARRAGE then
mission=AUFTRAG:NewBARRAGE(TargetZone)
elseif MissionType==AUFTRAG.Type.AMMOSUPPLY then
mission=AUFTRAG:NewAMMOSUPPLY(TargetZone)
end
if mission then
mission:_AddAssets(assets)
self:MissionAssign(mission,legions)
StratZone.opszone:_AddMission(self.coalition,MissionType,mission)
mission:SetName(string.format("Stratzone %s-%d",StratZone.opszone:GetName(),mission.auftragsnummer))
Resource.mission=mission
if transport and Ntransports>0 then
mission.opstransport=transport
transport.opszone=StratZone.opszone
transport.chief=self
transport.commander=self.commander
end
return true
else
self:E(self.lid..string.format("ERROR: Mission type %s not supported for OPSZONE! Unrecruiting assets...",tostring(MissionType)))
LEGION.UnRecruitAssets(assets)
return false
end
end
self:T2(self.lid..string.format("Could NOT recruit assets for %s mission of STRATEGIC zone %s",MissionType,tostring(StratZone.opszone.zoneName)))
return false
end
function CHIEF._FilterAssets(Assets,Categories,Attributes,Properties)
local filtered={}
for _,_asset in pairs(Assets)do
local asset=_asset
local hasCat=CHIEF._CheckAssetCategories(asset,Categories)
local hasAtt=CHIEF._CheckAssetAttributes(asset,Attributes)
local hasPro=CHIEF._CheckAssetProperties(asset,Properties)
if hasAtt and hasCat and hasPro then
table.insert(filtered,asset)
end
end
return filtered
end
function CHIEF._CheckAssetAttributes(Asset,Attributes)
if not Attributes then
return true
end
for _,attribute in pairs(UTILS.EnsureTable(Attributes))do
if attribute==Asset.attribute then
return true
end
end
return false
end
function CHIEF._CheckAssetCategories(Asset,Categories)
if not Categories then
return true
end
for _,attribute in pairs(UTILS.EnsureTable(Categories))do
if attribute==Asset.category then
return true
end
end
return false
end
function CHIEF._CheckAssetProperties(Asset,Properties)
if not Properties then
return true
end
for _,attribute in pairs(UTILS.EnsureTable(Properties))do
if attribute==Asset.DCSdesc then
return true
end
end
return false
end
COHORT={
ClassName="COHORT",
verbose=0,
lid=nil,
name=nil,
templatename=nil,
assets={},
missiontypes={},
repairtime=0,
maintenancetime=0,
livery=nil,
skill=nil,
legion=nil,
Ngroups=0,
engageRange=nil,
tacanChannel={},
weightAsset=99999,
cargobayLimit=0,
descriptors={},
properties={},
operations={},
}
COHORT.version="0.3.6"
_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=name
self.lid=string.format("COHORT %s | ",self.name)
self.templategroup=GROUP:FindByName(self.templatename)
if not self.templategroup then
self:E(self.lid..string.format("ERROR: Template group %s does not exist!",tostring(self.templatename)))
return nil
end
self.attribute=self.templategroup:GetAttribute()
self.category=self.templategroup:GetCategory()
self.aircrafttype=self.templategroup:GetTypeName()
self.descriptors=self.templategroup:GetUnit(1):GetDesc()
self.properties=self.descriptors.attributes
self.Ngroups=Ngroups or 3
self:SetSkill(AI.Skill.GOOD)
if self.category==Group.Category.AIRPLANE then
self:SetMissionRange(200)
elseif self.category==Group.Category.HELICOPTER then
self:SetMissionRange(150)
elseif self.category==Group.Category.GROUND then
self:SetMissionRange(75)
elseif self.category==Group.Category.SHIP then
self:SetMissionRange(100)
elseif self.category==Group.Category.TRAIN then
self:SetMissionRange(100)
else
self:SetMissionRange(150)
end
local units=self.templategroup:GetUnits()
self.weightAsset=0
for i,_unit in pairs(units)do
local unit=_unit
local desc=unit:GetDesc()
local mass=666
if desc then
mass=desc.massMax or desc.massEmpty
end
self.weightAsset=self.weightAsset+(mass or 666)
if i==1 then
self.cargobayLimit=unit:GetCargoBayFreeWeight()
end
end
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","OnDuty")
self:AddTransition("*","Status","*")
self:AddTransition("OnDuty","Pause","Paused")
self:AddTransition("Paused","Unpause","OnDuty")
self:AddTransition("OnDuty","Relocate","Relocating")
self:AddTransition("Relocating","Relocated","OnDuty")
self:AddTransition("*","Stop","Stopped")
return self
end
function COHORT:SetLivery(LiveryName)
self.livery=LiveryName
return self
end
function COHORT:SetSkill(Skill)
self.skill=Skill
return self
end
function COHORT:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function COHORT:SetTurnoverTime(MaintenanceTime,RepairTime)
self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0
self.repairtime=RepairTime and RepairTime*60 or 0
return self
end
function COHORT:SetRadio(Frequency,Modulation)
self.radioFreq=Frequency or 251
self.radioModu=Modulation or radio.modulation.AM
return self
end
function COHORT:SetGrouping(nunits)
self.ngrouping=nunits or 2
return self
end
function COHORT:AddMissionCapability(MissionTypes,Performance)
if MissionTypes and type(MissionTypes)~="table"then
MissionTypes={MissionTypes}
end
self.missiontypes=self.missiontypes or{}
for _,missiontype in pairs(MissionTypes)do
local Capability=self:GetMissionCapability(missiontype)
if Capability then
self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice. Will update the performance though!")
Capability.Performance=Performance or 50
else
local capability={}
capability.MissionType=missiontype
capability.Performance=Performance or 50
table.insert(self.missiontypes,capability)
self:T(self.lid..string.format("Adding mission capability %s, performance=%d",tostring(capability.MissionType),capability.Performance))
end
end
self:T2(self.missiontypes)
return self
end
function COHORT:GetMissionCapability(MissionType)
for _,_capability in pairs(self.missiontypes)do
local capability=_capability
if capability.MissionType==MissionType then
return capability
end
end
return nil
end
function COHORT:HasProperty(Property)
for _,property in pairs(self.properties)do
if Property==property then
return true
end
end
return false
end
function COHORT:GetMissionTypes()
local missiontypes={}
for _,Capability in pairs(self.missiontypes)do
local capability=Capability
table.insert(missiontypes,capability.MissionType)
end
return missiontypes
end
function COHORT:GetMissionCapabilities()
return self.missiontypes
end
function COHORT:GetMissionPeformance(MissionType)
for _,Capability in pairs(self.missiontypes)do
local capability=Capability
if capability.MissionType==MissionType then
return capability.Performance
end
end
return-1
end
function COHORT:SetMissionRange(Range)
self.engageRange=UTILS.NMToMeters(Range or 150)
return self
end
function COHORT:SetCallsign(Callsign,Index)
self.callsignName=Callsign
self.callsignIndex=Index
self.callsign={}
self.callsign.NumberSquad=Callsign
self.callsign.NumberGroup=Index
return self
end
function COHORT:SetAttribute(Attribute)
self.attribute=Attribute
return self
end
function COHORT:GetAttribute()
return self.attribute
end
function COHORT:GetCategory()
return self.category
end
function COHORT:GetProperties()
return self.properties
end
function COHORT:SetModex(Modex,Prefix,Suffix)
self.modex=Modex
self.modexPrefix=Prefix
self.modexSuffix=Suffix
return self
end
function COHORT:SetLegion(Legion)
self.legion=Legion
return self
end
function COHORT:AddAsset(Asset)
self:T(self.lid..string.format("Adding asset %s of type %s",Asset.spawngroupname,Asset.unittype))
Asset.squadname=self.name
Asset.legion=self.legion
Asset.cohort=self
table.insert(self.assets,Asset)
return self
end
function COHORT:DelAsset(Asset)
for i,_asset in pairs(self.assets)do
local asset=_asset
if Asset.uid==asset.uid then
self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname))
table.remove(self.assets,i)
break
end
end
return self
end
function COHORT:DelGroup(GroupName)
for i,_asset in pairs(self.assets)do
local asset=_asset
if GroupName==asset.spawngroupname then
self:T2(self.lid..string.format("Removing asset %s",asset.spawngroupname))
table.remove(self.assets,i)
break
end
end
return self
end
function COHORT:RemoveAssets(N)
self:T2(self.lid..string.format("Remove %d assets of Cohort",N))
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))
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))
return self
end
function COHORT:GetName()
return self.name
end
function COHORT:GetRadio()
return self.radioFreq,self.radioModu
end
function COHORT:GetCallsign(Asset)
if self.callsignName then
Asset.callsign={}
for i=1,Asset.nunits do
local callsign={}
callsign[1]=self.callsignName
callsign[2]=math.floor(self.callsigncounter/10)
callsign[3]=self.callsigncounter%10
if callsign[3]==0 then
callsign[3]=1
self.callsigncounter=self.callsigncounter+2
else
self.callsigncounter=self.callsigncounter+1
end
Asset.callsign[i]=callsign
self:T3({callsign=callsign})
end
end
end
function COHORT:GetModex(Asset)
if self.modex then
Asset.modex={}
for i=1,Asset.nunits do
Asset.modex[i]=string.format("%03d",self.modex+self.modexcounter)
self.modexcounter=self.modexcounter+1
self:T3({modex=Asset.modex[i]})
end
end
end
function COHORT:AddTacanChannel(ChannelMin,ChannelMax)
ChannelMax=ChannelMax or ChannelMin
if ChannelMin>126 then
self:E(self.lid.."ERROR: TACAN Channel must be <= 126! Will not add to available channels")
return self
end
if ChannelMax>126 then
self:E(self.lid.."WARNING: TACAN Channel must be <= 126! Adjusting ChannelMax to 126")
ChannelMax=126
end
for i=ChannelMin,ChannelMax do
self.tacanChannel[i]=true
end
return self
end
function COHORT:FetchTacan()
local freechannel=nil
for channel,free in pairs(self.tacanChannel)do
if free then
if freechannel==nil or channel<freechannel then
freechannel=channel
end
end
end
if freechannel then
self:T(self.lid..string.format("Checking out Tacan channel %d",freechannel))
self.tacanChannel[freechannel]=false
end
return freechannel
end
function COHORT:ReturnTacan(channel)
self:T(self.lid..string.format("Returning Tacan channel %d",channel))
self.tacanChannel[channel]=true
end
function COHORT:AddWeaponRange(RangeMin,RangeMax,BitType)
RangeMin=UTILS.NMToMeters(RangeMin or 0)
RangeMax=UTILS.NMToMeters(RangeMax or 10)
local weapon={}
weapon.BitType=BitType or ENUMS.WeaponFlag.Auto
weapon.RangeMax=RangeMax
weapon.RangeMin=RangeMin
self.weaponData=self.weaponData or{}
self.weaponData[tostring(weapon.BitType)]=weapon
self:T(self.lid..string.format("Adding weapon data: Bit=%s, Rmin=%d m, Rmax=%d m",tostring(weapon.BitType),weapon.RangeMin,weapon.RangeMax))
if self.verbose>=2 then
local text="Weapon data:"
for _,_weapondata in pairs(self.weaponData)do
local weapondata=_weapondata
text=text..string.format("\n- Bit=%s, Rmin=%d m, Rmax=%d m",tostring(weapondata.BitType),weapondata.RangeMin,weapondata.RangeMax)
end
self:I(self.lid..text)
end
return self
end
function COHORT:GetWeaponData(BitType)
return self.weaponData[tostring(BitType)]
end
function COHORT:IsOnDuty()
return self:Is("OnDuty")
end
function COHORT:IsStopped()
return self:Is("Stopped")
end
function COHORT:IsPaused()
return self:Is("Paused")
end
function COHORT:IsRelocating()
return self:Is("Relocating")
end
function COHORT:onafterStart(From,Event,To)
local text=string.format("Starting %s v%s %s [%s]",self.ClassName,self.version,self.name,self.attribute)
self:I(self.lid..text)
self:__Status(-1)
end
function COHORT:_CheckAssetStatus()
if self.verbose>=2 and#self.assets>0 then
local text=""
for j,_asset in pairs(self.assets)do
local asset=_asset
text=text..string.format("\n[%d] %s (%s*%d): ",j,asset.spawngroupname,asset.unittype,asset.nunits)
if asset.spawned then
local mission=self.legion and self.legion:GetAssetCurrentMission(asset)or false
if mission then
local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate()))or 0
text=text..string.format("Mission %s - %s: Status=%s, Dist=%.1f NM",mission.name,mission.type,mission.status,distance)
else
text=text.."Mission None"
end
text=text..", Flight: "
if asset.flightgroup and asset.flightgroup:IsAlive()then
local status=asset.flightgroup:GetState()
text=text..string.format("%s",status)
if asset.flightgroup:IsFlightgroup()then
local fuelmin=asset.flightgroup:GetFuelMin()
local fuellow=asset.flightgroup:IsFuelLow()
local fuelcri=asset.flightgroup:IsFuelCritical()
text=text..string.format("Fuel=%d",fuelmin)
if fuelcri then
text=text.." (Critical!)"
elseif fuellow then
text=text.." (Low)"
end
end
local lifept,lifept0=asset.flightgroup:GetLifePoints()
text=text..string.format(", Life=%d/%d",lifept,lifept0)
local ammo=asset.flightgroup:GetAmmoTot()
text=text..string.format(", Ammo=%d [G=%d, R=%d, B=%d, M=%d]",ammo.Total,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles)
else
text=text.."N/A"
end
if asset.flightgroup:IsFlightgroup()then
local payload=asset.payload and table.concat(self.legion:GetPayloadMissionTypes(asset.payload),", ")or"None"
text=text..", Payload={"..payload.."}"
end
else
text=text..string.format("In Stock")
if self:IsRepaired(asset)then
text=text..", Combat Ready"
else
text=text..string.format(", Repaired in %d sec",self:GetRepairTime(asset))
if asset.damage then
text=text..string.format(" (Damage=%.1f)",asset.damage)
end
end
if asset.Treturned then
local T=timer.getAbsTime()-asset.Treturned
text=text..string.format(", Returned for %d sec",T)
end
end
end
self:T(self.lid..text)
end
end
function COHORT:onafterStop(From,Event,To)
self:T(self.lid.."STOPPING Cohort and removing all assets!")
for i=#self.assets,1,-1 do
local asset=self.assets[i]
self:DelAsset(asset)
end
self.CallScheduler:Clear()
end
function COHORT:CanMission(Mission)
local cando=true
if not self:IsOnDuty()then
self:T(self.lid..string.format("Cohort in not OnDuty but in state %s. Cannot do mission %s with target %s",self:GetState(),Mission.name,Mission:GetTargetName()))
return false
end
if not AUFTRAG.CheckMissionType(Mission.type,self:GetMissionTypes())then
self:T(self.lid..string.format("INFO: Cohort cannot do mission type %s (%s, %s)",Mission.type,Mission.name,Mission:GetTargetName()))
return false
end
if Mission.type==AUFTRAG.Type.TANKER then
if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then
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
end
end
local TargetDistance=Mission:GetTargetDistance(self.legion:GetCoordinate())
local engagerange=Mission.engageRange and math.max(self.engageRange,Mission.engageRange)or self.engageRange
if TargetDistance>engagerange then
self:T(self.lid..string.format("INFO: Cohort is not in range. Target dist=%d > %d NM max mission Range",UTILS.MetersToNM(TargetDistance),UTILS.MetersToNM(engagerange)))
return false
end
return true
end
function COHORT:CountAssets(InStock,MissionTypes,Attributes)
local N=0
for _,_asset in pairs(self.assets)do
local asset=_asset
if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes,self.missiontypes)then
if Attributes==nil or self:CheckAttribute(Attributes)then
if asset.spawned then
if InStock==false or InStock==nil then
N=N+1
end
else
if InStock==true or InStock==nil then
N=N+1
end
end
end
end
end
return N
end
function COHORT:GetOpsGroups(MissionTypes,Attributes)
local set=SET_OPSGROUP:New()
for _,_asset in pairs(self.assets)do
local asset=_asset
if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes,self.missiontypes)then
if Attributes==nil or self:CheckAttribute(Attributes)then
if asset.flightgroup and asset.flightgroup:IsAlive()then
set:AddGroup(asset.flightgroup)
end
end
end
end
return set
end
function COHORT:RecruitAssets(MissionType,Npayloads)
self:T2(self.lid..string.format("Recruiting asset for Mission type=%s",MissionType))
local assets={}
for _,_asset in pairs(self.assets)do
local asset=_asset
local isRequested=asset.requested
local isReserved=asset.isReserved
local isSpawned=asset.spawned
local isOnMission=self.legion:IsAssetOnMission(asset)
local opsgroup=asset.flightgroup
self:T(self.lid..string.format("Asset %s: requested=%s, reserved=%s, spawned=%s,  onmission=%s",
asset.spawngroupname,tostring(isRequested),tostring(isReserved),tostring(isSpawned),tostring(isOnMission)))
if not(isRequested or isReserved)then
if self.legion:IsAssetOnMission(asset)then
if MissionType==AUFTRAG.Type.RELOCATECOHORT then
table.insert(assets,asset)
elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.NOTHING)then
table.insert(assets,asset)
elseif self.legion:IsAssetOnMission(asset,{AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})and MissionType==AUFTRAG.Type.INTERCEPT then
self:T(self.lid..string.format("Adding asset on GCICAP mission for an INTERCEPT mission"))
table.insert(assets,asset)
elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.ONGUARD)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then
if not opsgroup:IsOutOfAmmo()then
self:T(self.lid..string.format("Adding asset on ONGUARD mission for an XXX mission"))
table.insert(assets,asset)
end
elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.PATROLZONE)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then
if not opsgroup:IsOutOfAmmo()then
self:T(self.lid..string.format("Adding asset on PATROLZONE mission for an XXX mission"))
table.insert(assets,asset)
end
elseif self.legion:IsAssetOnMission(asset,AUFTRAG.Type.ALERT5)and AUFTRAG.CheckMissionCapability(MissionType,asset.payload.capabilities)and MissionType~=AUFTRAG.Type.ALERT5 then
self:T(self.lid..string.format("Adding asset on ALERT 5 mission for %s mission",MissionType))
table.insert(assets,asset)
end
else
if asset.spawned then
local flightgroup=asset.flightgroup
if flightgroup and flightgroup:IsAlive()and not(flightgroup:IsDead()or flightgroup:IsStopped())then
local combatready=true
if flightgroup:IsFlightgroup()then
if flightgroup:IsFuelLow()then
combatready=false
end
if MissionType==AUFTRAG.Type.INTERCEPT and not flightgroup:CanAirToAir()then
combatready=false
else
local excludeguns=MissionType==AUFTRAG.Type.BOMBING or MissionType==AUFTRAG.Type.BOMBRUNWAY or MissionType==AUFTRAG.Type.BOMBCARPET or MissionType==AUFTRAG.Type.SEAD or MissionType==AUFTRAG.Type.ANTISHIP
if excludeguns and not flightgroup:CanAirToGround(excludeguns)then
combatready=false
end
end
if flightgroup:IsHolding()or flightgroup:IsLanding()or flightgroup:IsLanded()or flightgroup:IsArrived()then
combatready=false
end
if asset.payload and not AUFTRAG.CheckMissionCapability(MissionType,asset.payload.capabilities)then
combatready=false
end
else
if flightgroup:IsArmygroup()then
if asset.attribute==WAREHOUSE.Attribute.GROUND_ARTILLERY or
asset.attribute==WAREHOUSE.Attribute.GROUND_TANK or
asset.attribute==WAREHOUSE.Attribute.GROUND_INFANTRY or
asset.attribute==WAREHOUSE.Attribute.GROUND_AAA or
asset.attribute==WAREHOUSE.Attribute.GROUND_SAM
then
combatready=true
end
else
combatready=false
end
if flightgroup:IsRearming()or flightgroup:IsRetreating()or flightgroup:IsReturning()then
combatready=false
end
end
if flightgroup:IsLoading()or flightgroup:IsTransporting()or flightgroup:IsUnloading()or flightgroup:IsPickingup()or flightgroup:IsCarrier()then
combatready=false
end
if flightgroup:IsCargo()or flightgroup:IsBoarding()or flightgroup:IsAwaitingLift()then
combatready=false
end
if combatready then
self:T(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY")
table.insert(assets,asset)
end
end
else
if Npayloads>0 and self:IsRepaired(asset)then
table.insert(assets,asset)
Npayloads=Npayloads-1
end
end
end
end
end
self:T2(self.lid..string.format("Recruited %d assets for Mission type=%s",#assets,MissionType))
return assets,Npayloads
end
function COHORT:GetRepairTime(Asset)
if Asset.Treturned then
local t=self.maintenancetime
t=t+Asset.damage*self.repairtime
local dt=timer.getAbsTime()-Asset.Treturned
local T=t-dt
return T
else
return 0
end
end
function COHORT:GetMissionRange(WeaponTypes)
if WeaponTypes and type(WeaponTypes)~="table"then
WeaponTypes={WeaponTypes}
end
local function checkWeaponType(Weapon)
local weapon=Weapon
if WeaponTypes and#WeaponTypes>0 then
for _,weapontype in pairs(WeaponTypes)do
if weapontype==weapon.BitType then
return true
end
end
return false
end
return true
end
local WeaponRange=0
for _,_weapon in pairs(self.weaponData or{})do
local weapon=_weapon
if weapon.RangeMax>WeaponRange and checkWeaponType(weapon)then
WeaponRange=weapon.RangeMax
end
end
return self.engageRange+WeaponRange
end
function COHORT:IsRepaired(Asset)
if Asset.Treturned then
local Tnow=timer.getAbsTime()
local Trepaired=Asset.Treturned+self.maintenancetime
if Tnow>=Trepaired then
return true
else
return false
end
else
return true
end
end
function COHORT:CheckAttribute(Attributes)
if type(Attributes)~="table"then
Attributes={Attributes}
end
for _,attribute in pairs(Attributes)do
if attribute==self.attribute then
return true
end
end
return false
end
function COHORT:_CheckAmmo()
local units=self.templategroup:GetUnits()
local nammo=0
local nguns=0
local nshells=0
local nrockets=0
local nmissiles=0
local nmissilesAA=0
local nmissilesAG=0
local nmissilesAS=0
local nmissilesSA=0
local nmissilesBM=0
local nmissilesCR=0
local ntorps=0
local nbombs=0
for _,_unit in pairs(units)do
local unit=_unit
local text=string.format("Unit %s:\n",unit:GetName())
local ammotable=unit:GetAmmo()
if ammotable then
self:T3(ammotable)
for w=1,#ammotable do
local weapon=ammotable[w]
local Desc=weapon["desc"]
local Warhead=Desc["warhead"]
local Nammo=weapon["count"]
local Category=Desc["category"]
local MissileCategory=(Category==Weapon.Category.MISSILE)and Desc.missileCategory or nil
local TypeName=Desc["typeName"]
local weaponString=UTILS.Split(TypeName,"%.")
local WeaponName=weaponString[#weaponString]
local Rmin=Desc["rangeMin"]or 0
local Rmax=Desc["rangeMaxAltMin"]or 0
local Caliber=Warhead and Warhead["caliber"]or 0
if Category==Weapon.Category.SHELL then
if Caliber<70 then
nguns=nguns+Nammo
else
nshells=nshells+Nammo
end
text=text..string.format("- %d shells [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax)
elseif Category==Weapon.Category.ROCKET then
nrockets=nrockets+Nammo
text=text..string.format("- %d rockets [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax)
elseif Category==Weapon.Category.BOMB then
nbombs=nbombs+Nammo
text=text..string.format("- %d bombs [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax)
elseif Category==Weapon.Category.MISSILE then
if MissileCategory==Weapon.MissileCategory.AAM then
nmissiles=nmissiles+Nammo
nmissilesAA=nmissilesAA+Nammo
if Rmax>0 then
self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.AnyAA)
end
elseif MissileCategory==Weapon.MissileCategory.SAM then
nmissiles=nmissiles+Nammo
nmissilesSA=nmissilesSA+Nammo
if Rmax>0 then
end
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
nmissiles=nmissiles+Nammo
nmissilesAS=nmissilesAS+Nammo
if Rmax>0 then
self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.AntiShipMissile)
end
elseif MissileCategory==Weapon.MissileCategory.BM then
nmissiles=nmissiles+Nammo
nmissilesBM=nmissilesBM+Nammo
if Rmax>0 then
end
elseif MissileCategory==Weapon.MissileCategory.CRUISE then
nmissiles=nmissiles+Nammo
nmissilesCR=nmissilesCR+Nammo
if Rmax>0 then
self:AddWeaponRange(UTILS.MetersToNM(Rmin),UTILS.MetersToNM(Rmax),ENUMS.WeaponFlag.CruiseMissile)
end
elseif MissileCategory==Weapon.MissileCategory.OTHER then
nmissiles=nmissiles+Nammo
nmissilesAG=nmissilesAG+Nammo
end
text=text..string.format("- %d %s missiles [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,self:_MissileCategoryName(MissileCategory),WeaponName,Caliber,Rmin,Rmax)
elseif Category==Weapon.Category.TORPEDO then
ntorps=ntorps+Nammo
text=text..string.format("- %d torpedos [%s]: caliber=%d mm, range=%d - %d meters\n",Nammo,WeaponName,Caliber,Rmin,Rmax)
else
text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,TypeName,Category,tostring(MissileCategory))
end
end
end
if self.verbose>=5 then
self:I(self.lid..text)
else
self:T2(self.lid..text)
end
end
nammo=nguns+nshells+nrockets+nmissiles+nbombs+ntorps
local ammo={}
ammo.Total=nammo
ammo.Guns=nguns
ammo.Shells=nshells
ammo.Rockets=nrockets
ammo.Bombs=nbombs
ammo.Torpedos=ntorps
ammo.Missiles=nmissiles
ammo.MissilesAA=nmissilesAA
ammo.MissilesAG=nmissilesAG
ammo.MissilesAS=nmissilesAS
ammo.MissilesCR=nmissilesCR
ammo.MissilesBM=nmissilesBM
ammo.MissilesSA=nmissilesSA
return ammo
end
function COHORT:_MissileCategoryName(categorynumber)
local cat="unknown"
if categorynumber==Weapon.MissileCategory.AAM then
cat="air-to-air"
elseif categorynumber==Weapon.MissileCategory.SAM then
cat="surface-to-air"
elseif categorynumber==Weapon.MissileCategory.BM then
cat="ballistic"
elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then
cat="anti-ship"
elseif categorynumber==Weapon.MissileCategory.CRUISE then
cat="cruise"
elseif categorynumber==Weapon.MissileCategory.OTHER then
cat="other"
end
return cat
end
function COHORT:_AddOperation(Operation)
self.operations[Operation.name]=Operation
end
COMMANDER={
ClassName="COMMANDER",
verbose=0,
coalition=nil,
legions={},
missionqueue={},
transportqueue={},
targetqueue={},
opsqueue={},
rearmingZones={},
refuellingZones={},
capZones={},
gcicapZones={},
awacsZones={},
tankerZones={},
limitMission={},
}
COMMANDER.version="0.1.4"
function COMMANDER:New(Coalition,Alias)
local self=BASE:Inherit(self,FSM:New())
if Coalition==nil then
env.error("ERROR: Coalition parameter is nil in COMMANDER:New() call!")
return nil
end
self.coalition=Coalition
self.alias=Alias
if self.alias==nil then
if Coalition==coalition.side.BLUE then
self.alias="George S. Patton"
elseif Coalition==coalition.side.RED then
self.alias="Georgy Zhukov"
elseif Coalition==coalition.side.NEUTRAL then
self.alias="Mahatma Gandhi"
end
end
self.lid=string.format("COMMANDER %s [%s] | ",self.alias,UTILS.GetCoalitionName(self.coalition))
self:SetStartState("NotReadyYet")
self:AddTransition("NotReadyYet","Start","OnDuty")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("*","MissionAssign","*")
self:AddTransition("*","MissionCancel","*")
self:AddTransition("*","TransportAssign","*")
self:AddTransition("*","TransportCancel","*")
self:AddTransition("*","OpsOnMission","*")
self:AddTransition("*","LegionLost","*")
return self
end
function COMMANDER:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function COMMANDER:SetLimitMission(Limit,MissionType)
MissionType=MissionType or"Total"
if MissionType then
self.limitMission[MissionType]=Limit or 10
else
self:E(self.lid.."ERROR: No mission type given for setting limit!")
end
return self
end
function COMMANDER:GetCoalition()
return self.coalition
end
function COMMANDER:AddAirwing(Airwing)
self:AddLegion(Airwing)
return self
end
function COMMANDER:AddBrigade(Brigade)
self:AddLegion(Brigade)
return self
end
function COMMANDER:AddFleet(Fleet)
self:AddLegion(Fleet)
return self
end
function COMMANDER:AddLegion(Legion)
Legion.commander=self
table.insert(self.legions,Legion)
return self
end
function COMMANDER:RemoveLegion(Legion)
for i,_legion in pairs(self.legions)do
local legion=_legion
if legion.alias==Legion.alias then
table.remove(self.legions,i)
Legion.commander=nil
end
end
return self
end
function COMMANDER:AddMission(Mission)
if not self:IsMission(Mission)then
Mission.commander=self
Mission.statusCommander=AUFTRAG.Status.PLANNED
table.insert(self.missionqueue,Mission)
end
return self
end
function COMMANDER:AddOpsTransport(Transport)
Transport.commander=self
Transport.statusCommander=OPSTRANSPORT.Status.PLANNED
table.insert(self.transportqueue,Transport)
return self
end
function COMMANDER:RemoveMission(Mission)
for i,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.auftragsnummer==Mission.auftragsnummer then
self:T(self.lid..string.format("Removing mission %s (%s) status=%s from queue",Mission.name,Mission.type,Mission.status))
mission.commander=nil
table.remove(self.missionqueue,i)
break
end
end
return self
end
function COMMANDER:RemoveTransport(Transport)
for i,_transport in pairs(self.transportqueue)do
local transport=_transport
if transport.uid==Transport.uid then
self:T(self.lid..string.format("Removing transport UID=%d status=%s from queue",transport.uid,transport:GetState()))
transport.commander=nil
table.remove(self.transportqueue,i)
break
end
end
return self
end
function COMMANDER:AddTarget(Target)
if not self:IsTarget(Target)then
table.insert(self.targetqueue,Target)
end
return self
end
function COMMANDER:AddOperation(Operation)
table.insert(self.opsqueue,Operation)
return self
end
function COMMANDER:IsTarget(Target)
for _,_target in pairs(self.targetqueue)do
local target=_target
if target.uid==Target.uid or target:GetName()==Target:GetName()then
return true
end
end
return false
end
function COMMANDER:RemoveTarget(Target)
for i,_target in pairs(self.targetqueue)do
local target=_target
if target.uid==Target.uid then
self:T(self.lid..string.format("Removing target %s from queue",Target.name))
table.remove(self.targetqueue,i)
break
end
end
return self
end
function COMMANDER:AddRearmingZone(RearmingZone)
local rearmingzone={}
rearmingzone.zone=RearmingZone
rearmingzone.mission=nil
table.insert(self.rearmingZones,rearmingzone)
return rearmingzone
end
function COMMANDER:AddRefuellingZone(RefuellingZone)
local rearmingzone={}
rearmingzone.zone=RefuellingZone
rearmingzone.mission=nil
table.insert(self.refuellingZones,rearmingzone)
return rearmingzone
end
function COMMANDER:AddCapZone(Zone,Altitude,Speed,Heading,Leg)
local patrolzone={}
patrolzone.zone=Zone
patrolzone.altitude=Altitude or 12000
patrolzone.heading=Heading or 270
patrolzone.speed=Speed or 350
patrolzone.leg=Leg or 30
patrolzone.mission=nil
table.insert(self.capZones,patrolzone)
return patrolzone
end
function COMMANDER:AddGciCapZone(Zone,Altitude,Speed,Heading,Leg)
local patrolzone={}
patrolzone.zone=Zone
patrolzone.altitude=Altitude or 12000
patrolzone.heading=Heading or 270
patrolzone.speed=Speed or 350
patrolzone.leg=Leg or 30
patrolzone.mission=nil
table.insert(self.gcicapZones,patrolzone)
return patrolzone
end
function COMMANDER:RemoveGciCapZone(Zone)
local patrolzone={}
patrolzone.zone=Zone
for i,_patrolzone in pairs(self.gcicapZones)do
if _patrolzone.zone==patrolzone.zone then
if _patrolzone.mission and _patrolzone.mission:IsNotOver()then
_patrolzone.mission:Cancel()
end
table.remove(self.gcicapZones,i)
break
end
end
return patrolzone
end
function COMMANDER:AddAwacsZone(Zone,Altitude,Speed,Heading,Leg)
local awacszone={}
awacszone.zone=Zone
awacszone.altitude=Altitude or 12000
awacszone.heading=Heading or 270
awacszone.speed=Speed or 350
awacszone.speed=Speed or 350
awacszone.leg=Leg or 30
awacszone.mission=nil
table.insert(self.awacsZones,awacszone)
return awacszone
end
function COMMANDER:RemoveAwacsZone(Zone)
local awacszone={}
awacszone.zone=Zone
for i,_awacszone in pairs(self.awacsZones)do
if _awacszone.zone==awacszone.zone then
if _awacszone.mission and _awacszone.mission:IsNotOver()then
_awacszone.mission:Cancel()
end
table.remove(self.awacsZones,i)
break
end
end
return awacszone
end
function COMMANDER:AddTankerZone(Zone,Altitude,Speed,Heading,Leg,RefuelSystem)
local tankerzone={}
tankerzone.zone=Zone
tankerzone.altitude=Altitude or 12000
tankerzone.heading=Heading or 270
tankerzone.speed=Speed or 350
tankerzone.leg=Leg or 30
tankerzone.refuelsystem=RefuelSystem
tankerzone.mission=nil
tankerzone.marker=MARKER:New(tankerzone.zone:GetCoordinate(),"Tanker Zone"):ToCoalition(self:GetCoalition())
table.insert(self.tankerZones,tankerzone)
return tankerzone
end
function COMMANDER:RemoveTankerZone(Zone)
local tankerzone={}
tankerzone.zone=Zone
for i,_tankerzone in pairs(self.tankerZones)do
if _tankerzone.zone==tankerzone.zone then
if _tankerzone.mission and _tankerzone.mission:IsNotOver()then
_tankerzone.mission:Cancel()
end
table.remove(self.tankerZones,i)
break
end
end
return tankerzone
end
function COMMANDER:IsMission(Mission)
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.auftragsnummer==Mission.auftragsnummer then
return true
end
end
return false
end
function COMMANDER:RelocateCohort(Cohort,Legion,Delay,NcarriersMin,NcarriersMax,TransportLegions)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,COMMANDER.RelocateCohort,self,Cohort,Legion,0,NcarriersMin,NcarriersMax,TransportLegions)
else
if Legion:IsCohort(Cohort.name)then
self:E(self.lid..string.format("ERROR: Cohort %s is already part of new legion %s ==> CANNOT Relocate!",Cohort.name,Legion.alias))
return self
else
table.insert(Legion.cohorts,Cohort)
end
local LegionOld=Cohort.legion
if not LegionOld:IsCohort(Cohort.name)then
self:E(self.lid..string.format("ERROR: Cohort %s is NOT part of this legion %s ==> CANNOT Relocate!",Cohort.name,self.alias))
return self
end
if LegionOld.alias==Legion.alias then
self:E(self.lid..string.format("ERROR: old legion %s is same as new legion %s ==> CANNOT Relocate!",LegionOld.alias,Legion.alias))
return self
end
Cohort:Relocate()
local mission=AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort)
mission:AssignCohort(Cohort)
mission:SetRequiredAssets(#Cohort.assets)
if NcarriersMin and NcarriersMin>0 then
mission:SetRequiredTransport(Legion.spawnzone,NcarriersMin,NcarriersMax)
end
if TransportLegions then
for _,legion in pairs(TransportLegions)do
mission:AssignTransportLegion(legion)
end
else
for _,legion in pairs(self.legions)do
mission:AssignTransportLegion(legion)
end
end
mission:SetMissionRange(10000)
self:AddMission(mission)
end
return self
end
function COMMANDER:onafterStart(From,Event,To)
local text=string.format("Starting Commander")
self:I(self.lid..text)
for _,_legion in pairs(self.legions)do
local legion=_legion
if legion:GetState()=="NotReadyYet"then
legion:Start()
end
end
self:__Status(-1)
end
function COMMANDER:onafterStatus(From,Event,To)
local fsmstate=self:GetState()
if self.verbose>=1 then
local text=string.format("Status %s: Legions=%d, Missions=%d, Targets=%d, Transports=%d",fsmstate,#self.legions,#self.missionqueue,#self.targetqueue,#self.transportqueue)
self:T(self.lid..text)
end
self:CheckOpsQueue()
self:CheckTargetQueue()
self:CheckMissionQueue()
self:CheckTransportQueue()
for _,_rearmingzone in pairs(self.rearmingZones)do
local rearmingzone=_rearmingzone
if(not rearmingzone.mission)or rearmingzone.mission:IsOver()then
rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone)
self:AddMission(rearmingzone.mission)
end
end
for _,_supplyzone in pairs(self.refuellingZones)do
local supplyzone=_supplyzone
if(not supplyzone.mission)or supplyzone.mission:IsOver()then
supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone)
self:AddMission(supplyzone.mission)
end
end
for _,_patrolzone in pairs(self.capZones)do
local patrolzone=_patrolzone
if(not patrolzone.mission)or patrolzone.mission:IsOver()then
local Coordinate=patrolzone.zone:GetCoordinate()
patrolzone.mission=AUFTRAG:NewCAP(patrolzone.zone,patrolzone.altitude,patrolzone.speed,Coordinate,patrolzone.heading,patrolzone.leg)
self:AddMission(patrolzone.mission)
end
end
for _,_patrolzone in pairs(self.gcicapZones)do
local patrolzone=_patrolzone
if(not patrolzone.mission)or patrolzone.mission:IsOver()then
local Coordinate=patrolzone.zone:GetCoordinate()
patrolzone.mission=AUFTRAG:NewGCICAP(Coordinate,patrolzone.altitude,patrolzone.speed,patrolzone.heading,patrolzone.leg)
self:AddMission(patrolzone.mission)
end
end
for _,_awacszone in pairs(self.awacsZones)do
local awacszone=_awacszone
if(not awacszone.mission)or awacszone.mission:IsOver()then
local Coordinate=awacszone.zone:GetCoordinate()
awacszone.mission=AUFTRAG:NewAWACS(Coordinate,awacszone.altitude,awacszone.speed,awacszone.heading,awacszone.leg)
self:AddMission(awacszone.mission)
end
end
for _,_tankerzone in pairs(self.tankerZones)do
local tankerzone=_tankerzone
if(not tankerzone.mission)or tankerzone.mission:IsOver()then
local Coordinate=tankerzone.zone:GetCoordinate()
tankerzone.mission=AUFTRAG:NewTANKER(Coordinate,tankerzone.altitude,tankerzone.speed,tankerzone.heading,tankerzone.leg,tankerzone.refuelsystem)
self:AddMission(tankerzone.mission)
end
end
if self.verbose>=2 and#self.legions>0 then
local text="Legions:"
for _,_legion in pairs(self.legions)do
local legion=_legion
local Nassets=legion:CountAssets()
local Nastock=legion:CountAssets(true)
text=text..string.format("\n* %s [%s]: Assets=%s stock=%s",legion.alias,legion:GetState(),Nassets,Nastock)
for _,aname in pairs(AUFTRAG.Type)do
local na=legion:CountAssets(true,{aname})
local np=legion:CountPayloadsInStock({aname})
local nm=legion:CountAssetsOnMission({aname})
if na>0 or np>0 then
text=text..string.format("\n   - %s: assets=%d, payloads=%d, on mission=%d",aname,na,np,nm)
end
end
end
self:T(self.lid..text)
if self.verbose>=3 then
local Ntotal=0
local Nspawned=0
local Nrequested=0
local Nreserved=0
local Nstock=0
local text="\n===========================================\n"
text=text.."Assets:"
for _,_legion in pairs(self.legions)do
local legion=_legion
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
for _,_asset in pairs(cohort.assets)do
local asset=_asset
local state="In Stock"
if asset.flightgroup then
state=asset.flightgroup:GetState()
local mission=legion:GetAssetCurrentMission(asset)
if mission then
state=state..string.format(", Mission \"%s\" [%s]",mission:GetName(),mission:GetType())
end
else
if asset.spawned then
env.info("FF ERROR: asset has opsgroup but is NOT spawned!")
end
if asset.requested and asset.isReserved then
env.info("FF ERROR: asset is requested and reserved. Should not be both!")
state="Reserved+Requested!"
elseif asset.isReserved then
state="Reserved"
elseif asset.requested then
state="Requested"
end
end
text=text..string.format("\n[UID=%03d] %s Legion=%s [%s]: State=%s [RID=%s]",
asset.uid,asset.spawngroupname,legion.alias,cohort.name,state,tostring(asset.rid))
if asset.spawned then
Nspawned=Nspawned+1
end
if asset.requested then
Nrequested=Nrequested+1
end
if asset.isReserved then
Nreserved=Nreserved+1
end
if not(asset.spawned or asset.requested or asset.isReserved)then
Nstock=Nstock+1
end
Ntotal=Ntotal+1
end
end
end
text=text.."\n-------------------------------------------"
text=text..string.format("\nNstock     = %d",Nstock)
text=text..string.format("\nNreserved  = %d",Nreserved)
text=text..string.format("\nNrequested = %d",Nrequested)
text=text..string.format("\nNspawned   = %d",Nspawned)
text=text..string.format("\nNtotal     = %d (=%d)",Ntotal,Nstock+Nspawned+Nrequested+Nreserved)
text=text.."\n==========================================="
self:I(self.lid..text)
end
end
if self.verbose>=2 and#self.missionqueue>0 then
local text="Mission queue:"
for i,_mission in pairs(self.missionqueue)do
local mission=_mission
local target=mission:GetTargetName()or"unknown"
text=text..string.format("\n[%d] %s (%s): status=%s, target=%s",i,mission.name,mission.type,mission.status,target)
end
self:I(self.lid..text)
end
if self.verbose>=2 and#self.targetqueue>0 then
local text="Target queue:"
for i,_target in pairs(self.targetqueue)do
local target=_target
text=text..string.format("\n[%d] %s: status=%s, life=%d",i,target:GetName(),target:GetState(),target:GetLife())
end
self:I(self.lid..text)
end
if self.verbose>=2 and#self.transportqueue>0 then
local text="Transport queue:"
for i,_transport in pairs(self.transportqueue)do
local transport=_transport
text=text..string.format("\n[%d] UID=%d: status=%s",i,transport.uid,transport:GetState())
end
self:I(self.lid..text)
end
self:__Status(-30)
end
function COMMANDER:onafterMissionAssign(From,Event,To,Mission,Legions)
self:AddMission(Mission)
Mission.statusCommander=AUFTRAG.Status.QUEUED
for _,_Legion in pairs(Legions)do
local Legion=_Legion
self:T(self.lid..string.format("Assigning mission \"%s\" [%s] to legion \"%s\"",Mission.name,Mission.type,Legion.alias))
Legion:AddMission(Mission)
Legion:MissionRequest(Mission)
end
end
function COMMANDER:onafterMissionCancel(From,Event,To,Mission)
self:T(self.lid..string.format("Cancelling mission \"%s\" [%s] in status %s",Mission.name,Mission.type,Mission.status))
Mission.statusCommander=AUFTRAG.Status.CANCELLED
if Mission:IsPlanned()then
self:RemoveMission(Mission)
else
if#Mission.legions>0 then
for _,_legion in pairs(Mission.legions)do
local legion=_legion
legion:MissionCancel(Mission)
end
end
end
end
function COMMANDER:onafterTransportAssign(From,Event,To,Transport,Legions)
Transport.statusCommander=OPSTRANSPORT.Status.QUEUED
for _,_Legion in pairs(Legions)do
local Legion=_Legion
self:T(self.lid..string.format("Assigning transport UID=%d to legion \"%s\"",Transport.uid,Legion.alias))
Legion:AddOpsTransport(Transport)
Legion:TransportRequest(Transport)
end
end
function COMMANDER:onafterTransportCancel(From,Event,To,Transport)
self:T(self.lid..string.format("Cancelling Transport UID=%d in status %s",Transport.uid,Transport:GetState()))
Transport.statusCommander=OPSTRANSPORT.Status.CANCELLED
if Transport:IsPlanned()then
self:RemoveTransport(Transport)
else
if#Transport.legions>0 then
for _,_legion in pairs(Transport.legions)do
local legion=_legion
legion:TransportCancel(Transport)
end
end
end
end
function COMMANDER:onafterOpsOnMission(From,Event,To,OpsGroup,Mission)
self:T2(self.lid..string.format("Group \"%s\" on mission \"%s\" [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType()))
end
function COMMANDER:CheckOpsQueue()
local Nops=#self.opsqueue
if Nops==0 then
return nil
end
for _,_ops in pairs(self.opsqueue)do
local operation=_ops
if operation:IsRunning()then
for _,_mission in pairs(operation.missions or{})do
local mission=_mission
if mission.phase==nil or(mission.phase and mission.phase==operation.phase)and mission:IsPlanned()then
self:AddMission(mission)
end
end
for _,_target in pairs(operation.targets or{})do
local target=_target
if(target.phase==nil or(target.phase and target.phase==operation.phase))and(not self:IsTarget(target))then
self:AddTarget(target)
end
end
end
end
end
function COMMANDER:CheckTargetQueue()
local Ntargets=#self.targetqueue
if Ntargets==0 then
return nil
end
for i=#self.targetqueue,1,-1 do
local target=self.targetqueue[i]
if(not target:IsAlive())or target:EvalConditionsAny(target.conditionStop)then
for _,_resource in pairs(target.resources)do
local resource=_resource
if resource.mission and resource.mission:IsNotOver()then
self:MissionCancel(resource.mission)
end
end
table.remove(self.targetqueue,i)
end
end
local NoLimit=self:_CheckMissionLimit("Total")
if NoLimit==false then
return nil
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.threatlevel0>taskB.threatlevel0)
end
table.sort(self.targetqueue,_sort)
local vip=math.huge
for _,_target in pairs(self.targetqueue)do
local target=_target
if target:IsAlive()and target.importance and target.importance<vip then
vip=target.importance
end
end
for _,_target in pairs(self.targetqueue)do
local target=_target
local isAlive=target:IsAlive()
local isImportant=(target.importance==nil or target.importance<=vip)
local isReadyStart=target:EvalConditionsAll(target.conditionStart)
local text=string.format("Target %s: Alive=%s, Important=%s",target:GetName(),tostring(isAlive),tostring(isImportant))
self:T2(self.lid..text)
if isAlive and isImportant then
for _,_resource in pairs(target.resources or{})do
local resource=_resource
local missionType=resource.MissionType
if(not resource.mission)or resource.mission:IsOver()then
self:T2(self.lid..string.format("Target \"%s\" ==> Creating mission type %s: Nmin=%d, Nmax=%d",target:GetName(),missionType,resource.Nmin,resource.Nmax))
local mission=AUFTRAG:NewFromTarget(target,missionType)
if mission then
mission:SetRequiredAssets(resource.Nmin,resource.Nmax)
mission:SetRequiredAttribute(resource.Attributes)
mission:SetRequiredProperty(resource.Properties)
mission.operation=target.operation
resource.mission=mission
self:AddMission(resource.mission)
end
end
end
end
end
end
function COMMANDER:CheckMissionQueue()
local Nmissions=#self.missionqueue
if Nmissions==0 then
return nil
end
local NoLimit=self:_CheckMissionLimit("Total")
if NoLimit==false then
return nil
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.Tstart<taskB.Tstart)
end
table.sort(self.missionqueue,_sort)
local vip=math.huge
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.importance and mission.importance<vip then
vip=mission.importance
end
end
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:IsPlanned()and mission:IsReadyToGo()and(mission.importance==nil or mission.importance<=vip)and self:_CheckMissionLimit(mission.type)then
local recruited,assets,legions=self:RecruitAssetsForMission(mission)
if recruited then
mission:_AddAssets(assets)
local EscortAvail=self:RecruitAssetsForEscort(mission,assets)
local TransportAvail=true
if EscortAvail then
if mission.NcarriersMin then
local Transport=nil
local Legions=mission.transportLegions or self.legions
TransportAvail,Transport=LEGION.AssignAssetsForTransport(self,Legions,assets,mission.NcarriersMin,mission.NcarriersMax,mission.transportDeployZone,mission.transportDisembarkZone,mission.carrierCategories,mission.carrierAttributes,mission.carrierProperties)
if TransportAvail and Transport then
mission.opstransport=Transport
end
end
end
if EscortAvail and TransportAvail then
self:MissionAssign(mission,legions)
else
LEGION.UnRecruitAssets(assets,mission)
end
return
end
else
end
end
end
function COMMANDER:_GetCohorts(Legions,Cohorts,Operation)
local function CheckOperation(LegionOrCohort)
if#self.opsqueue==0 then
return true
end
local isAvail=true
if Operation then
isAvail=false
end
for _,_operation in pairs(self.opsqueue)do
local operation=_operation
local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort)
if isOps and operation:IsRunning()then
isAvail=false
if Operation==nil then
return false
else
if Operation.uid==operation.uid then
return true
end
end
end
end
return isAvail
end
local cohorts={}
if(Legions and#Legions>0)or(Cohorts and#Cohorts>0)then
for _,_legion in pairs(Legions or{})do
local legion=_legion
local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true
if legion:IsRunning()and Runway then
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
if CheckOperation(cohort.legion)or CheckOperation(cohort)then
table.insert(cohorts,cohort)
end
end
end
end
for _,_cohort in pairs(Cohorts or{})do
local cohort=_cohort
if CheckOperation(cohort)then
table.insert(cohorts,cohort)
end
end
else
for _,_legion in pairs(self.legions)do
local legion=_legion
local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true
if legion:IsRunning()and Runway then
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
if CheckOperation(cohort.legion)or CheckOperation(cohort)then
table.insert(cohorts,cohort)
end
end
end
end
end
return cohorts
end
function COMMANDER:RecruitAssetsForMission(Mission)
self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]",Mission:GetName(),Mission:GetType()))
local NreqMin,NreqMax=Mission:GetRequiredAssets()
local TargetVec2=Mission:GetTargetVec2()
local Payloads=Mission.payloads
local MaxWeight=nil
if Mission.NcarriersMin then
local legions=self.legions
local cohorts=nil
if Mission.transportLegions or Mission.transportCohorts then
legions=Mission.transportLegions
cohorts=Mission.transportCohorts
end
local Cohorts=LEGION._GetCohorts(legions,cohorts)
local transportcohorts={}
for _,_cohort in pairs(Cohorts)do
local cohort=_cohort
local can=LEGION._CohortCan(cohort,AUFTRAG.Type.OPSTRANSPORT,Mission.carrierCategories,Mission.carrierAttributes,Mission.carrierProperties,nil,TargetVec2)
if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then
MaxWeight=cohort.cargobayLimit
end
end
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
legions=Mission.specialLegions
cohorts=Mission.specialCohorts
end
local Cohorts=LEGION._GetCohorts(legions,cohorts,Mission.operation,self.opsqueue)
self:T(self.lid..string.format("Found %d cohort candidates for mission",#Cohorts))
local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,Mission.type,Mission.alert5MissionType,NreqMin,NreqMax,TargetVec2,Payloads,
Mission.engageRange,Mission.refuelSystem,nil,nil,MaxWeight,nil,Mission.attributes,Mission.properties,{Mission.engageWeaponType})
return recruited,assets,legions
end
function COMMANDER:RecruitAssetsForEscort(Mission,Assets)
if Mission.NescortMin and Mission.NescortMax and(Mission.NescortMin>0 or Mission.NescortMax>0)then
local Cohorts=self:_GetCohorts(Mission.escortLegions,Mission.escortCohorts,Mission.operation)
local assigned=LEGION.AssignAssetsForEscort(self,Cohorts,Assets,Mission.NescortMin,Mission.NescortMax,Mission.escortMissionType,Mission.escortTargetTypes,Mission.escortEngageRange)
return assigned
end
return true
end
function COMMANDER:RecruitAssetsForTarget(Target,MissionType,NassetsMin,NassetsMax)
local Cohorts=self:_GetCohorts()
local TargetVec2=Target:GetVec2()
local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,MissionType,nil,NassetsMin,NassetsMax,TargetVec2)
return recruited,assets,legions
end
function COMMANDER:CheckTransportQueue()
local Ntransports=#self.transportqueue
if Ntransports==0 then
return nil
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.Tstart<taskB.Tstart)
end
table.sort(self.transportqueue,_sort)
local vip=math.huge
for _,_transport in pairs(self.transportqueue)do
local transport=_transport
if transport.importance and transport.importance<vip then
vip=transport.importance
end
end
for _,_transport in pairs(self.transportqueue)do
local transport=_transport
if transport:IsPlanned()and transport:IsReadyToGo()and(transport.importance==nil or transport.importance<=vip)then
local cargoOpsGroups=transport:GetCargoOpsGroups(false)
local weightGroup=0
local TotalWeight=0
if#cargoOpsGroups>0 then
for _,_opsgroup in pairs(cargoOpsGroups)do
local opsgroup=_opsgroup
local weight=opsgroup:GetWeightTotal()
if weight>weightGroup then
weightGroup=weight
end
TotalWeight=TotalWeight+weight
end
end
if weightGroup>0 then
local recruited,assets,legions=self:RecruitAssetsForTransport(transport,weightGroup,TotalWeight)
if recruited then
for _,_asset in pairs(assets)do
local asset=_asset
transport:AddAsset(asset)
end
self:TransportAssign(transport,legions)
return
else
LEGION.UnRecruitAssets(assets)
end
end
else
end
end
end
function COMMANDER:RecruitAssetsForTransport(Transport,CargoWeight,TotalWeight)
if CargoWeight==0 then
return false,{},{}
end
local Cohorts=self:_GetCohorts()
local TargetVec2=Transport:GetDeployZone():GetVec2()
local NreqMin,NreqMax=Transport:GetRequiredCarriers()
local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NreqMin,NreqMax,TargetVec2,nil,nil,nil,CargoWeight,TotalWeight)
return recruited,assets,legions
end
function COMMANDER:_CheckMissionLimit(MissionType)
local limit=self.limitMission[MissionType]
if limit then
if MissionType=="Total"then
MissionType=AUFTRAG.Type
end
local N=self:CountMissions(MissionType,true)
if N>=limit then
return false
end
end
return true
end
function COMMANDER:CountAssets(InStock,MissionTypes,Attributes)
local N=0
for _,_legion in pairs(self.legions)do
local legion=_legion
N=N+legion:CountAssets(InStock,MissionTypes,Attributes)
end
return N
end
function COMMANDER:CountMissions(MissionTypes,OnlyRunning)
local N=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if(not OnlyRunning)or(mission.statusCommander~=AUFTRAG.Status.PLANNED)then
if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then
N=N+1
end
end
end
return N
end
function COMMANDER:GetAssets(InStock,Legions,MissionTypes,Attributes)
local assets={}
for _,_legion in pairs(Legions or self.legions)do
local legion=_legion
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
for _,_asset in pairs(cohort.assets)do
local asset=_asset
if not(asset.spawned or asset.isReserved or asset.requested)then
table.insert(assets,asset)
end
end
end
end
return assets
end
function COMMANDER:GetLegionsForMission(Mission)
local legions={}
for _,_legion in pairs(self.legions)do
local legion=_legion
local Nassets=0
if legion:IsAirwing()then
Nassets=legion:CountAssetsWithPayloadsInStock(Mission.payloads,{Mission.type},Attributes)
else
Nassets=legion:CountAssets(true,{Mission.type},Attributes)
end
if Nassets>0 and false then
local coord=Mission:GetTargetCoordinate()
if coord then
local distance=UTILS.MetersToNM(coord:Get2DDistance(legion:GetCoordinate()))
local dist=UTILS.Round(distance/10,0)
self:T(self.lid..string.format("Got legion %s with Nassets=%d and dist=%.1f NM, rounded=%.1f",legion.alias,Nassets,distance,dist))
table.insert(legions,{airwing=legion,distance=distance,dist=dist,targetcoord=coord,nassets=Nassets})
end
end
if Nassets>0 then
table.insert(legions,legion)
end
end
return legions
end
function COMMANDER:GetAssetsOnMission(MissionTypes)
local assets={}
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then
for _,_asset in pairs(mission.assets or{})do
local asset=_asset
table.insert(assets,asset)
end
end
end
return assets
end
FLEET={
ClassName="FLEET",
verbose=0,
pathfinding=false,
}
FLEET.version="0.0.1"
function FLEET:New(WarehouseName,FleetName)
local self=BASE:Inherit(self,LEGION:New(WarehouseName,FleetName))
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!",WarehouseName))
return nil
end
self.lid=string.format("FLEET %s | ",self.alias)
self:SetRetreatZones()
if self:IsShip()then
local wh=self.warehouse
local group=wh:GetGroup()
self.warehouseOpsGroup=NAVYGROUP:New(group)
self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName())
end
self:AddTransition("*","NavyOnMission","*")
return self
end
function FLEET:AddFlotilla(Flotilla)
table.insert(self.cohorts,Flotilla)
self:AddAssetToFlotilla(Flotilla,Flotilla.Ngroups)
Flotilla:SetFleet(self)
if Flotilla:IsStopped()then
Flotilla:Start()
end
return self
end
function FLEET:AddAssetToFlotilla(Flotilla,Nassets)
if Flotilla then
local Group=GROUP:FindByName(Flotilla.templatename)
if Group then
local text=string.format("Adding asset %s to flotilla %s",Group:GetName(),Flotilla.name)
self:T(self.lid..text)
self:AddAsset(Group,Nassets,nil,nil,nil,nil,Flotilla.skill,Flotilla.livery,Flotilla.name)
else
self:E(self.lid.."ERROR: Group does not exist!")
end
else
self:E(self.lid.."ERROR: Flotilla does not exit!")
end
return self
end
function FLEET:SetPathfinding(Switch)
self.pathfinding=Switch
return self
end
function FLEET:SetRetreatZones(RetreatZoneSet)
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
return self
end
function FLEET:AddRetreatZone(RetreatZone)
self.retreatZones:AddZone(RetreatZone)
return self
end
function FLEET:GetRetreatZones()
return self.retreatZones
end
function FLEET:GetFlotilla(FlotillaName)
local flotilla=self:_GetCohort(FlotillaName)
return flotilla
end
function FLEET:GetFlotillaOfAsset(Asset)
local flotilla=self:GetFlotilla(Asset.squadname)
return flotilla
end
function FLEET:RemoveAssetFromFlotilla(Asset)
local flotilla=self:GetFlotillaOfAsset(Asset)
if flotilla then
flotilla:DelAsset(Asset)
end
end
function FLEET:onafterStart(From,Event,To)
self:GetParent(self,FLEET).onafterStart(self,From,Event,To)
self:I(self.lid..string.format("Starting FLEET v%s",FLEET.version))
end
function FLEET:onafterStatus(From,Event,To)
self:GetParent(self).onafterStatus(self,From,Event,To)
local fsmstate=self:GetState()
self:CheckTransportQueue()
self:CheckMissionQueue()
self:_TacticalOverview()
if self.verbose>=1 then
local Nmissions=self:CountMissionsInQueue()
local Npq,Np,Nq=self:CountAssetsOnMission()
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]",self:CountAssets(),Npq,Np,Nq)
local text=string.format("%s: Missions=%d, Flotillas=%d, Assets=%s",fsmstate,Nmissions,#self.cohorts,assets)
self:I(self.lid..text)
end
if self.verbose>=2 then
local text=string.format("Missions Total=%d:",#self.missionqueue)
for i,_mission in pairs(self.missionqueue)do
local mission=_mission
local prio=string.format("%d/%s",mission.prio,tostring(mission.importance));if mission.urgent then prio=prio.." (!)"end
local assets=string.format("%d/%d",mission:CountOpsGroups(),mission.Nassets or 0)
local target=string.format("%d/%d Damage=%.1f",mission:CountMissionTargets(),mission:GetTargetInitialNumber(),mission:GetTargetDamage())
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s",i,mission.name,mission.type,mission.status,prio,assets,target)
end
self:I(self.lid..text)
end
if self.verbose>=2 then
local text=string.format("Transports Total=%d:",#self.transportqueue)
for i,_transport in pairs(self.transportqueue)do
local transport=_transport
local prio=string.format("%d/%s",transport.prio,tostring(transport.importance));if transport.urgent then prio=prio.." (!)"end
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d",transport.Ncargo,transport.Ndelivered,transport.Ncarrier)
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s",i,transport.uid,transport:GetState(),prio,carriers)
end
self:I(self.lid..text)
end
if self.verbose>=3 then
local text="Flotillas:"
for i,_flotilla in pairs(self.cohorts)do
local flotilla=_flotilla
local callsign=flotilla.callsignName and UTILS.GetCallsignName(flotilla.callsignName)or"N/A"
local modex=flotilla.modex and flotilla.modex or-1
local skill=flotilla.skill and tostring(flotilla.skill)or"N/A"
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s",flotilla.name,flotilla:GetState(),flotilla.aircrafttype,flotilla:CountAssets(true),#flotilla.assets,callsign,modex,skill)
end
self:I(self.lid..text)
end
end
function FLEET:onafterNavyOnMission(From,Event,To,NavyGroup,Mission)
self:T(self.lid..string.format("Group %s on %s mission %s",NavyGroup:GetName(),Mission:GetType(),Mission:GetName()))
end
FLIGHTCONTROL={
ClassName="FLIGHTCONTROL",
verbose=0,
lid=nil,
theatre=nil,
airbasename=nil,
airbase=nil,
airbasetype=nil,
zoneAirbase=nil,
parking={},
runways={},
flights={},
clients={},
atis=nil,
Nlanding=nil,
dTlanding=nil,
Nparkingspots=nil,
holdingpatterns={},
hpcounter=0,
nosubs=false,
Nplayers=0,
}
FLIGHTCONTROL.FlightStatus={
UNKNOWN="Unknown",
PARKING="Parking",
READYTX="Ready To Taxi",
TAXIOUT="Taxi To Runway",
READYTO="Ready For Takeoff",
TAKEOFF="Takeoff",
INBOUND="Inbound",
HOLDING="Holding",
LANDING="Landing",
TAXIINB="Taxi To Parking",
ARRIVED="Arrived",
}
FLIGHTCONTROL.version="0.7.7"
function FLIGHTCONTROL:New(AirbaseName,Frequency,Modulation,PathToSRS,Port,GoogleKey)
local self=BASE:Inherit(self,FSM:New())
self.airbase=AIRBASE:FindByName(AirbaseName)
self.airbasename=AirbaseName
self.lid=string.format("FLIGHTCONTROL %s | ",AirbaseName)
if not self.airbase then
self:E(string.format("ERROR: Could not find airbase %s!",tostring(AirbaseName)))
return nil
end
if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
self:E(string.format("ERROR: Airbase %s is not an AIRDROME! Script does not handle FARPS or ships.",tostring(AirbaseName)))
return nil
end
self.airbasetype=self.airbase:GetAirbaseCategory()
self.theatre=env.mission.theatre
self.zoneAirbase=ZONE_RADIUS:New("FC",self:GetCoordinate():GetVec2(),UTILS.NMToMeters(5))
self:_AddHoldingPatternBackup()
self.alias=self.airbasename.." Tower"
self:SetLimitLanding(2,0)
self:SetLimitTaxi(2,false,0)
self:SetLandingInterval()
self:SetFrequency(Frequency,Modulation)
self:SetMarkHoldingPattern(true)
self:SetRunwayRepairtime()
self.nosubs=false
self:SetCallSignOptions(true,true)
self.msrsqueue=MSRSQUEUE:New(self.alias)
self:SetTransmitOnlyWithPlayers(true)
local path=PathToSRS or MSRS.path
local port=Port or MSRS.port or 5002
self:SetSRSPort(port)
self.msrsTower=MSRS:New(path,Frequency,Modulation)
self.msrsTower:SetPort(port)
if GoogleKey then
self.msrsTower:SetProviderOptionsGoogle(GoogleKey,GoogleKey)
self.msrsTower:SetProvider(MSRS.Provider.GOOGLE)
end
self.msrsTower:SetCoordinate(self:GetCoordinate())
self:SetSRSTower()
self.msrsPilot=MSRS:New(PathToSRS,Frequency,Modulation)
self.msrsPilot:SetPort(self.Port)
if GoogleKey then
self.msrsPilot:SetProviderOptionsGoogle(GoogleKey,GoogleKey)
self.msrsPilot:SetProvider(MSRS.Provider.GOOGLE)
end
self.msrsTower:SetCoordinate(self:GetCoordinate())
self:SetSRSPilot()
self.dTmessage=10
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","StatusUpdate","*")
self:AddTransition("*","PlayerKilledGuard","*")
self:AddTransition("*","PlayerSpeeding","*")
self:AddTransition("*","RunwayDestroyed","*")
self:AddTransition("*","RunwayRepaired","*")
self:AddTransition("*","Stop","Stopped")
_DATABASE:AddFlightControl(self)
return self
end
function FLIGHTCONTROL:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function FLIGHTCONTROL: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
end
function FLIGHTCONTROL:SwitchSubtitlesOff()
self.nosubs=true
return self
end
function FLIGHTCONTROL:SetFrequency(Frequency,Modulation)
self.frequency=Frequency or 305
self.modulation=Modulation or radio.modulation.AM
if self.msrsPilot then
self.msrsPilot:SetFrequencies(Frequency)
self.msrsPilot:SetModulations(Modulation)
end
if self.msrsTower then
self.msrsTower:SetFrequencies(Frequency)
self.msrsTower:SetModulations(Modulation)
end
return self
end
function FLIGHTCONTROL:SetSRSPort(Port)
self.Port=Port or 5002
return self
end
function FLIGHTCONTROL:_SetSRSOptions(msrs,Gender,Culture,Voice,Volume,Label,PathToGoogleCredentials,Port)
Gender=Gender or"female"
Culture=Culture or"en-GB"
Volume=Volume or 1.0
if msrs then
msrs:SetGender(Gender)
msrs:SetCulture(Culture)
msrs:SetVoice(Voice)
msrs:SetVolume(Volume)
msrs:SetLabel(Label)
msrs:SetCoalition(self:GetCoalition())
msrs:SetPort(Port or self.Port or 5002)
end
return self
end
function FLIGHTCONTROL:SetSRSTower(Gender,Culture,Voice,Volume,Label)
if self.msrsTower then
self:_SetSRSOptions(self.msrsTower,Gender or"female",Culture or"en-GB",Voice,Volume,Label or self.alias)
end
return self
end
function FLIGHTCONTROL:SetSRSPilot(Gender,Culture,Voice,Volume,Label)
if self.msrsPilot then
self:_SetSRSOptions(self.msrsPilot,Gender or"male",Culture or"en-US",Voice,Volume,Label or"Pilot")
end
return self
end
function FLIGHTCONTROL:SetLimitLanding(Nlanding,Ntakeoff)
self.NlandingTot=Nlanding or 2
self.NlandingTakeoff=Ntakeoff or 0
return self
end
function FLIGHTCONTROL:SetLandingInterval(dt)
self.dTlanding=dt or 180
return self
end
function FLIGHTCONTROL:SetLimitTaxi(Ntaxi,IncludeInbound,Nlanding)
self.NtaxiTot=Ntaxi or 2
self.NtaxiInbound=IncludeInbound
self.NtaxiLanding=Nlanding or 0
return self
end
function FLIGHTCONTROL:AddHoldingPattern(ArrivalZone,Heading,Length,FlightlevelMin,FlightlevelMax,Prio)
if type(ArrivalZone)=="string"then
ArrivalZone=ZONE:New(ArrivalZone)
end
self.hpcounter=self.hpcounter+1
local hp={}
hp.uid=self.hpcounter
hp.arrivalzone=ArrivalZone
hp.name=string.format("%s-%d",ArrivalZone:GetName(),hp.uid)
hp.pos0=ArrivalZone:GetCoordinate()
hp.pos1=hp.pos0:Translate(UTILS.NMToMeters(Length or 15),Heading)
hp.angelsmin=FlightlevelMin or 5
hp.angelsmax=FlightlevelMax or 15
hp.prio=Prio or 50
hp.stacks={}
for i=hp.angelsmin,hp.angelsmax do
local stack={}
stack.angels=i
stack.flightgroup=nil
stack.pos0=UTILS.DeepCopy(hp.pos0)
stack.pos0:SetAltitude(UTILS.FeetToMeters(i*1000))
stack.pos1=UTILS.DeepCopy(hp.pos1)
stack.pos1:SetAltitude(UTILS.FeetToMeters(i*1000))
stack.heading=Heading
table.insert(hp.stacks,stack)
end
table.insert(self.holdingpatterns,hp)
local function _sort(a,b)
return a.prio<b.prio
end
table.sort(self.holdingpatterns,_sort)
return self
end
function FLIGHTCONTROL:RemoveHoldingPattern(HoldingPattern)
for i,_holdingpattern in pairs(self.holdingpatterns)do
local hp=_holdingpattern
if hp.uid==HoldingPattern.uid then
self:_UnMarkHoldingPattern(HoldingPattern)
table.remove(self.holdingpatterns,i)
return self
end
end
return self
end
function FLIGHTCONTROL:SetMarkHoldingPattern(Switch)
if Switch==nil then
Switch=true
end
self.markPatterns=Switch
return self
end
function FLIGHTCONTROL:SetSpeedLimitTaxi(SpeedLimit)
if SpeedLimit then
self.speedLimitTaxi=UTILS.KnotsToMps(SpeedLimit)
else
self.speedLimitTaxi=nil
end
return self
end
function FLIGHTCONTROL:SetParkingGuard(TemplateGroupName)
local alias=string.format("Parking Guard %s",self.airbasename)
self.parkingGuard=SPAWN:NewWithAlias(TemplateGroupName,alias)
return self
end
function FLIGHTCONTROL:SetParkingGuardStatic(TemplateStaticName)
local alias=string.format("Parking Guard %s",self.airbasename)
self.parkingGuard=SPAWNSTATIC:NewFromStatic(TemplateStaticName):InitNamePrefix(alias)
return self
end
function FLIGHTCONTROL:SetATIS(Atis)
self.atis=Atis
return self
end
function FLIGHTCONTROL:GetCoordinate()
return self.airbase:GetCoordinate()
end
function FLIGHTCONTROL:GetCoalition()
return self.airbase:GetCoalition()
end
function FLIGHTCONTROL:GetCountry()
return self.airbase:GetCountry()
end
function FLIGHTCONTROL:SetRunwayRepairtime(RepairTime)
self.runwayrepairtime=RepairTime or 3600
return self
end
function FLIGHTCONTROL:GetRunwayRepairtime()
if self.runwaydestroyed then
local Tnow=timer.getAbsTime()
local Tsince=Tnow-self.runwaydestroyed
local Trepair=math.max(self.runwayrepairtime-Tsince,0)
return Trepair
end
return 0
end
function FLIGHTCONTROL:IsRunwayOperational()
if self.airbase then
if self.runwaydestroyed then
return false
else
return true
end
end
return nil
end
function FLIGHTCONTROL:IsRunwayDestroyed()
if self.airbase then
if self.runwaydestroyed then
return true
else
return false
end
end
return nil
end
function FLIGHTCONTROL:IsFlight(Flight)
for _,_flight in pairs(self.flights)do
local flight=_flight
if flight.groupname==Flight.groupname then
return true
end
end
return false
end
function FLIGHTCONTROL:IsCoordinateRunway(Coordinate)
local runways=self.airbase:GetRunways()
for _,_runway in pairs(runways)do
local runway=_runway
if runway.zone:IsCoordinateInZone(Coordinate)then
return true
end
end
return false
end
function FLIGHTCONTROL:onafterStart()
self:I(self.lid..string.format("Starting FLIGHTCONTROL v%s for airbase %s of type %d on map %s",FLIGHTCONTROL.version,self.airbasename,self.airbasetype,self.theatre))
self:_InitParkingSpots()
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.EngineStartup)
self:HandleEvent(EVENTS.Takeoff)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Crash,FLIGHTCONTROL.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Dead,FLIGHTCONTROL.OnEventCrashOrDead)
self:HandleEvent(EVENTS.Kill)
self:__StatusUpdate(-1)
end
function FLIGHTCONTROL:onbeforeStatusUpdate()
local Tqueue=self.msrsqueue:CalcTransmisstionDuration()
if Tqueue>0 then
local text=string.format("Still got %d messages in the radio queue. Will call status again in %.1f sec",#self.msrsqueue,Tqueue)
self:T(self.lid..text)
self:__StatusUpdate(-Tqueue)
return false
end
return true
end
function FLIGHTCONTROL:onafterStatusUpdate()
self:T2(self.lid.."Status update")
self:_CheckMarkHoldingPatterns()
if self:IsRunwayOperational()==false then
local Trepair=self:GetRunwayRepairtime()
if Trepair==0 then
self:RunwayRepaired()
else
self:I(self.lid..string.format("Runway still destroyed! Will be repaired in %d sec",Trepair))
end
end
self:_CheckFlights()
self:_CheckQueues()
local rwyLanding=self:GetActiveRunwayText()
local rwyTakeoff=self:GetActiveRunwayText(true)
local Nflights=self:CountFlights()
local NQparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING)
local NQreadytx=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTX)
local NQtaxiout=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIOUT)
local NQreadyto=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTO)
local NQtakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF)
local NQinbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.INBOUND)
local NQholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING)
local NQlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING)
local NQtaxiinb=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB)
local NQarrived=self:CountFlights(FLIGHTCONTROL.FlightStatus.ARRIVED)
local Nqueues=(NQparking+NQreadytx+NQtaxiout+NQreadyto+NQtakeoff)+(NQinbound+NQholding+NQlanding+NQtaxiinb+NQarrived)
local nfree=self.Nparkingspots-NQarrived-NQparking
local Nfree=self:CountParking(AIRBASE.SpotStatus.FREE)
local Noccu=self:CountParking(AIRBASE.SpotStatus.OCCUPIED)
local Nresv=self:CountParking(AIRBASE.SpotStatus.RESERVED)
if Nfree+Noccu+Nresv~=self.Nparkingspots then
self:E(self.lid..string.format("WARNING: Number of parking spots does not match! Nfree=%d, Noccu=%d, Nreserved=%d != %d total",Nfree,Noccu,Nresv,self.Nparkingspots))
end
if self.verbose>=1 then
local text=string.format("State %s - Runway Landing=%s, Takeoff=%s  - Parking F=%d/O=%d/R=%d of %d - Flights=%s: Qpark=%d Qtxout=%d Qready=%d Qto=%d | Qinbound=%d Qhold=%d Qland=%d Qtxinb=%d Qarr=%d",
self:GetState(),rwyLanding,rwyTakeoff,Nfree,Noccu,Nresv,self.Nparkingspots,Nflights,NQparking,NQtaxiout,NQreadyto,NQtakeoff,NQinbound,NQholding,NQlanding,NQtaxiinb,NQarrived)
self:I(self.lid..text)
end
if Nflights==Nqueues then
else
self:E(string.format("WARNING: Number of total flights %d!=%d number of flights in all queues!",Nflights,Nqueues))
end
if self.verbose>=2 then
local text="Holding Patterns:"
for i,_pattern in pairs(self.holdingpatterns)do
local pattern=_pattern
text=text..string.format("\n[%d] Pattern %s [Prio=%d, UID=%d]: Stacks=%d, Angels %d - %d",i,pattern.name,pattern.prio,pattern.uid,#pattern.stacks,pattern.angelsmin,pattern.angelsmax)
if self.verbose>=4 then
for _,_stack in pairs(pattern.stacks)do
local stack=_stack
local text=string.format("",stack.angels,stack)
end
end
end
self:I(self.lid..text)
end
self:__StatusUpdate(-30)
end
function FLIGHTCONTROL:onafterStop()
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.EngineStartup)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Kill)
end
function FLIGHTCONTROL:OnEventBirth(EventData)
self:F3({EvendData=EventData})
if EventData and EventData.IniGroupName and EventData.IniUnit then
self:T3(self.lid..string.format("BIRTH: unit  = %s",tostring(EventData.IniUnitName)))
self:T3(self.lid..string.format("BIRTH: group = %s",tostring(EventData.IniGroupName)))
local unit=EventData.IniUnit
if unit:IsAir()then
local bornhere=EventData.Place and EventData.Place:GetName()==self.airbasename or false
local playerunit,playername=self:_GetPlayerUnitAndName(EventData.IniUnitName)
if playername or bornhere then
self:ScheduleOnce(0.5,self._CreateFlightGroup,self,EventData.IniGroup)
end
if bornhere then
self:SpawnParkingGuard(unit)
end
end
end
end
function FLIGHTCONTROL:OnEventCrashOrDead(EventData)
if EventData then
if EventData.IniUnitName then
if self.airbase and self.airbasename and self.airbasename==EventData.IniUnitName then
self:RunwayDestroyed()
end
end
end
end
function FLIGHTCONTROL:OnEventLand(EventData)
self:F3({EvendData=EventData})
self:T2(self.lid..string.format("LAND: unit  = %s",tostring(EventData.IniUnitName)))
self:T3(self.lid..string.format("LAND: group = %s",tostring(EventData.IniGroupName)))
end
function FLIGHTCONTROL:OnEventTakeoff(EventData)
self:F3({EvendData=EventData})
self:T2(self.lid..string.format("TAKEOFF: unit  = %s",tostring(EventData.IniUnitName)))
self:T3(self.lid..string.format("TAKEOFF: group = %s",tostring(EventData.IniGroupName)))
local airbase=EventData.Place
local unit=EventData.IniUnit
if not(airbase or unit)then
self:E(self.lid.."WARNING: Airbase or IniUnit is nil in takeoff event!")
return
end
end
function FLIGHTCONTROL:OnEventEngineStartup(EventData)
self:F3({EvendData=EventData})
self:T2(self.lid..string.format("ENGINESTARTUP: unit  = %s",tostring(EventData.IniUnitName)))
self:T3(self.lid..string.format("ENGINESTARTUP: group = %s",tostring(EventData.IniGroupName)))
end
function FLIGHTCONTROL:OnEventEngineShutdown(EventData)
self:F3({EvendData=EventData})
self:T2(self.lid..string.format("ENGINESHUTDOWN: unit  = %s",tostring(EventData.IniUnitName)))
self:T3(self.lid..string.format("ENGINESHUTDOWN: group = %s",tostring(EventData.IniGroupName)))
end
function FLIGHTCONTROL:OnEventKill(EventData)
self:F3({EvendData=EventData})
self:T2(self.lid..string.format("KILL: ini unit  = %s",tostring(EventData.IniUnitName)))
self:T3(self.lid..string.format("KILL: ini group = %s",tostring(EventData.IniGroupName)))
self:T2(self.lid..string.format("KILL: tgt unit  = %s",tostring(EventData.TgtUnitName)))
self:T3(self.lid..string.format("KILL: tgt group = %s",tostring(EventData.TgtGroupName)))
local guardPrefix=string.format("Parking Guard %s",self.airbasename)
local victimName=EventData.IniUnitName
local killerName=EventData.TgtUnitName
if victimName and victimName:find(guardPrefix)then
env.info(string.format("Parking guard %s killed!",victimName))
for _,_flight in pairs(self.flights)do
local flight=_flight
local element=flight:GetElementByName(killerName)
if element then
env.info(string.format("Parking guard %s killed by %s!",victimName,killerName))
return
end
end
end
end
function FLIGHTCONTROL:onafterRunwayDestroyed(From,Event,To)
self:T(self.lid..string.format("Runway destoyed!"))
self.runwaydestroyed=timer.getAbsTime()
self:TransmissionTower("All flights, our runway was destroyed. All operations are suspended for one hour.",Flight,Delay)
end
function FLIGHTCONTROL:onafterRunwayRepaired(From,Event,To)
self:T(self.lid..string.format("Runway repaired!"))
self.runwaydestroyed=nil
end
function FLIGHTCONTROL:_CheckQueues()
if self.verbose>=2 then
self:_PrintQueue(self.flights,"All flights")
end
local flight,isholding,parking=self:_GetNextFlight()
if flight then
if isholding then
if self:_CheckFlightLanding(flight)then
local dTlanding=99999
if self.Tlanding then
dTlanding=timer.getAbsTime()-self.Tlanding
end
if parking and dTlanding>=self.dTlanding then
local callsign=self:_GetCallsignName(flight)
local runway=self:GetActiveRunwayText()
local text=string.format("%s, %s, you are cleared to land, runway %s",callsign,self.alias,runway)
self:TransmissionTower(text,flight)
if flight.isAI then
local text=string.format("Runway %s, cleared to land, %s",runway,callsign)
self:TransmissionPilot(text,flight,10)
self:_LandAI(flight,parking)
else
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.LANDING)
end
self.Tlanding=timer.getAbsTime()
end
else
self:T3(self.lid..string.format("FYI: Landing clearance for flight %s denied",flight.groupname))
end
else
if self:_CheckFlightTakeoff(flight)then
local callsign=self:_GetCallsignName(flight)
local runway=self:GetActiveRunwayText(true)
local text=string.format("%s, %s, taxi to runway %s, hold short",callsign,self.alias,runway)
if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.READYTO then
text=string.format("%s, %s, cleared for take-off, runway %s",callsign,self.alias,runway)
end
self:TransmissionTower(text,flight)
if flight.isAI then
local text="Wilco, "
if flight:IsUncontrolled()then
text=text..string.format("starting engines, ")
flight:StartUncontrolled()
end
text=text..string.format("runway %s, %s",runway,callsign)
self:TransmissionPilot(text,flight,10)
for _,_element in pairs(flight.elements)do
local element=_element
if element and element.parking then
local spot=self:GetParkingSpotByID(element.parking.TerminalID)
self:RemoveParkingGuard(spot)
end
end
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF)
else
if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.READYTO then
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF)
else
for _,_element in pairs(flight.elements)do
local element=_element
if element.parking then
local spot=self:GetParkingSpotByID(element.parking.TerminalID)
if element.ai then
self:RemoveParkingGuard(spot,15)
else
self:RemoveParkingGuard(spot,10)
end
end
end
end
end
else
self:T3(self.lid..string.format("FYI: Take off for flight %s denied",flight.groupname))
end
end
else
self:T2(self.lid..string.format("FYI: No flight in queue for takeoff or landing"))
end
end
function FLIGHTCONTROL:_CheckFlightTakeoff(flight)
local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING)
local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF,nil,true)
local status=self:GetFlightStatus(flight)
if flight.isAI then
if nlanding>self.NtaxiLanding then
self:T(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %d>%d flight(s) landing",flight.groupname,status,nlanding,self.NtaxiLanding))
return false
end
local ninbound=0
if self.NtaxiInbound then
ninbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB,nil,true)
end
if ntakeoff+ninbound>=self.NtaxiTot then
self:T(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %d>=%d flight(s) taxi/takeoff",flight.groupname,status,ntakeoff,self.NtaxiTot))
return false
end
self:T(self.lid..string.format("AI flight %s [status=%s] cleared for taxi/takeoff! nLanding=%d, nTakeoff=%d",flight.groupname,status,nlanding,ntakeoff))
return true
else
if status==FLIGHTCONTROL.FlightStatus.READYTO then
if nlanding>self.NtaxiLanding then
self:T(self.lid..string.format("Player flight %s [status=%s] not cleared for taxi/takeoff as %d>%d flight(s) landing",flight.groupname,status,nlanding,self.NtaxiLanding))
return false
end
end
self:T(self.lid..string.format("Player flight %s [status=%s] cleared for taxi/takeoff",flight.groupname,status))
return true
end
end
function FLIGHTCONTROL:_CheckFlightLanding(flight)
local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING)
local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF,nil,true)
local status=self:GetFlightStatus(flight)
if flight.isAi then
if ntakeoff<=self.NlandingTakeoff and nlanding<self.NlandingTot then
return true
end
return false
else
if ntakeoff<=self.NlandingTakeoff and nlanding<self.NlandingTot then
return true
end
return false
end
end
function FLIGHTCONTROL:_GetNextFlight()
local flightholding=self:_GetNextFightHolding()
local flightparking=self:_GetNextFightParking()
if not flightholding then
return flightparking,false,nil
end
local nH=flightholding:GetNelements()
local parking=flightholding:GetParking(self.airbase)
if not flightparking then
if parking then
return flightholding,true,parking
else
self:E(self.lid..string.format("WARNING: No flight parking but not enough parking spots for holding flight nH=%d!",nH))
return nil,nil,nil
end
end
if flightholding and flightparking then
local text=string.format("We got a flight holding %s [%s] and parking %s [%s]",flightholding:GetName(),flightholding:GetState(),flightparking:GetName(),flightparking:GetState())
self:T(self.lid..text)
if flightholding.fuellow then
if parking then
return flightholding,true,parking
else
return flightparking,false,nil
end
end
local text=string.format("Flight holding for %d sec, flight parking for %d sec",flightholding:GetHoldingTime(),flightparking:GetParkingTime())
self:T(self.lid..text)
if flightholding.Tholding and flightparking.Tparking and flightholding.Tholding<flightparking.Tparking and parking then
return flightholding,true,parking
else
return flightparking,false,nil
end
end
return nil,nil,nil
end
function FLIGHTCONTROL:_GetNextFightHolding()
local OnlyAI=nil
if self:IsRunwayDestroyed()then
OnlyAI=false
end
local Qholding=self:GetFlights(FLIGHTCONTROL.FlightStatus.HOLDING,nil,OnlyAI)
local TholdingMin=30
if#Qholding==0 then
return nil
elseif#Qholding==1 then
local fg=Qholding[1]
local T=fg:GetHoldingTime()
if T>TholdingMin then
return fg
end
end
local function _sortByFuel(a,b)
local flightA=a
local flightB=b
local fuelA=flightA.group:GetFuelMin()
local fuelB=flightB.group:GetFuelMin()
return fuelA<fuelB
end
local function _sortByTholding(a,b)
local flightA=a
local flightB=b
return flightA.Tholding<flightB.Tholding
end
table.sort(Qholding,_sortByFuel)
for _,_flight in pairs(Qholding)do
local flight=_flight
if flight.fuellow then
return flight
end
end
table.sort(Qholding,_sortByTholding)
local fg=Qholding[1]
local T=fg:GetHoldingTime()
if T>TholdingMin then
return fg
end
return nil
end
function FLIGHTCONTROL:_GetNextFightParking()
local OnlyAI=nil
if self:IsRunwayDestroyed()then
OnlyAI=false
end
local QreadyTO=self:GetFlights(FLIGHTCONTROL.FlightStatus.READYTO,OPSGROUP.GroupStatus.TAXIING,OnlyAI)
if#QreadyTO>0 then
return QreadyTO[1]
end
local QreadyTX=self:GetFlights(FLIGHTCONTROL.FlightStatus.READYTX,OPSGROUP.GroupStatus.PARKING,OnlyAI)
if#QreadyTX>0 then
return QreadyTX[1]
end
if self:IsRunwayDestroyed()then
return nil
end
local Qparking=self:GetFlights(FLIGHTCONTROL.FlightStatus.PARKING,nil,true)
local Nparking=#Qparking
if Nparking==0 then
return nil
end
local function _sortByTparking(a,b)
local flightA=a
local flightB=b
return flightA.Tparking<flightB.Tparking
end
table.sort(Qparking,_sortByTparking)
if self.verbose>=2 then
local text="Parking flights:"
for i,_flight in pairs(Qparking)do
local flight=_flight
text=text..string.format("\n[%d] %s [%s], state=%s [%s]: Tparking=%.1f sec",i,flight.groupname,tostring(flight.actype),flight:GetState(),self:GetFlightStatus(flight),flight:GetParkingTime())
end
self:I(self.lid..text)
end
for i,_flight in pairs(Qparking)do
local flight=_flight
if flight.isAI and flight.isReadyTO then
return flight
end
end
return nil
end
function FLIGHTCONTROL:_PrintQueue(queue,name)
local text=string.format("%s Queue N=%d:",name,#queue)
if#queue==0 then
text=text.." empty."
else
local time=timer.getAbsTime()
for i,_flight in ipairs(queue)do
local flight=_flight
local fuel=flight.group:GetFuelMin()*100
local ai=tostring(flight.isAI)
local actype=tostring(flight.actype)
local holding=flight.Tholding and UTILS.SecondsToClock(time-flight.Tholding,true)or"X"
local parking=flight.Tparking and UTILS.SecondsToClock(time-flight.Tparking,true)or"X"
local holding=flight:GetHoldingTime()
if holding>=0 then
holding=UTILS.SecondsToClock(holding,true)
else
holding="X"
end
local parking=flight:GetParkingTime()
if parking>=0 then
parking=UTILS.SecondsToClock(parking,true)
else
parking="X"
end
local nunits=flight:CountElements()
local state=flight:GetState()
local status=self:GetFlightStatus(flight)
text=text..string.format("\n[%d] %s (%s*%d): status=%s | %s, ai=%s, fuel=%d, holding=%s, parking=%s",
i,flight.groupname,actype,nunits,state,status,ai,fuel,holding,parking)
for j,_element in pairs(flight.elements)do
local element=_element
local life=element.unit:GetLife()
local life0=element.unit:GetLife0()
local park=element.parking and tostring(element.parking.TerminalID)or"N/A"
text=text..string.format("\n  (%d) %s (%s): status=%s, ai=%s, airborne=%s life=%d/%d spot=%s",
j,tostring(element.modex),element.name,tostring(element.status),tostring(element.ai),tostring(element.unit:InAir()),life,life0,park)
end
end
end
self:I(self.lid..text)
return text
end
function FLIGHTCONTROL:SetFlightStatus(flight,status)
self:T(self.lid..string.format("New status %s-->%s for flight %s",flight.controlstatus or"unknown",status,flight:GetName()))
if flight.controlstatus~=status and not flight.isAI then
self:T(self.lid.."Updating menu in 0.2 sec after flight status change")
flight:_UpdateMenu(0.2)
end
flight.controlstatus=status
end
function FLIGHTCONTROL:GetFlightStatus(flight)
if flight then
return flight.controlstatus or"unkonwn"
end
return"unknown"
end
function FLIGHTCONTROL:IsControlling(flight)
local is=flight.flightcontrol and flight.flightcontrol.airbasename==self.airbasename or false
return is
end
function FLIGHTCONTROL:_InQueue(queue,group)
local name=group:GetName()
for _,_flight in pairs(queue)do
local flight=_flight
if name==flight.groupname then
return true
end
end
return false
end
function FLIGHTCONTROL:GetFlights(Status,GroupStatus,AI)
if Status~=nil or GroupStatus~=nil or AI~=nil then
local flights={}
for _,_flight in pairs(self.flights)do
local flight=_flight
local status=self:GetFlightStatus(flight,Status)
if status==Status then
if AI==nil or AI==flight.isAI then
if GroupStatus==nil or GroupStatus==flight:GetState()then
table.insert(flights,flight)
end
end
end
end
return flights
else
return self.flights
end
end
function FLIGHTCONTROL:CountFlights(Status,GroupStatus,AI)
if Status~=nil or GroupStatus~=nil or AI~=nil then
local flights=self:GetFlights(Status,GroupStatus,AI)
return#flights
else
return#self.flights
end
end
function FLIGHTCONTROL:GetActiveRunway()
local rwy=self.airbase:GetActiveRunway()
return rwy
end
function FLIGHTCONTROL:GetActiveRunwayLanding()
local rwy=self.airbase:GetActiveRunwayLanding()
return rwy
end
function FLIGHTCONTROL:GetActiveRunwayTakeoff()
local rwy=self.airbase:GetActiveRunwayTakeoff()
return rwy
end
function FLIGHTCONTROL:GetActiveRunwayText(Takeoff)
local runway
if Takeoff then
runway=self:GetActiveRunwayTakeoff()
else
runway=self:GetActiveRunwayLanding()
end
local name=self.airbase:GetRunwayName(runway,true)
return name or"XX"
end
function FLIGHTCONTROL:_InitParkingSpots()
local parkingdata=self.airbase:GetParkingSpotsTable()
self.parking={}
self.Nparkingspots=0
for _,_spot in pairs(parkingdata)do
local spot=_spot
local text=string.format("Parking ID=%d, Terminal=%d: Free=%s, Client=%s, Dist=%.1f",spot.TerminalID,spot.TerminalType,tostring(spot.Free),tostring(spot.ClientName),spot.DistToRwy)
self:T3(self.lid..text)
self.parking[spot.TerminalID]=spot
if spot.Free then
self:SetParkingFree(spot)
else
local unit=spot.Coordinate:FindClosestUnit(20)
if unit then
local unitname=unit and unit:GetName()or"unknown"
local isalive=unit:IsAlive()
self:T2(self.lid..string.format("FF parking spot %d is occupied by unit %s alive=%s",spot.TerminalID,unitname,tostring(isalive)))
if isalive then
self:SetParkingOccupied(spot,unitname)
self:SpawnParkingGuard(unit)
else
self:SetParkingFree(spot)
end
else
self:E(self.lid..string.format("ERROR: Parking spot is NOT FREE but no unit could be found there!"))
end
end
self.Nparkingspots=self.Nparkingspots+1
end
end
function FLIGHTCONTROL:GetParkingSpotByID(TerminalID)
return self.parking[TerminalID]
end
function FLIGHTCONTROL:_UpdateSpotStatus(spot,status,unitname)
self:T2(self.lid..string.format("Updating parking spot %d status: %s --> %s (unit=%s)",spot.TerminalID,tostring(spot.Status),status,tostring(unitname)))
spot.Status=status
end
function FLIGHTCONTROL:SetParkingFree(spot)
local spot=self:GetParkingSpotByID(spot.TerminalID)
self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.FREE,spot.OccupiedBy or spot.ReservedBy)
spot.OccupiedBy=nil
spot.ReservedBy=nil
self:RemoveParkingGuard(spot)
self:UpdateParkingMarker(spot)
end
function FLIGHTCONTROL:SetParkingReserved(spot,unitname)
local spot=self:GetParkingSpotByID(spot.TerminalID)
self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.RESERVED,unitname)
spot.ReservedBy=unitname or"unknown"
self:UpdateParkingMarker(spot)
end
function FLIGHTCONTROL:SetParkingOccupied(spot,unitname)
local spot=self:GetParkingSpotByID(spot.TerminalID)
self:_UpdateSpotStatus(spot,AIRBASE.SpotStatus.OCCUPIED,unitname)
spot.OccupiedBy=unitname or"unknown"
self:UpdateParkingMarker(spot)
end
function FLIGHTCONTROL:UpdateParkingMarker(spot)
if self.markerParking then
local spot=self:GetParkingSpotByID(spot.TerminalID)
if spot.Status==AIRBASE.SpotStatus.FREE then
if spot.Marker then
spot.Marker:Remove()
end
else
local text=string.format("Spot %d (type %d): %s",spot.TerminalID,spot.TerminalType,spot.Status:upper())
if spot.OccupiedBy then
text=text..string.format("\nOccupied by %s",tostring(spot.OccupiedBy))
end
if spot.ReservedBy then
text=text..string.format("\nReserved for %s",tostring(spot.ReservedBy))
end
if spot.ClientSpot then
text=text..string.format("\nClient %s",tostring(spot.ClientName))
end
if spot.Marker then
if text~=spot.Marker.text or not spot.Marker.shown then
spot.Marker:UpdateText(text)
end
else
spot.Marker=MARKER:New(spot.Coordinate,text):ToAll()
end
end
end
end
function FLIGHTCONTROL:IsParkingFree(spot)
return spot.Status==AIRBASE.SpotStatus.FREE
end
function FLIGHTCONTROL:IsParkingOccupied(spot)
if spot.Status==AIRBASE.SpotStatus.OCCUPIED then
return tostring(spot.OccupiedBy)
else
return false
end
end
function FLIGHTCONTROL:IsParkingReserved(spot)
if spot.Status==AIRBASE.SpotStatus.RESERVED then
return tostring(spot.ReservedBy)
else
return false
end
end
function FLIGHTCONTROL:_GetFreeParkingSpots(terminal)
local freespots={}
local n=0
for _,_parking in pairs(self.parking)do
local parking=_parking
if self:IsParkingFree(parking)then
if terminal==nil or terminal==parking.terminal then
n=n+1
table.insert(freespots,parking)
end
end
end
return n,freespots
end
function FLIGHTCONTROL:GetClosestParkingSpot(Coordinate,TerminalType,Status)
local distmin=math.huge
local spotmin=nil
for TerminalID,Spot in pairs(self.parking)do
local spot=Spot
if(Status==nil or Status==spot.Status)and AIRBASE._CheckTerminalType(spot.TerminalType,TerminalType)then
local dist=Coordinate:Get2DDistance(spot.Coordinate)
if dist<distmin then
distmin=dist
spotmin=spot
end
end
end
return spotmin
end
function FLIGHTCONTROL:_GetPlayerSpot(UnitName)
for TerminalID,Spot in pairs(self.parking)do
local spot=Spot
if spot.ClientName and spot.ClientName==UnitName and(spot.Status==AIRBASE.SpotStatus.FREE or spot.ReservedBy==UnitName)then
return spot
end
end
return nil
end
function FLIGHTCONTROL:CountParking(SpotStatus)
local n=0
for _,_spot in pairs(self.parking)do
local spot=_spot
if SpotStatus==nil or SpotStatus==spot.Status then
n=n+1
end
end
return n
end
function FLIGHTCONTROL:_CreatePlayerMenu(flight,mainmenu)
local group=flight.group
local groupname=flight.groupname
local gid=group:GetID()
local flightstatus=self:GetFlightStatus(flight)
local gotcontrol=self:IsControlling(flight)
local player=flight:GetPlayerElement()
local text=string.format("Creating ATC player menu for flight %s: in state=%s status=%s, gotcontrol=%s, player=%s",
tostring(flight.groupname),flight:GetState(),flightstatus,tostring(gotcontrol),player.status)
self:T(self.lid..text)
local rootmenu=MENU_GROUP:New(group,self.airbasename,mainmenu)
local helpmenu=MENU_GROUP:New(group,"Help",rootmenu)
MENU_GROUP_COMMAND:New(group,"Radio Check",helpmenu,self._PlayerRadioCheck,self,groupname)
MENU_GROUP_COMMAND:New(group,"Confirm Status",helpmenu,self._PlayerConfirmStatus,self,groupname)
if gotcontrol and flight:IsInbound()and flight.stack then
MENU_GROUP_COMMAND:New(group,"Vector Holding",helpmenu,self._PlayerVectorInbound,self,groupname)
end
local infomenu=MENU_GROUP:New(group,"Info",rootmenu)
MENU_GROUP_COMMAND:New(group,"Airbase",infomenu,self._PlayerInfoAirbase,self,groupname)
MENU_GROUP_COMMAND:New(group,"Traffic",infomenu,self._PlayerInfoTraffic,self,groupname)
MENU_GROUP_COMMAND:New(group,"ATIS",infomenu,self._PlayerInfoATIS,self,groupname)
if gotcontrol then
local status=self:GetFlightStatus(flight)
if flight:IsParking(player)or player.status==OPSGROUP.ElementStatus.ENGINEON then
if status==FLIGHTCONTROL.FlightStatus.READYTX then
MENU_GROUP_COMMAND:New(group,"Cancel Taxi",rootmenu,self._PlayerAbortTaxi,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Request Taxi",rootmenu,self._PlayerRequestTaxi,self,groupname)
end
elseif flight:IsTaxiing(player)then
if status==FLIGHTCONTROL.FlightStatus.READYTX or status==FLIGHTCONTROL.FlightStatus.TAXIOUT then
MENU_GROUP_COMMAND:New(group,"Request Takeoff",rootmenu,self._PlayerRequestTakeoff,self,groupname)
MENU_GROUP_COMMAND:New(group,"Abort Taxi",rootmenu,self._PlayerAbortTaxi,self,groupname)
elseif status==FLIGHTCONTROL.FlightStatus.READYTO then
MENU_GROUP_COMMAND:New(group,"Abort Takeoff",rootmenu,self._PlayerAbortTakeoff,self,groupname)
elseif status==FLIGHTCONTROL.FlightStatus.TAKEOFF then
MENU_GROUP_COMMAND:New(group,"Abort Takeoff",rootmenu,self._PlayerAbortTakeoff,self,groupname)
elseif status==FLIGHTCONTROL.FlightStatus.TAXIINB then
MENU_GROUP_COMMAND:New(group,"Request Taxi",rootmenu,self._PlayerRequestTaxi,self,groupname)
if player.parking then
MENU_GROUP_COMMAND:New(group,"Cancel Parking",rootmenu,self._PlayerCancelParking,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Reserve Parking",rootmenu,self._PlayerRequestParking,self,groupname)
end
MENU_GROUP_COMMAND:New(group,"Arrived at Parking",rootmenu,self._PlayerArrived,self,groupname)
end
elseif flight:IsInbound()then
if status==FLIGHTCONTROL.FlightStatus.LANDING then
MENU_GROUP_COMMAND:New(group,"Confirm Landing!",rootmenu,self._PlayerConfirmLanding,self,groupname)
MENU_GROUP_COMMAND:New(group,"Abort Landing",rootmenu,self._PlayerAbortLanding,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Holding!",rootmenu,self._PlayerHolding,self,groupname)
MENU_GROUP_COMMAND:New(group,"Direct Approach",rootmenu,self._PlayerRequestDirectLanding,self,groupname)
MENU_GROUP_COMMAND:New(group,"Abort Inbound",rootmenu,self._PlayerAbortInbound,self,groupname)
end
if player.parking then
MENU_GROUP_COMMAND:New(group,"Cancel Parking",rootmenu,self._PlayerCancelParking,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Reserve Parking",rootmenu,self._PlayerRequestParking,self,groupname)
end
elseif flight:IsHolding()then
MENU_GROUP_COMMAND:New(group,"Confirm Landing!",rootmenu,self._PlayerConfirmLanding,self,groupname)
MENU_GROUP_COMMAND:New(group,"Abort Holding",rootmenu,self._PlayerAbortHolding,self,groupname)
if player.parking then
MENU_GROUP_COMMAND:New(group,"Cancel Parking",rootmenu,self._PlayerCancelParking,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Reserve Parking",rootmenu,self._PlayerRequestParking,self,groupname)
end
elseif flight:IsLanding(player)then
MENU_GROUP_COMMAND:New(group,"Abort Landing",rootmenu,self._PlayerAbortLanding,self,groupname)
if player.parking then
MENU_GROUP_COMMAND:New(group,"Cancel Parking",rootmenu,self._PlayerCancelParking,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Reserve Parking",rootmenu,self._PlayerRequestParking,self,groupname)
end
elseif flight:IsLanded(player)then
MENU_GROUP_COMMAND:New(group,"Arrived at Parking",rootmenu,self._PlayerArrived,self,groupname)
if player.parking then
MENU_GROUP_COMMAND:New(group,"Cancel Parking",rootmenu,self._PlayerCancelParking,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Reserve Parking",rootmenu,self._PlayerRequestParking,self,groupname)
end
elseif flight:IsArrived(player)then
if status==FLIGHTCONTROL.FlightStatus.READYTX then
MENU_GROUP_COMMAND:New(group,"Abort Taxi",rootmenu,self._PlayerAbortTaxi,self,groupname)
else
MENU_GROUP_COMMAND:New(group,"Request Taxi",rootmenu,self._PlayerRequestTaxi,self,groupname)
end
elseif flight:IsAirborne(player)then
end
else
if flight:IsAirborne()then
MENU_GROUP_COMMAND:New(group,"Inbound",rootmenu,self._PlayerRequestInbound,self,groupname)
end
end
end
function FLIGHTCONTROL:_PlayerNotImplemented(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local text=string.format("Sorry, this feature is not implemented yet!")
self:TextMessageToFlight(text,flight)
end
end
function FLIGHTCONTROL:_PlayerRadioCheck(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local callsign=self:_GetCallsignName(flight)
local text=""
if type(self.frequency)=="table"then
local multifreq=""
for _,_entry in pairs(self.frequency)do
multifreq=string.format("%s%.2f, ",multifreq,_entry)
end
multifreq=string.gsub(multifreq,", $","")
text=string.format("%s, %s, radio check %s",self.alias,callsign,multifreq)
else
text=string.format("%s, %s, radio check %.3f",self.alias,callsign,self.frequency)
end
self:TransmissionPilot(text,flight)
local text=string.format("%s, %s, reading you 5",callsign,self.alias)
self:TransmissionTower(text,flight,10)
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerConfirmStatus(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, confirm my status",self.alias,callsign)
self:TransmissionPilot(text,flight)
local s1=flight:GetState()
local text=string.format("%s, %s, your current flight status is %s.",callsign,self.alias,s1)
if flight.flightcontrol then
local s2=flight.flightcontrol:GetFlightStatus(flight)
if flight.flightcontrol.airbasename==self.airbasename then
text=text..string.format(" You are controlled by us with status %s",s2)
else
text=text..string.format(" You are controlled by %s with status %s",flight.flightcontrol.airbasename,s2)
end
else
text=text..string.format(" You are not controlled by anyone.")
end
self:TransmissionTower(text,flight,10)
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerInfoAirbase(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local text=string.format("Airbase %s Info:",self.airbasename)
text=text..string.format("\nATC Status: %s",self:GetState())
if type(self.frequency)=="table"then
local multifreq=""
for i=1,#self.frequency do
multifreq=string.format("%s%.2f %s, ",multifreq,self.frequency[i],UTILS.GetModulationName(self.modulation[i]or 0))
end
text=string.gsub(text,", $","")
text=text..string.format("\nFrequencies: %s",multifreq)
else
text=text..string.format("\nFrequency: %.3f %s",self.frequency,UTILS.GetModulationName(self.modulation))
end
text=text..string.format("\nRunway Landing: %s",self:GetActiveRunwayText())
text=text..string.format("\nRunway Takeoff: %s",self:GetActiveRunwayText(true))
self:TextMessageToFlight(text,flight,10,true)
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerInfoATIS(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local text=string.format("Airbase %s ATIS:",self.airbasename)
local srstxt=string.format("Airbase %s ",self.airbasename)
if self.atis then
text=text..string.format("\nATIS %.3f MHz %s",self.atis.frequency,UTILS.GetModulationName(self.atis.modulation))
srstxt=srstxt..string.format("ATIS %.3f Megahertz %s",self.atis.frequency,UTILS.GetModulationName(self.atis.modulation))
if self.atis.towerfrequency then
local tower=""
for _,freq in pairs(self.atis.towerfrequency)do
tower=tower..string.format("%.3f, ",freq)
end
text=text..string.format("\nTower %.3f MHz",self.atis.towerfrequency[1])
end
if self.atis.ils then
end
if self.atis.tacan then
end
if self.atis.ndbinner then
end
if self.atis.ndbouter then
end
else
text=text.." Not defined"
end
local callsign=self:_GetCallsignName(flight)
local rtext=string.format("%s, %s, request ATIS frequency.",self.alias,callsign)
self:TransmissionPilot(rtext,flight)
if self.atis then
self:TransmissionTower(srstxt,flight,10)
else
self:TransmissionTower(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerInfoTraffic(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local Nflights=self:CountFlights()
local NQparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING)
local NQreadytx=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTX)
local NQtaxiout=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIOUT)
local NQreadyto=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTO)
local NQtakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF)
local NQinbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.INBOUND)
local NQholding=self:CountFlights(FLIGHTCONTROL.FlightStatus.HOLDING)
local NQlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING)
local NQtaxiinb=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB)
local NQarrived=self:CountFlights(FLIGHTCONTROL.FlightStatus.ARRIVED)
local text=string.format("Traffic %s airbase:",self.airbasename)
text=text..string.format("\n- Total Flights %d",Nflights)
if NQparking>0 then
text=text..string.format("\n- Parking %d",NQparking)
end
if NQreadytx>0 then
text=text..string.format("\n- Ready to taxi %d",NQreadytx)
end
if NQtaxiout>0 then
text=text..string.format("\n- Taxi to runway %d",NQtaxiout)
end
if NQreadyto>0 then
text=text..string.format("\n- Ready for takeoff %d",NQreadyto)
end
if NQtakeoff>0 then
text=text..string.format("\n- Taking off %d",NQtakeoff)
end
if NQinbound>0 then
text=text..string.format("\n- Inbound %d",NQinbound)
end
if NQholding>0 then
text=text..string.format("\n- Holding pattern %d",NQholding)
end
if NQlanding>0 then
text=text..string.format("\n- Landing %d",NQlanding)
end
if NQtaxiinb>0 then
text=text..string.format("\n- Taxi to parking %d",NQtaxiinb)
end
if NQarrived>0 then
text=text..string.format("\n- Arrived at parking %d",NQarrived)
end
self:TextMessageToFlight(text,flight,15,true)
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerRequestInbound(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if flight:IsAirborne()then
local callsign=self:_GetCallsignName(flight)
local player=flight:GetPlayerElement()
local text=string.format("%s, %s, inbound for landing",self.alias,callsign)
self:TransmissionPilot(text,flight)
local flightcoord=flight:GetCoordinate(nil,player.name)
local dist=flightcoord:Get2DDistance(self:GetCoordinate())
if dist<UTILS.NMToMeters(50)then
flight:RTB(self.airbase)
local stack=self:_GetHoldingStack(flight)
if stack then
stack.flightgroup=flight
flight.stack=stack
local heading=flightcoord:HeadingTo(stack.pos0)
local distance=flightcoord:Get2DDistance(stack.pos0)
local dist=UTILS.MetersToNM(distance)
local text=string.format("%s, %s, roger, fly heading %03d for %d nautical miles, hold at angels %d. Report entering the pattern.",
callsign,self.alias,heading,dist,stack.angels)
self:TransmissionTower(text,flight,10)
flight:SetFlightControl(self)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.INBOUND)
else
local text=string.format("Negative, could not get a holding stack for you! Try again later...")
self:TextMessageToFlight(text,flight,10)
self:E(self.lid..string.format("WARNING: Could not get holding stack for flight %s",flight:GetName()))
end
else
local text=string.format("Negative, you have to be withing 50 nautical miles of the airbase to request inbound!")
self:TextMessageToFlight(text,flight,10)
end
else
local text=string.format("Negative, you must be AIRBORNE to call INBOUND!")
self:TextMessageToFlight(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerVectorInbound(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if flight:IsInbound()and self:IsControlling(flight)and flight.stack then
local callsign=self:_GetCallsignName(flight)
local player=flight:GetPlayerElement()
local flightcoord=flight:GetCoordinate(nil,player.name)
local dist=flightcoord:Get2DDistance(self:GetCoordinate())
local callsign=self:_GetCallsignName(flight)
local heading=flightcoord:HeadingTo(flight.stack.pos0)
local distance=flightcoord:Get2DDistance(flight.stack.pos0)
local dist=UTILS.MetersToNM(distance)
local text=string.format("%s, fly heading %03d for %d nautical miles, hold at angels %d.",callsign,heading,dist,flight.stack.angels)
self:TextMessageToFlight(text,flight)
else
local text="Negative, you must be INBOUND, CONTROLLED by us and have an assigned STACK!"
self:TextMessageToFlight(text,flight)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerAbortInbound(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if flight:IsInbound()and self:IsControlling(flight)then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, abort inbound",self.alias,callsign)
self:TransmissionPilot(text,flight)
local text=string.format("%s, %s, roger, have a nice day!",callsign,self.alias)
self:TransmissionTower(text,flight,5)
if flight.stack then
flight.stack.flightgroup=nil
flight.stack=nil
else
self:E(self.lid.."ERROR: No stack!")
end
self:_RemoveFlight(flight)
flight:Cruise()
flight.currbase=nil
else
local text=string.format("Negative, you must be INBOUND and CONTROLLED by us!")
self:TextMessageToFlight(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerHolding(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if flight:IsInbound()then
if self:IsControlling(flight)then
local callsign=self:_GetCallsignName(flight)
local player=flight:GetPlayerElement()
local stack=flight.stack
if stack then
local text=string.format("%s, %s, arrived at holding pattern",self.alias,callsign)
self:TransmissionPilot(text,flight)
local Coordinate=flight:GetCoordinate(nil,player.name)
local dist=stack.pos0:Get2DDistance(Coordinate)
local dmax=UTILS.NMToMeters(500)
if dist<dmax then
local text=string.format("%s, roger, fly heading %d at angels %d and wait for landing clearance",callsign,stack.heading,stack.angels)
self:TransmissionTower(text,flight,10)
flight:Holding()
else
local text=string.format("Negative, you have to be within %d NM of the arrival zone! You still %d NM away.",UTILS.MetersToNM(dmax),UTILS.MetersToNM(dist))
self:TextMessageToFlight(text,flight,10,true)
end
else
local text=string.format("Negative, we have no holding stack for you!")
self:TextMessageToFlight(text,flight,10,true)
end
else
local text=string.format("Negative, you are not controlled by us!")
self:TextMessageToFlight(text,flight,10,true)
end
else
local text=string.format("Negative, you must be INBOUND to call HOLDING!")
self:TextMessageToFlight(text,flight,10,true)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerAbortHolding(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if flight:IsHolding()and self:IsControlling(flight)then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, abort holding",self.alias,callsign)
self:TransmissionPilot(text,flight)
local text=string.format("%s, %s, roger, have a nice day!",callsign,self.alias)
self:TransmissionTower(text,flight,10)
flight.Tholding=nil
flight:Cruise()
flight.currbase=nil
if flight.stack then
flight.stack.flightgroup=nil
flight.stack=nil
else
self:E(self.lid.."ERROR: No stack!")
end
self:_RemoveFlight(flight)
else
local text=string.format("Negative, you must be HOLDING and CONTROLLED by us!")
self:TextMessageToFlight(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerConfirmLanding(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if(flight:IsHolding()or flight:IsInbound())and self:IsControlling(flight)then
local callsign=self:_GetCallsignName(flight)
if self:GetFlightStatus(flight)==FLIGHTCONTROL.FlightStatus.LANDING then
local runway=self:GetActiveRunwayText()
local text=string.format("Runway %s, cleared to land, %s",runway,callsign)
self:TransmissionPilot(text,flight)
flight:Landing()
flight:_UpdateMenu(0.5)
else
local text=string.format("%s, %s, leaving pattern for landing.",self.alias,callsign)
self:TransmissionPilot(text,flight)
local text=string.format("%s, negative! Hold position until you get clearance.",callsign)
self:TransmissionTower(text,flight,10)
end
else
local text=string.format("Negative, you must be HOLDING or INBOUND and CONTROLLED by us!")
self:TextMessageToFlight(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerAbortLanding(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local flightstatus=self:GetFlightStatus(flight)
if(flight:IsLanding()or flightstatus==FLIGHTCONTROL.FlightStatus.LANDING)and self:IsControlling(flight)then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, abort landing",self.alias,callsign)
self:TransmissionPilot(text,flight)
local text=string.format("%s, %s, roger, have a nice day!",callsign,self.alias)
self:TransmissionTower(text,flight,10)
if flight.stack then
flight.stack.flightgroup=nil
flight.stack=nil
end
flight.Tholding=nil
flight:Cruise()
flight.currbase=nil
self:_RemoveFlight(flight)
else
local text=string.format("Negative, you must be LANDING and CONTROLLED by us!")
self:TextMessageToFlight(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerRequestDirectLanding(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if flight:IsInbound()and self:IsControlling(flight)then
local callsign=self:_GetCallsignName(flight)
local nTakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF)
local text=string.format("%s, request direct approach.",callsign)
self:TransmissionPilot(text,flight)
if nTakeoff>self.NlandingTakeoff then
local text=string.format("%s, negative! We have currently traffic taking off!",callsign)
self:TransmissionTower(text,flight,10)
else
local runway=self:GetActiveRunwayText()
local text=string.format("%s, affirmative, runway %s. Confirm approach!",callsign,runway)
self:TransmissionTower(text,flight,10)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.LANDING)
end
else
local text=string.format("Negative, you must be INBOUND and CONTROLLED by us!")
self:TextMessageToFlight(text,flight,10)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerRequestTaxi(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, request taxi to runway.",self.alias,callsign)
self:TransmissionPilot(text,flight)
if flight:IsParking()then
local text=string.format("%s, %s, hold position until further notice.",callsign,self.alias)
self:TransmissionTower(text,flight,10)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.READYTX)
elseif flight:IsTaxiing()then
local runway=self:GetActiveRunwayText(true)
local text=string.format("%s, %s, taxi to runway %s, hold short.",callsign,self.alias,runway)
self:TransmissionTower(text,flight,10)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIOUT)
local playerElement=flight:GetPlayerElement()
if playerElement and playerElement.parking then
self:SetParkingFree(playerElement.parking)
end
else
self:TextMessageToFlight(string.format("Negative, you must be PARKING to request TAXI!"),flight)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerAbortTaxi(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, cancel my taxi request.",self.alias,callsign)
self:TransmissionPilot(text,flight)
if flight:IsParking()then
local text=string.format("%s, %s, roger, remain on your parking position.",callsign,self.alias)
self:TransmissionTower(text,flight,10)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING)
local playerElement=flight:GetPlayerElement()
if playerElement then
self:SpawnParkingGuard(playerElement.unit)
end
elseif flight:IsTaxiing()then
local text=string.format("%s, %s, roger, return to your parking position.",callsign,self.alias)
self:TransmissionTower(text,flight,10)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIINB)
else
self:TextMessageToFlight(string.format("Negative, you must be PARKING or TAXIING to abort TAXI!"),flight)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
if flight:IsTaxiing()then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, ready for departure. Request takeoff.",self.alias,callsign)
self:TransmissionPilot(text,flight)
local Nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING)
local Ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF)
local text=string.format("%s, %s, ",callsign,self.alias)
if Nlanding==0 then
text=text.."no current traffic. You are cleared for takeoff."
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAKEOFF)
elseif Nlanding>0 then
if Nlanding==1 then
text=text..string.format("negative, we got %d flight inbound before it's your turn. Hold position until futher notice.",Nlanding)
else
text=text..string.format("negative, we got %d flights inbound. Hold positon until futher notice.",Nlanding)
end
end
self:TransmissionTower(text,flight,10)
else
self:TextMessageToFlight(string.format("Negative, you must request TAXI before you can request TAKEOFF!"),flight)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local status=self:GetFlightStatus(flight)
if status==FLIGHTCONTROL.FlightStatus.TAKEOFF or status==FLIGHTCONTROL.FlightStatus.READYTO then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, %s, abort takeoff.",self.alias,callsign)
self:TransmissionPilot(text,flight)
if flight:IsParking()then
text=string.format("%s, %s, affirm, remain on your parking position.",callsign,self.alias)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING)
local playerElement=flight:GetPlayerElement()
if playerElement then
self:SpawnParkingGuard(playerElement.unit)
end
elseif flight:IsTaxiing()then
text=string.format("%s, %s, roger, report whether you want to taxi back or takeoff later.",callsign,self.alias)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.TAXIOUT)
else
env.info(self.lid.."ERROR")
end
self:TransmissionTower(text,flight,10)
else
self:TextMessageToFlight("Negative, You are NOT in the takeoff queue",flight)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerRequestParking(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local callsign=self:_GetCallsignName(flight)
local player=flight:GetPlayerElement()
local TerminalType=AIRBASE.TerminalType.FighterAircraft
if flight.isHelo then
TerminalType=AIRBASE.TerminalType.HelicopterUsable
end
local coord=flight:GetCoordinate(nil,player.name)
local spot=self:_GetPlayerSpot(player.name)
if not spot then
spot=self:GetClosestParkingSpot(coord,TerminalType,AIRBASE.SpotStatus.FREE)
end
if spot then
local text=string.format("%s, your assigned parking position is terminal ID %d.",callsign,spot.TerminalID)
self:TransmissionTower(text,flight)
if player.parking then
self:SetParkingFree(player.parking)
end
player.parking=spot
self:SetParkingReserved(spot,player.name)
flight:_UpdateMenu(0.2)
else
local text=string.format("%s, no free parking spot available. Try again later.",callsign)
self:TransmissionTower(text,flight)
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerCancelParking(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local callsign=self:_GetCallsignName(flight)
local player=flight:GetPlayerElement()
if player.parking then
self:SetParkingFree(player.parking)
player.parking=nil
self:TextMessageToFlight(string.format("%s, your parking spot reservation at terminal ID %d was cancelled.",callsign,player.parking.TerminalID),flight)
else
self:TextMessageToFlight("You did not have a valid parking spot reservation.",flight)
end
flight:_UpdateMenu(0.2)
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_PlayerArrived(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local player=flight:GetPlayerElement()
local coord=flight:GetCoordinate(nil,player.name)
local spot=self:_GetPlayerSpot(player.name)
if player.parking then
spot=self:GetParkingSpotByID(player.parking.TerminalID)
else
if not spot then
spot=self:GetClosestParkingSpot(coord)
end
end
if spot then
local callsign=self:_GetCallsignName(flight)
local dist=coord:Get2DDistance(spot.Coordinate)
if dist<12 then
local text=string.format("%s, %s, arrived at parking position. Terminal ID %d.",self.alias,callsign,spot.TerminalID)
self:TransmissionPilot(text,flight)
local text=""
if spot.ReservedBy and spot.ReservedBy~=player.name then
text=string.format("%s, this spot is already reserved for %s. Find yourself a different parking position.",callsign,self.alias,spot.ReservedBy)
else
text=string.format("%s, %s, roger. Enjoy a cool bevarage in the officers' club.",callsign,self.alias)
flight:ElementParking(player,spot)
self:SetFlightStatus(flight,FLIGHTCONTROL.FlightStatus.PARKING)
if player then
self:SpawnParkingGuard(player.unit)
end
end
self:TransmissionTower(text,flight,10)
else
local text=string.format("%s, %s, arrived at parking position.",self.alias,callsign)
self:TransmissionPilot(text,flight)
local text=""
if spot.ReservedBy then
if spot.ReservedBy==player.name then
text=string.format("%s, %s, you are still %d meters away from your reserved parking position at terminal ID %d. Continue taxiing!",callsign,self.alias,dist,spot.TerminalID)
else
text=string.format("%s, %s, the closest parking spot is already reserved. Continue taxiing to a free spot!",callsign,self.alias)
end
else
text=string.format("%s, %s, you are still %d meters away from the closest parking position. Continue taxiing to a proper spot!",callsign,self.alias,dist)
end
self:TransmissionTower(text,flight,10)
end
else
end
else
self:E(self.lid..string.format("Cannot find flight group %s.",tostring(groupname)))
end
end
function FLIGHTCONTROL:_CreateFlightGroup(group)
if self:_InQueue(self.flights,group)then
self:E(self.lid..string.format("WARNING: Flight group %s does already exist!",group:GetName()))
return
end
self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.",group:GetName(),group:GetTypeName()))
local flight=_DATABASE:GetOpsGroup(group:GetName())
if not flight then
flight=FLIGHTGROUP:New(group:GetName())
end
if flight.homebase and flight.homebase:GetName()==self.airbasename then
flight:SetFlightControl(self)
end
return flight
end
function FLIGHTCONTROL:_RemoveFlight(Flight)
for i,_flight in pairs(self.flights)do
local flight=_flight
if flight.groupname==Flight.groupname then
self:T(self.lid..string.format("Removing flight group %s",flight.groupname))
table.remove(self.flights,i)
Flight.flightcontrol=nil
self:SetFlightStatus(Flight,FLIGHTCONTROL.FlightStatus.UNKNOWN)
return true
end
end
self:E(self.lid..string.format("WARNING: Could NOT remove flight group %s",Flight.groupname))
end
function FLIGHTCONTROL:_GetFlightFromGroup(group)
if group then
local name=group:GetName()
for i,_flight in pairs(self.flights)do
local flight=_flight
if flight.groupname==name then
return flight,i
end
end
self:T2(self.lid..string.format("WARNING: Flight group %s could not be found in queue.",name))
end
self:T2(self.lid..string.format("WARNING: Flight group could not be found in queue. Group is nil!"))
return nil,nil
end
function FLIGHTCONTROL:_GetFlightElement(unitname)
local unit=UNIT:FindByName(unitname)
if unit then
local flight=self:_GetFlightFromGroup(unit:GetGroup())
if flight then
for i,_element in pairs(flight.elements)do
local element=_element
if element.unit:GetName()==unitname then
return element,i,flight
end
end
self:T2(self.lid..string.format("WARNING: Flight element %s could not be found in flight group.",unitname,flight.groupname))
end
end
return nil,nil,nil
end
function FLIGHTCONTROL:_CheckFlights()
for i=#self.flights,1,-1 do
local flight=self.flights[i]
if flight:IsDead()then
self:T(self.lid..string.format("Removing DEAD flight %s",tostring(flight.groupname)))
self:_RemoveFlight(flight)
end
end
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
if not flight.isAI then
local playerElement=flight:GetPlayerElement()
local flightstatus=self:GetFlightStatus(flight)
if playerElement then
if(flightstatus==FLIGHTCONTROL.FlightStatus.TAXIINB or flightstatus==FLIGHTCONTROL.FlightStatus.TAXIOUT)and self.speedLimitTaxi then
local speed=playerElement.unit:GetVelocityMPS()
local coord=playerElement.unit:GetCoord()
local onRunway=self:IsCoordinateRunway(coord)
self:T(self.lid..string.format("Player %s speed %.1f knots (max=%.1f) onRunway=%s",playerElement.playerName,UTILS.MpsToKnots(speed),UTILS.MpsToKnots(self.speedLimitTaxi),tostring(onRunway)))
if speed and speed>self.speedLimitTaxi and not onRunway then
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, slow down, you are taxiing too fast!",callsign)
self:TransmissionTower(text,flight)
local PlayerData=flight:_GetPlayerData()
self:PlayerSpeeding(PlayerData)
end
end
end
end
end
end
end
function FLIGHTCONTROL:_CheckParking()
for TerminalID,_spot in pairs(self.parking)do
local spot=_spot
if spot.Reserved then
if spot.MarkerID then
spot.Coordinate:RemoveMark(spot.MarkerID)
end
spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking reserved for %s",tostring(spot.Reserved)),self:GetCoalition())
end
for i=1,#self.flights do
local flight=self.flights[i]
for _,_element in pairs(flight.elements)do
local element=_element
if element.parking and element.parking.TerminalID==TerminalID then
if spot.MarkerID then
spot.Coordinate:RemoveMark(spot.MarkerID)
end
spot.MarkerID=spot.Coordinate:MarkToCoalition(string.format("Parking spot occupied by %s",tostring(element.name)),self:GetCoalition())
end
end
end
end
end
function FLIGHTCONTROL:_LandAI(flight,parking)
self:T(self.lid..string.format("Landing AI flight %s.",flight.groupname))
local respawn=false
if respawn then
local Template=flight.group:GetTemplate()
Template.route.points=wp
for i,unit in pairs(Template.units)do
local spot=parking[i]
local element=flight:GetElementByName(unit.name)
if element then
unit.parking_landing=spot.TerminalID
local text=string.format("Reserving parking spot %d for unit %s",spot.TerminalID,tostring(unit.name))
self:T(self.lid..text)
self:SetParkingReserved(spot,element.name)
else
env.info("FF error could not get element to assign parking!")
end
end
self:TextMessageToFlight(string.format("Respawning group %s",flight.groupname),flight)
flight:Respawn(Template)
else
flight:ClearToLand()
end
end
function FLIGHTCONTROL:_GetHoldingStack(flight)
self:T(self.lid..string.format("Getting holding point for flight %s",flight:GetName()))
for i,_hp in pairs(self.holdingpatterns)do
local holdingpattern=_hp
self:T(self.lid..string.format("Checking holding point %s",holdingpattern.name))
for j,_stack in pairs(holdingpattern.stacks)do
local stack=_stack
local name=stack.flightgroup and stack.flightgroup:GetName()or"empty"
self:T(self.lid..string.format("Stack %d: %s",j,name))
if not stack.flightgroup then
return stack
end
end
end
return nil
end
function FLIGHTCONTROL:_CountFlightsInPattern(Pattern)
local N=0
for _,_stack in pairs(Pattern.stacks)do
local stack=_stack
if stack.flightgroup then
N=N+1
end
end
return N
end
function FLIGHTCONTROL:_FlightOnFinal(flight)
local callsign=self:_GetCallsignName(flight)
local text=string.format("%s, final",callsign)
self:TransmissionPilot(text,flight)
return self
end
function FLIGHTCONTROL:TransmissionTower(Text,Flight,Delay)
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
local playerData=Flight:_GetPlayerData()
if playerData.subtitles and(not self.nosubs)then
subgroups=subgroups or{}
table.insert(subgroups,Flight.group)
end
end
local transmission=self.msrsqueue:NewTransmission(text,nil,self.msrsTower,nil,1,subgroups,Text)
self.Tlastmessage=timer.getAbsTime()+(Delay or 0)
self:T(self.lid..string.format("Radio Tower: %s",Text))
end
function FLIGHTCONTROL:TransmissionPilot(Text,Flight,Delay)
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)
local msrs=self.msrsPilot
if Flight.useSRS and Flight.msrs then
msrs=Flight.msrs
end
local subgroups=nil
if Flight and not Flight.isAI then
local playerData=Flight:_GetPlayerData()
if playerData.subtitles and(not self.nosubs)then
subgroups=subgroups or{}
table.insert(subgroups,Flight.group)
end
end
local coordinate=Flight:GetCoordinate(true)
msrs:SetCoordinate()
self.msrsqueue:NewTransmission(text,nil,msrs,nil,1,subgroups,Text,nil,self.frequency,self.modulation)
end
self.Tlastmessage=timer.getAbsTime()+(Delay or 0)
self:T(self.lid..string.format("Radio Pilot: %s",Text))
end
function FLIGHTCONTROL:TextMessageToFlight(Text,Flight,Duration,Clear,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,FLIGHTCONTROL.TextMessageToFlight,self,Text,Flight,Duration,Clear,0)
else
if Flight and Flight.group and Flight.group:IsAlive()then
local gid=Flight.group:GetID()
trigger.action.outTextForGroup(gid,self:_CleanText(Text),Duration or 5,Clear)
end
end
end
function FLIGHTCONTROL:_CleanText(Text)
local text=Text:gsub("\n$",""):gsub("\n$","")
return text
end
function FLIGHTCONTROL:_SpawnParkingGuard(unit)
local coordinate=unit:GetCoordinate()
local spot=self:GetClosestParkingSpot(coordinate)
if not spot.ParkingGuard then
local heading=unit:GetHeading()
local size,x,y,z=unit:GetObjectSize()
local xdiff=3
if AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.Shelter)then
xdiff=27-(x*0.5)
end
if(AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.OpenMed)or AIRBASE._CheckTerminalType(spot.TerminalType,AIRBASE.TerminalType.Shelter))and self.airbasename==AIRBASE.Sinai.Ramon_Airbase then
xdiff=12
end
self:T2(self.lid..string.format("Parking guard for %s: heading=%d, length x=%.1f m, xdiff=%.1f m",unit:GetName(),heading,x,xdiff))
local Coordinate=coordinate:Translate(x*0.5+xdiff,heading)
local lookat=heading-180
self.parkingGuard:InitHeading(lookat)
if self.parkingGuard:IsInstanceOf("SPAWN")then
end
spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate)
else
self:E(self.lid.."ERROR: Parking Guard already exists!")
end
end
function FLIGHTCONTROL:SpawnParkingGuard(unit)
if unit and self.parkingGuard then
self:ScheduleOnce(1,FLIGHTCONTROL._SpawnParkingGuard,self,unit)
end
end
function FLIGHTCONTROL:RemoveParkingGuard(spot,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,FLIGHTCONTROL.RemoveParkingGuard,self,spot)
else
if spot.ParkingGuard then
spot.ParkingGuard:Destroy()
spot.ParkingGuard=nil
end
end
end
function FLIGHTCONTROL:_IsFlightOnRunway(flight)
for _,_runway in pairs(self.airbase.runways)do
local runway=_runway
local inzone=flight:IsInZone(runway.zone)
if inzone then
return runway
end
end
return nil
end
function FLIGHTCONTROL:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
if not ShortCallsign or ShortCallsign==false then
self.ShortCallsign=false
else
self.ShortCallsign=true
end
self.Keepnumber=Keepnumber or false
self.CallsignTranslations=CallsignTranslations
return self
end
function FLIGHTCONTROL:_GetCallsignName(flight)
local callsign=flight:GetCallsignName(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
return callsign
end
function FLIGHTCONTROL:_GetTextForSpeech(text)
local function space(text)
local res=""
for i=1,#text do
local char=text:sub(i,i)
res=res..char.." "
end
return res
end
local t=text:gsub("(%d+)",space)
return t
end
function FLIGHTCONTROL:_GetPlayerUnitAndName(unitName)
if unitName then
local DCSunit=Unit.getByName(unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
if DCSunit and unit and playername then
self:T(self.lid..string.format("Found DCS unit %s with player %s",tostring(unitName),tostring(playername)))
return unit,playername
end
end
end
return nil,nil
end
function FLIGHTCONTROL:_CheckMarkHoldingPatterns()
for _,pattern in pairs(self.holdingpatterns)do
local Pattern=pattern
if self.markPatterns then
self:_MarkHoldingPattern(Pattern)
else
self:_UnMarkHoldingPattern(Pattern)
end
end
end
function FLIGHTCONTROL:_MarkHoldingPattern(Pattern)
if not Pattern.markArrow then
Pattern.markArrow=Pattern.pos0:ArrowToAll(Pattern.pos1,nil,{1,0,0},1,{1,1,0},0.5,2,true)
end
if not Pattern.markArrival then
Pattern.markArrival=Pattern.arrivalzone:DrawZone()
end
end
function FLIGHTCONTROL:_UnMarkHoldingPattern(Pattern)
if Pattern.markArrow then
UTILS.RemoveMark(Pattern.markArrow)
Pattern.markArrow=nil
end
if Pattern.markArrival then
UTILS.RemoveMark(Pattern.markArrival)
Pattern.markArrival=nil
end
end
function FLIGHTCONTROL:_AddHoldingPatternBackup()
local runway=self:GetActiveRunway()
local heading=runway.heading
local vec2=self.airbase:GetVec2()
local Vec2=UTILS.Vec2Translate(vec2,UTILS.NMToMeters(5),heading+90)
local ArrivalZone=ZONE_RADIUS:New("Arrival Zone",Vec2,5000)
self.holdingBackup=self:AddHoldingPattern(ArrivalZone,heading,15,5,25,999)
return self
end
FLIGHTGROUP={
ClassName="FLIGHTGROUP",
homebase=nil,
destbase=nil,
homezone=nil,
destzone=nil,
actype=nil,
speedMax=nil,
rangemax=nil,
ceiling=nil,
fuellow=false,
fuellowthresh=nil,
fuellowrtb=nil,
fuelcritical=nil,
fuelcriticalthresh=nil,
fuelcriticalrtb=false,
outofAAMrtb=false,
outofAGMrtb=false,
flightcontrol=nil,
flaghold=nil,
Tholding=nil,
Tparking=nil,
Twaiting=nil,
menu=nil,
isHelo=nil,
RTBRecallCount=0,
playerSettings={},
playerWarnings={},
prohibitAB=false,
jettisonEmptyTanks=true,
jettisonWeapons=true,
}
FLIGHTGROUP.Attribute={
TRANSPORTPLANE="TransportPlane",
AWACS="AWACS",
FIGHTER="Fighter",
BOMBER="Bomber",
TANKER="Tanker",
TRANSPORTHELO="TransportHelo",
ATTACKHELO="AttackHelo",
UAV="UAV",
OTHER="Other",
}
FLIGHTGROUP.RadioMessage={
AIRBORNE={normal="Airborn",enhanced="Airborn"},
TAXIING={normal="Taxiing",enhanced="Taxiing"},
}
FLIGHTGROUP.PlayerSkill={
STUDENT="Student",
AVIATOR="Aviator",
GRADUATE="Graduate",
INSTRUCTOR="Instructor",
}
FLIGHTGROUP.Players={}
FLIGHTGROUP.version="1.0.3"
function FLIGHTGROUP:New(group)
local og=_DATABASE:GetOpsGroup(group)
if og then
og:I(og.lid..string.format("WARNING: OPS group already exists in data base!"))
return og
end
local self=BASE:Inherit(self,OPSGROUP:New(group))
self.lid=string.format("FLIGHTGROUP %s | ",self.groupname or"N/A")
self:SetDefaultROE()
self:SetDefaultROT()
self:SetDefaultEPLRS(self.isEPLRS)
self:SetDetection()
self:SetFuelLowThreshold()
self:SetFuelLowRTB()
self:SetFuelCriticalThreshold()
self:SetFuelCriticalRTB()
self.flaghold=USERFLAG:New(string.format("%s_FlagHold",self.groupname))
self.flaghold:Set(0)
self.holdtime=2*60
self:AddTransition("*","LandAtAirbase","Inbound")
self:AddTransition("*","RTB","Inbound")
self:AddTransition("*","RTZ","Inbound")
self:AddTransition("Inbound","Holding","Holding")
self:AddTransition("*","Refuel","Going4Fuel")
self:AddTransition("Going4Fuel","Refueled","Cruising")
self:AddTransition("*","LandAt","LandingAt")
self:AddTransition("LandingAt","LandedAt","LandedAt")
self:AddTransition("*","FuelLow","*")
self:AddTransition("*","FuelCritical","*")
self:AddTransition("Cruising","EngageTarget","Engaging")
self:AddTransition("Engaging","Disengage","Cruising")
self:AddTransition("*","ElementParking","*")
self:AddTransition("*","ElementEngineOn","*")
self:AddTransition("*","ElementTaxiing","*")
self:AddTransition("*","ElementTakeoff","*")
self:AddTransition("*","ElementAirborne","*")
self:AddTransition("*","ElementLanded","*")
self:AddTransition("*","ElementArrived","*")
self:AddTransition("*","ElementOutOfAmmo","*")
self:AddTransition("*","Parking","Parking")
self:AddTransition("*","Taxiing","Taxiing")
self:AddTransition("*","Takeoff","Airborne")
self:AddTransition("*","Airborne","Airborne")
self:AddTransition("*","Cruise","Cruising")
self:AddTransition("*","Landing","Landing")
self:AddTransition("*","Landed","Landed")
self:AddTransition("*","Arrived","Arrived")
self:HandleEvent(EVENTS.Birth,self.OnEventBirth)
self:HandleEvent(EVENTS.EngineStartup,self.OnEventEngineStartup)
self:HandleEvent(EVENTS.Takeoff,self.OnEventTakeOff)
self:HandleEvent(EVENTS.Land,self.OnEventLanding)
self:HandleEvent(EVENTS.EngineShutdown,self.OnEventEngineShutdown)
self:HandleEvent(EVENTS.PilotDead,self.OnEventPilotDead)
self:HandleEvent(EVENTS.Ejection,self.OnEventEjection)
self:HandleEvent(EVENTS.Crash,self.OnEventCrash)
self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit)
self:HandleEvent(EVENTS.UnitLost,self.OnEventUnitLost)
self:HandleEvent(EVENTS.Kill,self.OnEventKill)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventPlayerLeaveUnit)
self:_InitGroup()
self:_InitWaypoints()
self.timerStatus=TIMER:New(self.Status,self):Start(1,30)
self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5)
self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(3,10)
_DATABASE:AddOpsGroup(self)
return self
end
function FLIGHTGROUP:AddTaskEnrouteEngageTargetsInZone(ZoneRadius,TargetTypes,Priority)
local Task=self.group:EnRouteTaskEngageTargetsInZone(ZoneRadius:GetVec2(),ZoneRadius:GetRadius(),TargetTypes,Priority)
self:AddTaskEnroute(Task)
end
function FLIGHTGROUP:GetAirwing()
return self.legion
end
function FLIGHTGROUP:GetAirwingName()
local name=self.legion and self.legion.alias or"None"
return name
end
function FLIGHTGROUP:GetSquadron()
return self.cohort
end
function FLIGHTGROUP:GetSquadronName()
local name=self.cohort and self.cohort:GetName()or"None"
return name
end
function FLIGHTGROUP:SetVTOL()
self.isVTOL=true
return self
end
function FLIGHTGROUP:SetProhibitAfterburner()
self.prohibitAB=true
if self:GetGroup():IsAlive()then
self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,true)
end
return self
end
function FLIGHTGROUP:SetAllowAfterburner()
self.prohibitAB=false
if self:GetGroup():IsAlive()then
self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,false)
end
return self
end
function FLIGHTGROUP:SetJettisonEmptyTanks(Switch)
self.jettisonEmptyTanks=Switch
if self:GetGroup():IsAlive()then
self:GetGroup():SetOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY,Switch)
end
return self
end
function FLIGHTGROUP:SetJettisonWeapons(Switch)
self.jettisonWeapons=not Switch
if self:GetGroup():IsAlive()then
self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,not Switch)
end
return self
end
function FLIGHTGROUP:SetReadyForTakeoff(ReadyTO,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,FLIGHTGROUP.SetReadyForTakeoff,self,ReadyTO,0)
else
self:T(self.lid.."Set Ready for Takeoff switch for flightcontrol")
self.isReadyTO=ReadyTO
end
return self
end
function FLIGHTGROUP:SetFlightControl(flightcontrol)
if self.flightcontrol then
if self.flightcontrol:IsControlling(self)then
return
else
self.flightcontrol:_RemoveFlight(self)
end
end
self:T(self.lid..string.format("Setting FLIGHTCONTROL to airbase %s",flightcontrol.airbasename))
self.flightcontrol=flightcontrol
if not flightcontrol:IsFlight(self)then
table.insert(flightcontrol.flights,self)
end
return self
end
function FLIGHTGROUP:GetFlightControl()
return self.flightcontrol
end
function FLIGHTGROUP:SetHomebase(HomeAirbase)
if type(HomeAirbase)=="string"then
HomeAirbase=AIRBASE:FindByName(HomeAirbase)
end
self.homebase=HomeAirbase
return self
end
function FLIGHTGROUP:SetDestinationbase(DestinationAirbase)
if type(DestinationAirbase)=="string"then
DestinationAirbase=AIRBASE:FindByName(DestinationAirbase)
end
self.destbase=DestinationAirbase
return self
end
function FLIGHTGROUP:SetAirboss(airboss)
self.airboss=airboss
return self
end
function FLIGHTGROUP:SetFuelLowThreshold(threshold)
self.fuellowthresh=threshold or 25
return self
end
function FLIGHTGROUP:SetFuelLowRTB(switch)
if switch==false then
self.fuellowrtb=false
else
self.fuellowrtb=true
end
return self
end
function FLIGHTGROUP:SetOutOfAAMRTB(switch)
if switch==false then
self.outofAAMrtb=false
else
self.outofAAMrtb=true
end
return self
end
function FLIGHTGROUP:SetOutOfAGMRTB(switch)
if switch==false then
self.outofAGMrtb=false
else
self.outofAGMrtb=true
end
return self
end
function FLIGHTGROUP:SetFuelLowRefuel(switch)
if switch==false then
self.fuellowrefuel=false
else
self.fuellowrefuel=true
end
return self
end
function FLIGHTGROUP:SetFuelCriticalThreshold(threshold)
self.fuelcriticalthresh=threshold or 10
return self
end
function FLIGHTGROUP:SetFuelCriticalRTB(switch)
if switch==false then
self.fuelcriticalrtb=false
else
self.fuelcriticalrtb=true
end
return self
end
function FLIGHTGROUP:SetDespawnAfterLanding()
self.despawnAfterLanding=true
return self
end
function FLIGHTGROUP:SetDespawnAfterHolding()
self.despawnAfterHolding=true
return self
end
function FLIGHTGROUP:IsParking(Element)
local is=self:Is("Parking")
if Element then
is=Element.status==OPSGROUP.ElementStatus.PARKING
end
return is
end
function FLIGHTGROUP:IsTaxiing(Element)
local is=self:Is("Taxiing")
if Element then
is=Element.status==OPSGROUP.ElementStatus.TAXIING
end
return is
end
function FLIGHTGROUP:IsAirborne(Element)
local is=self:Is("Airborne")or self:Is("Cruising")
if Element then
is=Element.status==OPSGROUP.ElementStatus.AIRBORNE
end
return is
end
function FLIGHTGROUP:IsCruising()
local is=self:Is("Cruising")
return is
end
function FLIGHTGROUP:IsLanding(Element)
local is=self:Is("Landing")
if Element then
is=Element.status==OPSGROUP.ElementStatus.LANDING
end
return is
end
function FLIGHTGROUP:IsLanded(Element)
local is=self:Is("Landed")
if Element then
is=Element.status==OPSGROUP.ElementStatus.LANDED
end
return is
end
function FLIGHTGROUP:IsArrived(Element)
local is=self:Is("Arrived")
if Element then
is=Element.status==OPSGROUP.ElementStatus.ARRIVED
end
return is
end
function FLIGHTGROUP:IsInbound()
local is=self:Is("Inbound")
return is
end
function FLIGHTGROUP:IsHolding()
local is=self:Is("Holding")
return is
end
function FLIGHTGROUP:IsGoing4Fuel()
local is=self:Is("Going4Fuel")
return is
end
function FLIGHTGROUP:IsLandingAt()
local is=self:Is("LandingAt")
return is
end
function FLIGHTGROUP:IsLandedAt()
local is=self:Is("LandedAt")
return is
end
function FLIGHTGROUP:IsFuelLow()
return self.fuellow
end
function FLIGHTGROUP:IsFuelCritical()
return self.fuelcritical
end
function FLIGHTGROUP:IsFuelGood()
local isgood=not(self.fuellow or self.fuelcritical)
return isgood
end
function FLIGHTGROUP:CanAirToGround(ExcludeGuns)
local ammo=self:GetAmmoTot()
if ExcludeGuns then
return ammo.MissilesAG+ammo.Rockets+ammo.Bombs>0
else
return ammo.MissilesAG+ammo.Rockets+ammo.Bombs+ammo.Guns>0
end
end
function FLIGHTGROUP:CanAirToAir(ExcludeGuns)
local ammo=self:GetAmmoTot()
if ExcludeGuns then
return ammo.MissilesAA>0
else
return ammo.MissilesAA+ammo.Guns>0
end
end
function FLIGHTGROUP:StartUncontrolled(delay)
if delay and delay>0 then
self:T2(self.lid..string.format("Starting uncontrolled group in %d seconds",delay))
self:ScheduleOnce(delay,FLIGHTGROUP.StartUncontrolled,self)
else
local alive=self:IsAlive()
if alive~=nil then
local _delay=0
if alive==false then
self:Activate()
_delay=1
end
self:T(self.lid.."Starting uncontrolled group")
self.group:StartUncontrolled(_delay)
self.isUncontrolled=false
else
self:T(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!")
end
end
return self
end
function FLIGHTGROUP:ClearToLand(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,FLIGHTGROUP.ClearToLand,self)
else
if self:IsHolding()then
self:T(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)"))
self.flaghold:Set(1)
self.Tholding=nil
if self.stack then
self.stack.flightgroup=nil
self.stack=nil
end
end
end
return self
end
function FLIGHTGROUP:GetFuelMin()
local fuelmin=math.huge
for i,_element in pairs(self.elements)do
local element=_element
local unit=element.unit
local life=unit:GetLife()
if unit and unit:IsAlive()and life>1 then
local fuel=unit:GetFuel()
if fuel<fuelmin then
fuelmin=fuel
end
end
end
return fuelmin*100
end
function FLIGHTGROUP:GetKills()
return self.Nkills
end
function FLIGHTGROUP:Status()
local fsmstate=self:GetState()
local alive=self:IsAlive()
if alive then
self:_UpdatePosition()
self:_CheckDetectedUnits()
self:_CheckAmmoStatus()
self:_CheckDamage()
self:_CheckStuck()
local mission=self:GetMissionCurrent()
if self:IsWaiting()then
if self.Twaiting and self.dTwait then
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()
local hdg=mission:GetTargetHeading()
local hdgchange=false
if mission.orbitLeg then
if UTILS.HdgDiff(hdg,mission.targetHeading)>0 then
hdgchange=true
end
end
local dist=UTILS.VecDist2D(vec2,mission.orbitVec2)
local distchange=dist>mission.orbitDeltaR
self:T3(self.lid..string.format("Checking orbit mission dist=%d meters",dist))
if distchange or hdgchange then
self:T3(self.lid..string.format("Updating orbit!"))
local DCSTask=mission:GetDCSMissionTask()
local Task=mission:GetGroupWaypointTask(self)
self.controller:resetTask()
self:_SandwitchDCSTask(DCSTask,Task,false,1)
end
elseif mission.type==AUFTRAG.Type.CAPTUREZONE then
local Task=mission:GetGroupWaypointTask(self)
if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then
self:_UpdateTask(Task,mission)
end
end
end
if self:IsParking()then
for _,_element in pairs(self.elements)do
local element=_element
if element.parking then
local dist=self:_GetDistToParking(element.parking,element.unit:GetCoord())
self:T(self.lid..string.format("Distance to parking spot %d = %.1f meters",element.parking.TerminalID,dist))
if dist>12 and element.engineOn then
self:ElementTaxiing(element)
end
else
end
end
end
else
self:_CheckDamage()
end
if self.verbose>=1 then
local nelem=self:CountElements()
local Nelem=#self.elements
local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks()
local nMissions=self:CountRemainingMissison()
local roe=self:GetROE()or-1
local rot=self:GetROT()or-1
local wpidxCurr=self.currentwp
local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0
local wpidxNext=self:GetWaypointIndexNext()or 0
local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0
local wpN=#self.waypoints or 0
local wpF=tostring(self.passedfinalwp)
local speed=UTILS.MpsToKnots(self.velocity or 0)
local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed())
local alt=self.position and self.position.y or 0
local hdg=self.heading or 0
local formation=self.option.Formation or"unknown"
local life=self.life or 0
local ammo=self:GetAmmoTot().Total
local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off"
local cargo=0
for _,_element in pairs(self.elements)do
local element=_element
cargo=cargo+element.weightCargo
end
local home=self.homebase and self.homebase:GetName()or"unknown"
local dest=self.destbase and self.destbase:GetName()or"unknown"
local curr=self.currbase and self.currbase:GetName()or"N/A"
local text=string.format("%s [%d/%d]: ROE/ROT=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Base=%s [%s-->%s]",
fsmstate,nelem,Nelem,roe,rot,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,hdg,ammo,ndetected,cargo,curr,home,dest)
self:I(self.lid..text)
end
if self.verbose>=2 then
local text="Elements:"
for i,_element in pairs(self.elements)do
local element=_element
local name=element.name
local status=element.status
local unit=element.unit
local fuel=unit:GetFuel()or 0
local life=unit:GetLifeRelative()or 0
local lp=unit:GetLife()
local lp0=unit:GetLife0()
local parking=element.parking and tostring(element.parking.TerminalID)or"X"
local ammo=self:GetAmmoElement(element)
text=text..string.format("\n[%d] %s: status=%s, fuel=%.1f, life=%.1f [%.1f/%.1f], guns=%d, rockets=%d, bombs=%d, missiles=%d (AA=%d, AG=%d, AS=%s), parking=%s",
i,name,status,fuel*100,life*100,lp,lp0,ammo.Guns,ammo.Rockets,ammo.Bombs,ammo.Missiles,ammo.MissilesAA,ammo.MissilesAG,ammo.MissilesAS,parking)
end
if#self.elements==0 then
text=text.." none!"
end
self:I(self.lid..text)
end
if self.verbose>=4 and alive then
local ds=self.travelds
local dt=self.dTpositionUpdate
local v=ds/dt
local TmaxFuel=math.huge
for _,_element in pairs(self.elements)do
local element=_element
local fuel=element.unit:GetFuel()or 0
local dFrel=element.fuelrel-fuel
local dFreldt=dFrel/dt
local Tfuel=fuel/dFreldt
if Tfuel<TmaxFuel then
TmaxFuel=Tfuel
end
self:T3(self.lid..string.format("Fuel consumption %s F=%.1f: dF=%.3f  dF/min=%.3f ==> Tfuel=%.1f min",element.name,fuel*100,dFrel*100,dFreldt*100*60,Tfuel/60))
element.fuelrel=fuel
end
self:T(self.lid..string.format("Travelled ds=%.1f km dt=%.1f s ==> v=%.1f knots. Fuel left for %.1f min",self.traveldist/1000,dt,UTILS.MpsToKnots(v),TmaxFuel/60))
end
if false then
for _,_element in pairs(self.elements)do
local element=_element
local unit=element.unit
if unit and unit:IsAlive()then
local vec3=unit:GetVec3()
if vec3 and element.pos then
local id=UTILS.GetMarkID()
trigger.action.lineToAll(-1,id,vec3,element.pos,{1,1,1,0.5},1)
end
element.pos=vec3
end
end
end
if alive and self.group:IsAirborne(true)then
local fuelmin=self:GetFuelMin()
self:T2(self.lid..string.format("Fuel state=%d",fuelmin))
if fuelmin>=self.fuellowthresh then
self.fuellow=false
end
if fuelmin>=self.fuelcriticalthresh then
self.fuelcritical=false
end
if fuelmin<self.fuellowthresh and not self.fuellow then
self:FuelLow()
end
if fuelmin<self.fuelcriticalthresh and not self.fuelcritical then
self:FuelCritical()
end
end
if self.isHelo and self.airboss and self:IsHolding()then
if self.airboss:IsRecovering()or self:IsFuelCritical()then
self:ClearToLand()
end
end
if self:IsAirborne()and self:IsFuelGood()and self.detectionOn and self.engagedetectedOn then
local targetgroup,targetdist=self:_GetDetectedTarget()
if targetgroup then
self:T(self.lid..string.format("Engaging target group %s at distance %d meters",targetgroup:GetName(),targetdist))
self:EngageTarget(targetgroup)
end
end
self:_CheckCargoTransport()
self:_PrintTaskAndMissionStatus()
local mission=self:GetMissionCurrent()
if not mission then
self.Twaiting=nil
self.dTwait=nil
self:_CheckGroupDone()
end
end
function FLIGHTGROUP:OnEventEngineStartup(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element then
if self:IsAirborne()or self:IsInbound()or self:IsHolding()then
else
self:T3(self.lid..string.format("EVENT: Element %s started engines ==> taxiing (if AI)",element.name))
self:ElementEngineOn(element)
element.engineOn=true
end
end
end
end
function FLIGHTGROUP:OnEventTakeOff(EventData)
self:T3(self.lid.."EVENT: TakeOff")
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element then
self:T2(self.lid..string.format("EVENT: Element %s took off ==> airborne",element.name))
self:ElementTakeoff(element,EventData.Place)
end
end
end
function FLIGHTGROUP:OnEventLanding(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
local airbase=EventData.Place
local airbasename="unknown"
if airbase then
airbasename=tostring(airbase:GetName())
end
if element then
self:T3(self.lid..string.format("EVENT: Element %s landed at %s ==> landed",element.name,airbasename))
self:ElementLanded(element,airbase)
end
end
end
function FLIGHTGROUP:OnEventEngineShutdown(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element then
element.engineOn=false
if element.unit and element.unit:IsAlive()then
local airbase=self:GetClosestAirbase()
local parking=self:GetParkingSpot(element,100,airbase)
if airbase and parking then
self:ElementArrived(element,airbase,parking)
self:T3(self.lid..string.format("EVENT: Element %s shut down engines ==> arrived",element.name))
else
self:T3(self.lid..string.format("EVENT: Element %s shut down engines but is not parking. Is it dead?",element.name))
end
else
end
end
end
end
function FLIGHTGROUP:OnEventCrash(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
self:T(self.lid..string.format("EVENT: Element %s crashed ==> destroyed",element.name))
self:ElementDestroyed(element)
end
end
end
function FLIGHTGROUP:OnEventUnitLost(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
self:T2(self.lid..string.format("EVENT: Unit %s lost at t=%.3f",EventData.IniUnitName,timer.getTime()))
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
self:T(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed t=%.3f",element.name,timer.getTime()))
self:ElementDestroyed(element)
end
end
end
function FLIGHTGROUP:onafterElementSpawned(From,Event,To,Element)
self:T(self.lid..string.format("Element spawned %s",Element.name))
if Element.playerName then
self:_InitPlayerData(Element.playerName)
end
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED)
if Element.unit:InAir(not self.isHelo)then
self:__ElementAirborne(0.11,Element)
else
local spot=self:GetParkingSpot(Element,10)
if spot then
self:__ElementParking(0.11,Element,spot)
else
self:T(self.lid..string.format("Element spawned not in air but not on any parking spot."))
self:__ElementParking(0.11,Element)
end
end
end
function FLIGHTGROUP:onafterElementParking(From,Event,To,Element,Spot)
if Spot then
self:_SetElementParkingAt(Element,Spot)
end
self:T(self.lid..string.format("Element parking %s at spot %s",Element.name,Element.parking and tostring(Element.parking.TerminalID)or"N/A"))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.PARKING)
if self:IsTakeoffCold()then
elseif self:IsTakeoffHot()then
self:__ElementEngineOn(0.5,Element)
Element.engineOn=true
elseif self:IsTakeoffRunway()then
self:__ElementEngineOn(0.5,Element)
Element.engineOn=true
end
end
function FLIGHTGROUP:onafterElementEngineOn(From,Event,To,Element)
self:T(self.lid..string.format("Element %s started engines",Element.name))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ENGINEON)
end
function FLIGHTGROUP:onafterElementTaxiing(From,Event,To,Element)
local TerminalID=Element.parking and tostring(Element.parking.TerminalID)or"N/A"
self:T(self.lid..string.format("Element taxiing %s. Parking spot %s is now free",Element.name,TerminalID))
self:_SetElementParkingFree(Element)
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.TAXIING)
end
function FLIGHTGROUP:onafterElementTakeoff(From,Event,To,Element,airbase)
self:T(self.lid..string.format("Element takeoff %s at %s airbase.",Element.name,airbase and airbase:GetName()or"unknown"))
if Element.parking then
self:_SetElementParkingFree(Element)
end
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.TAKEOFF,airbase)
self:__ElementAirborne(0.01,Element)
end
function FLIGHTGROUP:onafterElementAirborne(From,Event,To,Element)
self:T2(self.lid..string.format("Element airborne %s",Element.name))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.AIRBORNE)
end
function FLIGHTGROUP:onafterElementLanded(From,Event,To,Element,airbase)
self:T2(self.lid..string.format("Element landed %s at %s airbase",Element.name,airbase and airbase:GetName()or"unknown"))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.LANDED,airbase)
if self.isHelo then
local Spot=self:GetParkingSpot(Element,10,airbase)
if Spot then
self:_SetElementParkingAt(Element,Spot)
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ARRIVED)
end
end
if self.despawnAfterLanding then
if self.legion then
if airbase and self.legion.airbase and airbase.AirbaseName==self.legion.airbase.AirbaseName then
if self:IsLanded()then
self:ReturnToLegion()
else
self:DespawnElement(Element)
end
end
else
self:DespawnElement(Element)
end
end
end
function FLIGHTGROUP:onafterElementArrived(From,Event,To,Element,airbase,Parking)
self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d",Element.name,airbase and airbase:GetName()or"unknown",Parking and Parking.TerminalID or-99))
self:_SetElementParkingAt(Element,Parking)
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.ARRIVED)
end
function FLIGHTGROUP:onafterElementDestroyed(From,Event,To,Element)
self:GetParent(self).onafterElementDestroyed(self,From,Event,To,Element)
end
function FLIGHTGROUP:onafterElementDead(From,Event,To,Element)
if self.flightcontrol and Element.parking then
self.flightcontrol:SetParkingFree(Element.parking)
end
self:GetParent(self).onafterElementDead(self,From,Event,To,Element)
Element.parking=nil
end
function FLIGHTGROUP:onafterSpawned(From,Event,To)
self:T(self.lid..string.format("Flight spawned"))
if self.verbose>=1 then
local text=string.format("Initialized Flight Group %s:\n",self.groupname)
text=text..string.format("Unit type     = %s\n",tostring(self.actype))
text=text..string.format("Speed max    = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax))
text=text..string.format("Range max    = %.1f km\n",self.rangemax/1000)
text=text..string.format("Ceiling      = %.1f feet\n",UTILS.MetersToFeet(self.ceiling))
text=text..string.format("Weight       = %.1f kg\n",self:GetWeightTotal())
text=text..string.format("Cargo bay    = %.1f kg\n",self:GetFreeCargobay())
text=text..string.format("Tanker type  = %s\n",tostring(self.tankertype))
text=text..string.format("Refuel type  = %s\n",tostring(self.refueltype))
text=text..string.format("AI           = %s\n",tostring(self.isAI))
text=text..string.format("Has EPLRS    = %s\n",tostring(self.isEPLRS))
text=text..string.format("Helicopter   = %s\n",tostring(self.isHelo))
text=text..string.format("Elements     = %d\n",#self.elements)
text=text..string.format("Waypoints    = %d\n",#self.waypoints)
text=text..string.format("Radio        = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On))
text=text..string.format("Ammo         = %d (G=%d/R=%d/B=%d/M=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Bombs,self.ammo.Missiles)
text=text..string.format("FSM state    = %s\n",self:GetState())
text=text..string.format("Is alive     = %s\n",tostring(self.group:IsAlive()))
text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated()))
text=text..string.format("Uncontrolled = %s\n",tostring(self:IsUncontrolled()))
text=text..string.format("Start Air    = %s\n",tostring(self:IsTakeoffAir()))
text=text..string.format("Start Cold   = %s\n",tostring(self:IsTakeoffCold()))
text=text..string.format("Start Hot    = %s\n",tostring(self:IsTakeoffHot()))
text=text..string.format("Start Rwy    = %s\n",tostring(self:IsTakeoffRunway()))
text=text..string.format("Elements:")
for i,_element in pairs(self.elements)do
local element=_element
text=text..string.format("\n[%d] %s: callsign=%s, modex=%s, player=%s",i,element.name,tostring(element.callsign),tostring(element.modex),tostring(element.playerName))
end
self:I(self.lid..text)
end
self:_UpdatePosition()
self.isDead=false
self.isDestroyed=false
if self.isAI then
self:SwitchROE(self.option.ROE)
self:SwitchROT(self.option.ROT)
self:SwitchEPLRS(self.option.EPLRS)
self:SwitchInvisible(self.option.Invisible)
self:SwitchImmortal(self.option.Immortal)
self:SwitchFormation(self.option.Formation)
self:_SwitchTACAN()
if self.radioDefault then
self:SwitchRadio()
else
self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,self.radio.On)
end
if self.callsignDefault then
self:SwitchCallsign(self.callsignDefault.NumberSquad,self.callsignDefault.NumberGroup)
else
self:SetDefaultCallsign(self.callsign.NumberSquad,self.callsign.NumberGroup)
end
self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_JETT,self.jettisonWeapons)
self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,self.prohibitAB)
self:GetGroup():SetOption(AI.Option.Air.id.RTB_ON_BINGO,false)
self:GetGroup():SetOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY,self.jettisonEmptyTanks)
self:__UpdateRoute(-0.5)
else
if self.currbase then
local flightcontrol=_DATABASE:GetFlightControl(self.currbase:GetName())
if flightcontrol then
self:SetFlightControl(flightcontrol)
else
self:_UpdateMenu(0.5)
end
else
self:_UpdateMenu(0.5)
end
end
end
function FLIGHTGROUP:onafterParking(From,Event,To)
local airbase=self:GetClosestAirbase()
local airbasename=airbase:GetName()or"unknown"
self:T(self.lid..string.format("Flight is parking at airbase %s",airbasename))
self.currbase=airbase
if not self.homebase then
self.homebase=airbase
end
self.Tparking=timer.getAbsTime()
local flightcontrol=_DATABASE:GetFlightControl(airbasename)
if flightcontrol then
self:SetFlightControl(flightcontrol)
if self.flightcontrol then
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.PARKING)
end
else
self:T3(self.lid.."INFO: No flight control in onAfterParking!")
end
end
function FLIGHTGROUP:onafterTaxiing(From,Event,To)
self:T(self.lid..string.format("Flight is taxiing"))
self.Tparking=nil
if self.flightcontrol and self.flightcontrol:IsControlling(self)then
if self.isAI then
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAKEOFF)
else
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAXIOUT)
end
end
end
function FLIGHTGROUP:onafterTakeoff(From,Event,To,airbase)
self:T(self.lid..string.format("Flight takeoff from %s",airbase and airbase:GetName()or"unknown airbase"))
if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName()then
self.flightcontrol:_RemoveFlight(self)
self.flightcontrol=nil
end
end
function FLIGHTGROUP:onafterAirborne(From,Event,To)
self:T(self.lid..string.format("Flight airborne"))
self.currbase=nil
self:__Cruise(-0.01)
end
function FLIGHTGROUP:onafterCruise(From,Event,To)
self:T(self.lid..string.format("Flight cruising"))
self.Twaiting=nil
self.dTwait=nil
if self.isAI then
self:_CheckGroupDone(nil,120)
else
self:_UpdateMenu(0.1)
end
end
function FLIGHTGROUP:onafterLanding(From,Event,To)
self:T(self.lid..string.format("Flight is landing"))
self:_SetElementStatusAll(OPSGROUP.ElementStatus.LANDING)
if self.flightcontrol and self.flightcontrol:IsControlling(self)then
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.LANDING)
end
self.Tholding=nil
if self.stack then
self.stack.flightgroup=nil
self.stack=nil
end
end
function FLIGHTGROUP:onafterLanded(From,Event,To,airbase)
self:T(self.lid..string.format("Flight landed at %s",airbase and airbase:GetName()or"unknown place"))
if self.flightcontrol and self.flightcontrol:IsControlling(self)then
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAXIINB)
end
end
function FLIGHTGROUP:onafterLandedAt(From,Event,To)
self:T(self.lid..string.format("Flight landed at"))
if self:IsPickingup()then
self:__Loading(-1)
elseif self:IsTransporting()then
self:__Unloading(-1)
end
end
function FLIGHTGROUP:onafterArrived(From,Event,To)
self:T(self.lid..string.format("Flight arrived"))
if self.flightcontrol then
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.ARRIVED)
end
if not self.isAI then
return
end
local airwing=self:GetAirwing()
if airwing and not(self:IsPickingup()or self:IsTransporting())then
self:T(self.lid..string.format("Airwing asset group %s arrived ==> Adding asset back to stock of airwing %s",self.groupname,airwing.alias))
self:ReturnToLegion(1)
elseif self.isLandingAtAirbase then
local Template=UTILS.DeepCopy(self.template)
self.isLateActivated=false
Template.lateActivation=self.isLateActivated
self.isUncontrolled=true
Template.uncontrolled=self.isUncontrolled
local SpawnPoint=Template.route.points[1]
SpawnPoint.linkUnit=nil
SpawnPoint.helipadId=nil
SpawnPoint.airdromeId=nil
local airbase=self.isLandingAtAirbase
local AirbaseID=airbase:GetID()
if airbase:IsShip()then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif airbase:IsHelipad()then
SpawnPoint.linkUnit=AirbaseID
SpawnPoint.helipadId=AirbaseID
elseif airbase:IsAirdrome()then
SpawnPoint.airdromeId=AirbaseID
end
SpawnPoint.alt=0
SpawnPoint.type=COORDINATE.WaypointType.TakeOffParking
SpawnPoint.action=COORDINATE.WaypointAction.FromParkingArea
local units=Template.units
for i=#units,1,-1 do
local unit=units[i]
local element=self:GetElementByName(unit.name)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
unit.parking=element.parking and element.parking.TerminalID or nil
unit.parking_id=nil
local vec3=element.unit:GetVec3()
local heading=element.unit:GetHeading()
unit.x=vec3.x
unit.y=vec3.z
unit.alt=vec3.y
unit.heading=math.rad(heading)
unit.psi=-unit.heading
else
table.remove(units,i)
end
end
self:_Respawn(0,Template)
self.isLandingAtAirbase=nil
if self:IsPickingup()then
self:__Loading(-1)
elseif self:IsTransporting()then
self:__Unloading(-1)
end
else
self:T(self.lid..string.format("Despawning group in 5 minutes after arrival!"))
self:Despawn(5*60)
end
end
function FLIGHTGROUP:onafterDead(From,Event,To)
if self.flightcontrol then
self.flightcontrol:_RemoveFlight(self)
self.flightcontrol=nil
end
self:GetParent(self).onafterDead(self,From,Event,To)
end
function FLIGHTGROUP:onbeforeUpdateRoute(From,Event,To,n,N)
local allowed=true
local trepeat=nil
if self:IsAlive()then
self:T3(self.lid.."Update route possible. Group is ALIVE")
elseif self:IsDead()then
self:T(self.lid.."Update route denied. Group is DEAD!")
allowed=false
elseif self:IsInUtero()then
self:T(self.lid.."Update route denied. Group is INUTERO!")
allowed=false
else
self:T(self.lid.."Update route denied ==> checking back in 5 sec")
trepeat=-5
allowed=false
end
if allowed and self:IsUncontrolled()then
self:T(self.lid.."Update route denied. Group is UNCONTROLLED!")
local mission=self:GetMissionCurrent()
if mission and mission.type==AUFTRAG.Type.ALERT5 then
trepeat=nil
else
trepeat=-5
end
allowed=false
end
if n and n<1 then
self:T(self.lid.."Update route denied because waypoint n<1!")
allowed=false
end
if not self.currentwp then
self:T(self.lid.."Update route denied because self.currentwp=nil!")
allowed=false
end
local Nn=n or self.currentwp+1
if not Nn or Nn<1 then
self:T(self.lid.."Update route denied because N=nil or N<1")
trepeat=-5
allowed=false
end
if self.taskcurrent>0 then
local task=self:GetTaskByID(self.taskcurrent)
if task then
if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
self:T2(self.lid.."Allowing update route for Task: PatrolZone")
elseif task.dcstask.id==AUFTRAG.SpecialTask.CAPTUREZONE then
self:T2(self.lid.."Allowing update route for Task: CaptureZone")
elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then
self:T2(self.lid.."Allowing update route for Task: ReconMission")
elseif task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then
self:T2(self.lid.."Allowing update route for Task: Patrol Race Track")
elseif task.dcstask.id==AUFTRAG.SpecialTask.HOVER then
self:T2(self.lid.."Allowing update route for Task: Hover")
elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then
self:T2(self.lid.."Allowing update route for Task: Relocate Cohort")
elseif task.description and task.description=="Task_Land_At"then
self:T2(self.lid.."Allowing update route for Task: Task_Land_At")
else
local taskname=task and task.description or"No description"
self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname)))
allowed=false
end
else
self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent))
allowed=false
end
end
if not self.isAI then
allowed=false
end
self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat)))
if trepeat then
self:__UpdateRoute(trepeat,n)
end
return allowed
end
function FLIGHTGROUP:onafterUpdateRoute(From,Event,To,n,N)
n=n or self.currentwp+1
N=N or#self.waypoints
N=math.min(N,#self.waypoints)
local wp={}
local speed=self.group and self.group:GetVelocityKMH()or 100
local waypointType=COORDINATE.WaypointType.TurningPoint
local waypointAction=COORDINATE.WaypointAction.TurningPoint
if self:IsLanded()or self:IsLandedAt()or self:IsAirborne()==false then
waypointType=COORDINATE.WaypointType.TakeOff
end
local current=self:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO,waypointType,waypointAction,speed,true,nil,{},"Current")
table.insert(wp,current)
for i=n,N do
table.insert(wp,self.waypoints[i])
end
if wp[2]then
self.speedWp=wp[2].speed
end
local hb=self.homebase and self.homebase:GetName()or"unknown"
local db=self.destbase and self.destbase:GetName()or"unknown"
self:T(self.lid..string.format("Updating route for WP #%d-%d [%s], homebase=%s destination=%s",n,#wp,self:GetState(),hb,db))
if#wp>1 then
self:Route(wp)
else
if self:IsAirborne()then
self:T(self.lid.."No waypoints left ==> CheckGroupDone")
self:_CheckGroupDone()
end
end
end
function FLIGHTGROUP:onafterOutOfMissilesAA(From,Event,To)
self:T(self.lid.."Group is out of AA Missiles!")
if self.outofAAMrtb then
local airbase=self.destbase or self.homebase
self:T(self.lid.."Calling RTB in onafterOutOfMissilesAA")
self:__RTB(-5,airbase)
end
end
function FLIGHTGROUP:onafterOutOfMissilesAG(From,Event,To)
self:T(self.lid.."Group is out of AG Missiles!")
if self.outofAGMrtb then
local airbase=self.destbase or self.homebase
self:T(self.lid.."Calling RTB in onafterOutOfMissilesAG")
self:__RTB(-5,airbase)
end
end
function FLIGHTGROUP:_CheckGroupDone(delay,waittime)
local fsmstate=self:GetState()
if self:IsAlive()and self.isAI then
if delay and delay>0 then
self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done in %.3f seconds... (t=%.4f)",fsmstate,delay,timer.getTime()))
self:ScheduleOnce(delay,FLIGHTGROUP._CheckGroupDone,self)
else
self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done? (t=%.4f)",fsmstate,timer.getTime()))
if self:IsEngaging()then
self:T(self.lid.."Engaging! Group NOT done...")
return
end
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()
local nPaused=self:_CountPausedMissions()
if nPaused>0 and nPaused==nMissions then
local missionpaused=self:_GetPausedMission()
self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...",missionpaused.name,missionpaused.type))
self:UnpauseMission()
return
end
if self.isLandingAtAirbase then
self:T(self.lid..string.format("Landing at airbase %s! Group NOT done...",self.isLandingAtAirbase:GetName()))
return
end
if self:IsWaiting()then
self:T(self.lid.."Waiting! Group NOT done...")
return
end
self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d",tostring(self.passedfinalwp),nMissions,nTasks,nTransports))
if self:HasPassedFinalWaypoint()or self:GetWaypointIndexNext()==1 then
if self.currentmission==nil and self.taskcurrent==0 and(self.cargoTransport==nil or self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED)then
if nTasks==0 and nMissions==0 and nTransports==0 then
local destbase=self.destbase or self.homebase
local destzone=self.destzone or self.homezone
if waittime then
self:T(self.lid..string.format("Passed Final WP and No current and/or future missions/tasks/transports. Waittime given ==> Waiting for %d sec!",waittime))
self:Wait(waittime)
elseif destbase then
if self.currbase and self.currbase.AirbaseName==destbase.AirbaseName and self:IsParking()then
self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports AND parking at destination airbase ==> Arrived!")
self:Arrived()
else
if self.currbase==nil or self.currbase.AirbaseName~=destbase.AirbaseName then
self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!")
self:__RTB(-0.1,destbase)
end
end
elseif destzone then
self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!")
self:__RTZ(-0.1,destzone)
else
self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!")
self:__Wait(-1)
end
else
if not self:IsParking()then
self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!",nTasks,nMissions))
self:__Wait(-1)
end
end
else
self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do",tostring(self.taskcurrent),tostring(self.currentmission)))
end
else
self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route in -0.01 sec",self:GetState()))
self:__UpdateRoute(-0.01)
end
end
end
end
function FLIGHTGROUP:onbeforeRTB(From,Event,To,airbase,SpeedTo,SpeedHold)
self:T(self.lid..string.format("RTB: before event=%s: %s --> %s to %s",Event,From,To,airbase and airbase:GetName()or"None"))
if self:IsAlive()then
local allowed=true
local Tsuspend=nil
if airbase==nil then
self:T(self.lid.."ERROR: Airbase is nil in RTB() call!")
allowed=false
end
if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then
self:T(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0",airbase:GetCoalition(),self.group:GetCoalition()))
return false
end
if self.currbase and self.currbase:GetName()==airbase:GetName()then
self:T(self.lid.."WARNING: Currbase is already same as RTB airbase. RTB canceled!")
return false
end
if self:IsLanded()then
self:T(self.lid.."WARNING: Flight has already landed. RTB canceled!")
return false
end
if not self.group:IsAirborne(true)then
self:T(self.lid..string.format("WARNING: Group [%s] is not AIRBORNE  ==> RTB event is suspended for 20 sec",self:GetState()))
allowed=false
Tsuspend=-20
local groupspeed=self.group:GetVelocityMPS()
if groupspeed<=1 and not self:IsParking()then
self.RTBRecallCount=self.RTBRecallCount+1
end
if self.RTBRecallCount>6 then
self:T(self.lid..string.format("WARNING: Group [%s] is not moving and was called RTB %d times. Assuming a problem and despawning!",self:GetState(),self.RTBRecallCount))
self.RTBRecallCount=0
self:Despawn(5)
return
end
end
if self:IsFuelGood()then
local Ntot,Nsched,Nwp=self:CountRemainingTasks()
if self.taskcurrent>0 then
self:T(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec"))
Tsuspend=-10
allowed=false
end
if Nsched>0 then
self:T(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec",Nsched))
Tsuspend=-10
allowed=false
end
if Nwp>0 then
self:T(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec",Nwp))
Tsuspend=-10
allowed=false
end
if self.Twaiting and self.dTwait then
self:T(self.lid..string.format("WARNING: Group is Waiting for a specific duration ==> RTB event is canceled",Nwp))
allowed=false
end
end
if Tsuspend and not allowed then
self:T(self.lid.."Calling RTB in onbeforeRTB")
self:__RTB(Tsuspend,airbase,SpeedTo,SpeedHold)
end
return allowed
else
self:T(self.lid.."WARNING: Group is not alive! RTB call not allowed.")
return false
end
end
function FLIGHTGROUP:onafterRTB(From,Event,To,airbase,SpeedTo,SpeedHold,SpeedLand)
self:T(self.lid..string.format("RTB: event=%s: %s --> %s to %s",Event,From,To,airbase:GetName()))
self.destbase=airbase
self:CancelAllMissions()
self:_LandAtAirbase(airbase,SpeedTo,SpeedHold,SpeedLand)
end
function FLIGHTGROUP:onbeforeLandAtAirbase(From,Event,To,airbase)
if self:IsAlive()then
local allowed=true
local Tsuspend=nil
if airbase==nil then
self:T(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!")
allowed=false
end
if airbase and airbase:GetCoalition()~=self.group:GetCoalition()and airbase:GetCoalition()>0 then
self:T(self.lid..string.format("ERROR: Wrong airbase coalition %d in LandAtAirbase() call! We allow only same as group %d or neutral airbases 0",airbase:GetCoalition(),self.group:GetCoalition()))
return false
end
if self.currbase and self.currbase:GetName()==airbase:GetName()then
self:T(self.lid.."WARNING: Currbase is already same as LandAtAirbase airbase. LandAtAirbase canceled!")
return false
end
if self:IsLanded()then
self:T(self.lid.."WARNING: Flight has already landed. LandAtAirbase canceled!")
return false
end
if self:IsParking()then
allowed=false
Tsuspend=-30
self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 30 sec")
elseif self:IsTaxiing()then
allowed=false
Tsuspend=-1
self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 1 sec")
end
if Tsuspend and not allowed then
self:__LandAtAirbase(Tsuspend,airbase)
end
return allowed
else
self:T(self.lid.."WARNING: Group is not alive! LandAtAirbase call not allowed")
return false
end
end
function FLIGHTGROUP:onafterLandAtAirbase(From,Event,To,airbase)
self.isLandingAtAirbase=airbase
self:_LandAtAirbase(airbase)
end
function FLIGHTGROUP:_LandAtAirbase(airbase,SpeedTo,SpeedHold,SpeedLand)
self.currbase=airbase
self:_PassedFinalWaypoint(true,"_LandAtAirbase")
self.Twaiting=nil
self.dTwait=nil
SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise)
SpeedHold=SpeedHold or(self.isHelo and 80 or 250)
SpeedLand=SpeedLand or(self.isHelo and 40 or 170)
self.Tholding=nil
local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d",airbase:GetName(),SpeedTo,SpeedHold,SpeedLand)
self:T(self.lid..text)
local althold=self.isHelo and 1000+math.random(10)*100 or math.random(4,10)*1000
local c0=self:GetCoordinate()
local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold))
local p1=nil
local wpap=nil
local fc=_DATABASE:GetFlightControl(airbase:GetName())
if fc and self.isAI then
local stack=fc:_GetHoldingStack(self)
if stack then
stack.flightgroup=self
self.stack=stack
p0=stack.pos0
p1=stack.pos1
if false then
p0:MarkToAll(string.format("%s: Holding stack P0, alt=%d meters",self:GetName(),p0.y))
p1:MarkToAll(string.format("%s: Holding stack P1, alt=%d meters",self:GetName(),p0.y))
end
else
end
self:SetFlightControl(fc)
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.INBOUND)
local callsign=self:GetCallsignName()
local text=string.format("%s, %s, inbound for landing",fc.alias,callsign)
fc:TransmissionPilot(text,self)
local text=string.format("%s, %s, roger, hold at angels %d. Report entering the pattern.",callsign,fc.alias,stack.angels)
fc:TransmissionTower(text,self,10)
end
local c1=c0:GetIntermediateCoordinate(p0,0.25):SetAltitude(self.altitudeCruise,true)
local c2=c0:GetIntermediateCoordinate(p0,0.75):SetAltitude(self.altitudeCruise,true)
local x1=self.isHelo and UTILS.NMToMeters(2.0)or UTILS.NMToMeters(10)
local x2=self.isHelo and UTILS.NMToMeters(1.0)or UTILS.NMToMeters(5)
local alpha=math.rad(3)
local h1=x1*math.tan(alpha)
local h2=x2*math.tan(alpha)
local runway=airbase:GetActiveRunwayLanding()
self.flaghold:Set(0)
local holdtime=self.holdtime
if fc or self.airboss then
holdtime=nil
end
local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding",self)
local TaskOrbit=self.group:TaskOrbit(p0,nil,UTILS.KnotsToMps(SpeedHold),p1)
local TaskLand=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,holdtime)
local TaskHold=self.group:TaskControlled(TaskOrbit,TaskLand)
local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand",self)
local wp={}
wp[#wp+1]=c1:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{},"Climb")
wp[#wp+1]=c2:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{},"Descent")
wp[#wp+1]=p0:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{TaskArrived,TaskHold,TaskKlar},"Holding Point")
if airbase:IsAirdrome()then
local TaskFinal=self.group:TaskFunction("FLIGHTGROUP._OnFinal",self)
local rheading
if runway then
rheading=runway.heading-180
else
local wind=airbase:GetCoordinate():GetWind()
rheading=-wind
end
local papp=airbase:GetCoordinate():Translate(x1,rheading):SetAltitude(h1)
wp[#wp+1]=papp:WaypointAirTurningPoint("BARO",UTILS.KnotsToKmph(SpeedLand),{TaskFinal},"Final Approach")
local pland=airbase:GetCoordinate():Translate(x2,rheading):SetAltitude(h2)
wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing")
elseif airbase:IsShip()or airbase:IsHelipad()then
local pland=airbase:GetCoordinate()
wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing")
end
if self.isAI then
self:Route(wp,1.0)
end
end
function FLIGHTGROUP:onbeforeWait(From,Event,To,Duration,Altitude,Speed)
local allowed=true
local Tsuspend=nil
if self.taskcurrent>0 and not self:IsLandedAt()then
self:T(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!"))
Tsuspend=-30
allowed=false
end
if self.cargoTransport and not self:IsLandedAt()then
end
if Tsuspend and not allowed then
self:__Wait(Tsuspend,Duration,Altitude,Speed)
end
return allowed
end
function FLIGHTGROUP:onafterWait(From,Event,To,Duration,Altitude,Speed)
local Coord=self:GetCoordinate()
if Altitude then
Altitude=UTILS.FeetToMeters(Altitude)
else
Altitude=self.altitudeCruise
end
Speed=Speed or(self.isHelo and 20 or 250)
local text=string.format("Group set to wait/orbit at altitude %d m and speed %.1f km/h for %s seconds",Altitude,Speed,tostring(Duration))
self:T(self.lid..text)
self.flaghold:Set(0)
local TaskOrbit=self.group:TaskOrbit(Coord,Altitude,UTILS.KnotsToMps(Speed))
local TaskStop=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,Duration)
local TaskCntr=self.group:TaskControlled(TaskOrbit,TaskStop)
local TaskOver=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting",self)
local DCSTasks
if Duration or true then
DCSTasks=self.group:TaskCombo({TaskCntr,TaskOver})
else
DCSTasks=self.group:TaskCombo({TaskOrbit,TaskOver})
end
self:PushTask(DCSTasks)
self.Twaiting=timer.getAbsTime()
self.dTwait=Duration
end
function FLIGHTGROUP:onafterRefuel(From,Event,To,Coordinate)
local text=string.format("Flight group set to refuel at the nearest tanker")
self:T(self.lid..text)
self:PauseMission()
local TaskRefuel=self.group:TaskRefueling()
local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedRefuelling",self)
local DCSTasks={TaskRefuel,TaskFunction}
local Speed=self.speedCruise
local coordinate=self:GetCoordinate()
Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5),self.group:GetHeading(),true)
local wp0=coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true)
local wp9=Coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,"Refuel")
self:Route({wp0,wp9},1)
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)
self.flaghold:Set(0)
if self.despawnAfterHolding then
if self.legion then
self:ReturnToLegion(1)
else
self:Despawn(1)
end
return
end
self.Tholding=timer.getAbsTime()
local text=string.format("Flight group %s is HOLDING now",self.groupname)
self:T(self.lid..text)
if self.flightcontrol then
self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.HOLDING)
if self.isAI then
local callsign=self:GetCallsignName()
local text=string.format("%s, %s, arrived at holding pattern",self.flightcontrol.alias,callsign)
if self.stack then
text=text..string.format(", angels %d.",self.stack.angels)
end
self.flightcontrol:TransmissionPilot(text,self)
local text=string.format("%s, roger, fly heading %d and wait for landing clearance",callsign,self.stack.heading)
self.flightcontrol:TransmissionTower(text,self,10)
end
elseif self.airboss then
if self.isHelo then
local carrierpos=self.airboss:GetCoordinate()
local carrierheading=self.airboss:GetHeading()
local Distance=UTILS.NMToMeters(5)
local Angle=carrierheading+90
local altitude=math.random(12,25)*100
local oc=carrierpos:Translate(Distance,Angle):SetAltitude(altitude,true)
local TaskOrbit=self.group:TaskOrbit(oc,nil,UTILS.KnotsToMps(50))
local TaskLand=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1)
local TaskHold=self.group:TaskControlled(TaskOrbit,TaskLand)
local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand",self)
local DCSTask=self.group:TaskCombo({TaskOrbit,TaskHold,TaskKlar})
self:SetTask(DCSTask)
end
end
end
function FLIGHTGROUP:onafterEngageTarget(From,Event,To,Target)
local DCStask=nil
if Target:IsInstanceOf("UNIT")or Target:IsInstanceOf("STATIC")then
DCStask=self:GetGroup():TaskAttackUnit(Target,true)
elseif Target:IsInstanceOf("GROUP")then
DCStask=self:GetGroup():TaskAttackGroup(Target,nil,nil,nil,nil,nil,nil,true)
elseif Target:IsInstanceOf("SET_UNIT")then
local DCSTasks={}
for _,_unit in pairs(Target:GetSet())do
local unit=_unit
local task=self:GetGroup():TaskAttackUnit(unit,true)
table.insert(DCSTasks)
end
DCStask=self:GetGroup():TaskCombo(DCSTasks)
elseif Target:IsInstanceOf("SET_GROUP")then
local DCSTasks={}
for _,_unit in pairs(Target:GetSet())do
local unit=_unit
local task=self:GetGroup():TaskAttackGroup(Target,nil,nil,nil,nil,nil,nil,true)
table.insert(DCSTasks)
end
DCStask=self:GetGroup():TaskCombo(DCSTasks)
else
self:T("ERROR: unknown Target in EngageTarget! Needs to be a UNIT, STATIC, GROUP, SET_UNIT or SET_GROUP")
return
end
local Task=self:NewTaskScheduled(DCStask,1,"Engage_Target",0)
Task.backupROE=self:GetROE()
self:SwitchROE(ENUMS.ROE.OpenFire)
local mission=self:GetMissionCurrent()
if mission then
self:PauseMission()
end
self:TaskExecute(Task)
end
function FLIGHTGROUP:onafterDisengage(From,Event,To)
self:T(self.lid.."Disengage target")
end
function FLIGHTGROUP:onbeforeLandAt(From,Event,To,Coordinate,Duration)
return self.isHelo
end
function FLIGHTGROUP:onafterLandAt(From,Event,To,Coordinate,Duration)
self:T(self.lid..string.format("Landing at Coordinate for %s seconds",tostring(Duration)))
Coordinate=Coordinate or self:GetCoordinate()
local DCStask=self.group:TaskLandAtVec2(Coordinate:GetVec2(),Duration)
local Task=self:NewTaskScheduled(DCStask,1,"Task_Land_At",0)
self:TaskExecute(Task)
end
function FLIGHTGROUP:onafterFuelLow(From,Event,To)
local fuel=self:GetFuelMin()or 0
local text=string.format("Low fuel %d for flight group %s",fuel,self.groupname)
self:T(self.lid..text)
self.fuellow=true
local airbase=self.destbase or self.homebase
if self.fuellowrefuel and self.refueltype then
local tanker=self:FindNearestTanker(50)
if tanker then
self:T(self.lid..string.format("Send to refuel at tanker %s",tanker:GetName()))
local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker:GetCoordinate(),0.75)
self:Refuel(coordinate)
return
end
end
if airbase and self.fuellowrtb then
self:T(self.lid.."Calling RTB in onafterFuelLow")
self:RTB(airbase)
end
end
function FLIGHTGROUP:onafterFuelCritical(From,Event,To)
local text=string.format("Critical fuel for flight group %s",self.groupname)
self:T(self.lid..text)
self.fuelcritical=true
local airbase=self.destbase or self.homebase
if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel()then
self:T(self.lid.."Calling RTB in onafterFuelCritical")
self:RTB(airbase)
end
end
function FLIGHTGROUP._ReachedHolding(group,flightgroup)
flightgroup:T2(flightgroup.lid..string.format("Group reached holding point"))
flightgroup:__Holding(-1)
end
function FLIGHTGROUP._ClearedToLand(group,flightgroup)
flightgroup:T2(flightgroup.lid..string.format("Group was cleared to land"))
flightgroup:__Landing(-1)
end
function FLIGHTGROUP._OnFinal(group,flightgroup)
flightgroup:T2(flightgroup.lid..string.format("Group on final approach"))
local fc=flightgroup.flightcontrol
if fc and fc:IsControlling(flightgroup)then
fc:_FlightOnFinal(flightgroup)
end
end
function FLIGHTGROUP._FinishedRefuelling(group,flightgroup)
flightgroup:T2(flightgroup.lid..string.format("Group finished refueling"))
flightgroup:__Refueled(-1)
end
function FLIGHTGROUP._FinishedWaiting(group,flightgroup)
flightgroup:T(flightgroup.lid..string.format("Group finished waiting"))
flightgroup.Twaiting=nil
flightgroup.dTwait=nil
flightgroup:_CheckGroupDone(0.1)
end
function FLIGHTGROUP:_InitGroup(Template,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
self.isHelo=group:IsHelicopter()
self.speedMax=group:GetSpeedMax()
if self.speedMax and self.speedMax>3.6 then
self.isMobile=true
else
self.isMobile=false
self.speedMax=0
end
local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110)or UTILS.KnotsToKmph(380)
self.speedCruise=math.min(self.speedMax*0.7,speedCruiseLimit)
self.ammo=self:GetAmmoTot()
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
local callsign=template.units[1].callsign
if type(callsign)=="number"then
local cs=tostring(callsign)
callsign={}
callsign[1]=cs:sub(1,1)
callsign[2]=cs:sub(2,2)
callsign[3]=cs:sub(3,3)
end
self.callsign.NumberSquad=tonumber(callsign[1])
self.callsign.NumberGroup=tonumber(callsign[2])
self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad)
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{}
self.menu.atc=self.menu.atc or{}
self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group,"ATC")
self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group,"Help",self.menu.atc.root)
end
local units=self.group:GetUnits()
local dcsgroup=Group.getByName(self.groupname)
local size0=dcsgroup:getInitialSize()
if#units~=size0 then
self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!",#units,size0))
end
for _,unit in pairs(units)do
self:_AddElementByName(unit:GetName())
end
self.groupinitialized=true
end
return self
end
function FLIGHTGROUP:GetHomebaseFromWaypoints()
local wp=self.waypoints0 and self.waypoints0[1]or nil
if wp then
if wp and wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea
or wp.action==COORDINATE.WaypointAction.FromParkingAreaHot
or wp.action==COORDINATE.WaypointAction.FromRunway then
local airbaseID=nil
if wp.airdromeId then
airbaseID=wp.airdromeId
else
airbaseID=-wp.helipadId
end
local airbase=AIRBASE:FindByID(airbaseID)
return airbase
end
end
return nil
end
function FLIGHTGROUP:FindNearestAirbase(Radius)
local coord=self:GetCoordinate()
local dmin=math.huge
local airbase=nil
for _,_airbase in pairs(AIRBASE.GetAllAirbases())do
local ab=_airbase
local coalitionAB=ab:GetCoalition()
if coalitionAB==self:GetCoalition()or coalitionAB==coalition.side.NEUTRAL then
if airbase then
local d=ab:GetCoordinate():Get2DDistance(coord)
if d<dmin then
d=dmin
airbase=ab
end
end
end
end
return airbase
end
function FLIGHTGROUP:FindNearestTanker(Radius)
Radius=UTILS.NMToMeters(Radius or 50)
if self.refueltype then
local coord=self:GetCoordinate()
local units=coord:ScanUnits(Radius)
local dmin=math.huge
local tanker=nil
for _,_unit in pairs(units.Set)do
local unit=_unit
local istanker,refuelsystem=unit:IsTanker()
if istanker and self.refueltype==refuelsystem and unit:IsAlive()and unit:GetCoalition()==self:GetCoalition()then
local d=unit:GetCoordinate():Get2DDistance(coord)
if d<dmin then
dmin=d
tanker=unit
end
end
end
if tanker then
return tanker:GetGroup()
end
end
return nil
end
function FLIGHTGROUP:GetDestinationFromWaypoints()
local wp=self.waypoints0 and self.waypoints0[#self.waypoints0]or nil
if wp then
if wp and wp.action and wp.action==COORDINATE.WaypointAction.Landing then
local airbaseID=wp.airdromeId or wp.helipadId
local airbase=AIRBASE:FindByID(airbaseID)
return airbase
end
end
return nil
end
function FLIGHTGROUP:IsTakeoffHot()
local wp=self.waypoints0 and self.waypoints0[1]or nil
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.FromParkingAreaHot then
return true
else
return false
end
end
return nil
end
function FLIGHTGROUP:IsTakeoffCold()
local wp=self.waypoints0 and self.waypoints0[1]or nil
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea then
return true
else
return false
end
end
return nil
end
function FLIGHTGROUP:IsTakeoffRunway()
local wp=self.waypoints0 and self.waypoints0[1]or nil
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.FromRunway then
return true
else
return false
end
end
return nil
end
function FLIGHTGROUP:IsTakeoffAir()
local wp=self.waypoints0 and self.waypoints0[1]or nil
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.TurningPoint or wp.action==COORDINATE.WaypointAction.FlyoverPoint then
return true
else
return false
end
end
return nil
end
function FLIGHTGROUP:IsLandingAir(wp)
wp=wp or self:GetWaypointFinal()
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.TurningPoint or wp.action==COORDINATE.WaypointAction.FlyoverPoint then
return true
else
return false
end
end
return nil
end
function FLIGHTGROUP:IsLandingAirbase(wp)
wp=wp or self:GetWaypointFinal()
if wp then
if wp.action and wp.action==COORDINATE.WaypointAction.Landing then
return true
else
return false
end
end
return nil
end
function FLIGHTGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Altitude,Updateroute)
local coordinate=self:_CoordinateFromObject(Coordinate)
local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID)
Speed=Speed or self:GetSpeedCruise()
self:T3(self.lid..string.format("Waypoint Speed=%.1f knots",Speed))
local alttype=COORDINATE.WaypointAltType.BARO
if self.isHelo then
alttype=COORDINATE.WaypointAltType.RADIO
end
local wp=coordinate:WaypointAir(alttype,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(Speed),true,nil,{})
local waypoint=self:_CreateWaypoint(wp)
if Altitude then
waypoint.alt=UTILS.FeetToMeters(Altitude)
end
self:_AddWaypoint(waypoint,wpnumber)
self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d",wpnumber,Speed,self.currentwp,#self.waypoints))
if Updateroute==nil or Updateroute==true then
self:__UpdateRoute(-0.01)
end
return waypoint
end
function FLIGHTGROUP:AddWaypointLanding(Airbase,Speed,AfterWaypointWithID,Altitude,Updateroute)
local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID)
if wpnumber>self.currentwp then
self:_PassedFinalWaypoint(false,"AddWaypointLanding")
end
Speed=Speed or self.speedCruise
local Coordinate=Airbase:GetCoordinate()
local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO,COORDINATE.WaypointType.Land,COORDINATE.WaypointAction.Landing,Speed,nil,Airbase,{},"Landing Temp",nil)
local waypoint=self:_CreateWaypoint(wp)
if Altitude then
waypoint.alt=UTILS.FeetToMeters(Altitude)
end
self:_AddWaypoint(waypoint,wpnumber)
self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d",wpnumber,Speed,self.currentwp,#self.waypoints))
if Updateroute==nil or Updateroute==true then
self:__UpdateRoute(-1)
end
return waypoint
end
function FLIGHTGROUP:GetPlayerElement()
for _,_element in pairs(self.elements)do
local element=_element
if not element.ai then
return element
end
end
return nil
end
function FLIGHTGROUP:GetPlayerName()
local playerElement=self:GetPlayerElement()
if playerElement then
return playerElement.playerName
end
return nil
end
function FLIGHTGROUP:_SetElementParkingAt(Element,Spot)
Element.parking=Spot
if Spot then
self:T(self.lid..string.format("Element %s is parking on spot %d",Element.name,Spot.TerminalID))
local fc=_DATABASE:GetFlightControl(Spot.AirbaseName)
if fc and not self.flightcontrol then
self:SetFlightControl(fc)
end
if self.flightcontrol then
self.flightcontrol:SetParkingOccupied(Element.parking,Element.name)
end
end
end
function FLIGHTGROUP:_SetElementParkingFree(Element)
if Element.parking then
if self.flightcontrol then
self.flightcontrol:SetParkingFree(Element.parking)
end
Element.parking=nil
end
end
function FLIGHTGROUP:_GetOnboardNumber(unitname)
local group=UNIT:FindByName(unitname):GetGroup()
local units=group:GetTemplate().units
local numbers={}
for _,unit in pairs(units)do
if unitname==unit.name then
return tostring(unit.onboard_num)
end
end
return nil
end
function FLIGHTGROUP:_IsHumanUnit(unit)
local playerunit=self:_GetPlayerUnitAndName(unit:GetName())
if playerunit then
return true
else
return false
end
end
function FLIGHTGROUP:_IsHuman(group)
local units=group:GetUnits()
for _,_unit in pairs(units)do
local human=self:_IsHumanUnit(_unit)
if human then
return true
end
end
return false
end
function FLIGHTGROUP:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName~=nil then
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
if DCSunit and unit and playername then
return unit,playername
end
end
end
return nil,nil
end
function FLIGHTGROUP:GetParkingSpot(element,maxdist,airbase)
local coord=element.unit:GetCoordinate()
airbase=airbase or self:GetClosestAirbase()
local parking=airbase.parking
if airbase and airbase:IsShip()then
if#parking>1 then
coord=airbase:GetRelativeCoordinate(coord.x,coord.y,coord.z)
else
coord.x=0
coord.z=0
maxdist=500
end
end
local spot=nil
local dist=nil
local distmin=math.huge
for _,_parking in pairs(parking)do
local parking=_parking
dist=coord:Get2DDistance(parking.Coordinate)
if dist<distmin then
distmin=dist
spot=_parking
end
end
if distmin<=maxdist and not element.unit:InAir()then
return spot
else
return nil
end
end
function FLIGHTGROUP:GetHoldingTime()
if self.Tholding then
return timer.getAbsTime()-self.Tholding
end
return-1
end
function FLIGHTGROUP:GetParkingTime()
if self.Tparking then
return timer.getAbsTime()-self.Tparking
end
return-1
end
function FLIGHTGROUP:GetClosestAirbase()
local group=self.group
if group and group:IsAlive()then
local coord=group:GetCoordinate()
local coalition=self:GetCoalition()
if coord then
local airbase=coord:GetClosestAirbase()
return airbase
else
return nil
end
else
return nil
end
end
function FLIGHTGROUP:GetParking(airbase)
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=true
local function _overlap(l1,l2,dist)
local safedist=(l1/2+l2/2)*1.05
local safe=(dist>safedist)
return safe
end
local function _clients()
local clients=_DATABASE.CLIENTS
local coords={}
for clientname,client in pairs(clients)do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units
for i,unit in pairs(units)do
local coord=COORDINATE:New(unit.x,unit.alt,unit.y)
coords[unit.name]=coord
end
end
return coords
end
local airbasecategory=airbase:GetAirbaseCategory()
local parkingdata=airbase:GetParkingSpotsTable()
local obstacles={}
for _,_parkingspot in pairs(parkingdata)do
local parkingspot=_parkingspot
local _,_,_,_units,_statics,_sceneries=parkingspot.Coordinate:ScanObjects(scanradius,scanunits,scanstatics,scanscenery)
for _,_unit in pairs(_units)do
local unit=_unit
local _coord=unit:GetCoordinate()
local _size=self:_GetObjectSize(unit:GetDCSObject())
local _name=unit:GetName()
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"})
end
local clientcoords=_clients()
for clientname,_coord in pairs(clientcoords)do
table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"})
end
for _,static in pairs(_statics)do
local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=static:getName()
local _size=self:_GetObjectSize(static)
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"})
end
for _,scenery in pairs(_sceneries)do
local _vec3=scenery:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=scenery:getTypeName()
local _size=self:_GetObjectSize(scenery)
table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"})
end
end
local parking={}
local terminaltype=self:_GetTerminal(self.attribute,airbase:GetAirbaseCategory())
for i,_element in pairs(self.elements)do
local element=_element
local gotit=false
for _,_parkingspot in pairs(parkingdata)do
local parkingspot=_parkingspot
if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)then
local free=true
local problem=nil
if verysafe and parkingspot.TOAC then
free=false
self:T2(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).",parkingspot.TerminalID))
end
for _,obstacle in pairs(obstacles)do
local dist=parkingspot.Coordinate:Get2DDistance(obstacle.coord)
local safe=_overlap(element.size,obstacle.size,dist)
if not safe then
free=false
problem=obstacle
problem.dist=dist
break
end
end
if self.flightcontrol and self.flightcontrol.airbasename==airbase:GetName()then
local problem=self.flightcontrol:IsParkingReserved(parkingspot)or self.flightcontrol:IsParkingOccupied(parkingspot)
if problem then
free=false
end
end
if free then
table.insert(parking,parkingspot)
self:T2(self.lid..string.format("Parking spot %d is free for element %s!",parkingspot.TerminalID,element.name))
table.insert(obstacles,{coord=parkingspot.Coordinate,size=element.size,name=element.name,type="element"})
gotit=true
break
else
self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!",parkingspot.TerminalID))
end
end
end
if not gotit then
self:T(self.lid..string.format("WARNING: No free parking spot for element %s",element.name))
return nil
end
end
return parking
end
function FLIGHTGROUP:_GetObjectSize(DCSobject)
local DCSdesc=DCSobject:getDesc()
if DCSdesc.box then
local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x)
local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y)
local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z)
return math.max(x,z),x,y,z
end
return 0,0,0,0
end
function FLIGHTGROUP:_GetAttribute()
local attribute=FLIGHTGROUP.Attribute.OTHER
local group=self.group
if group then
local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes")
local awacs=group:HasAttribute("AWACS")
local fighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers"))
local bomber=group:HasAttribute("Strategic bombers")
local tanker=group:HasAttribute("Tankers")
local uav=group:HasAttribute("UAVs")
local transporthelo=group:HasAttribute("Transport helicopters")
local attackhelicopter=group:HasAttribute("Attack helicopters")
if transportplane then
attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE
elseif awacs then
attribute=FLIGHTGROUP.Attribute.AIR_AWACS
elseif fighter then
attribute=FLIGHTGROUP.Attribute.AIR_FIGHTER
elseif bomber then
attribute=FLIGHTGROUP.Attribute.AIR_BOMBER
elseif tanker then
attribute=FLIGHTGROUP.Attribute.AIR_TANKER
elseif transporthelo then
attribute=FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO
elseif attackhelicopter then
attribute=FLIGHTGROUP.Attribute.AIR_ATTACKHELO
elseif uav then
attribute=FLIGHTGROUP.Attribute.AIR_UAV
end
end
return attribute
end
function FLIGHTGROUP:_GetTerminal(_attribute,_category)
local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==FLIGHTGROUP.Attribute.AIR_FIGHTER or _attribute==FLIGHTGROUP.Attribute.AIR_UAV then
_terminal=AIRBASE.TerminalType.FighterAircraft
elseif _attribute==FLIGHTGROUP.Attribute.AIR_BOMBER or _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE or _attribute==FLIGHTGROUP.Attribute.AIR_TANKER or _attribute==FLIGHTGROUP.Attribute.AIR_AWACS then
_terminal=AIRBASE.TerminalType.OpenBig
elseif _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO then
_terminal=AIRBASE.TerminalType.HelicopterUsable
else
end
if _category==Airbase.Category.SHIP then
if not(_attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO)then
_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
end
return _terminal
end
function FLIGHTGROUP:_CheckStuck(Despawn)
if not self:IsTaxiing()then
return nil
end
local Tnow=timer.getTime()
local ExpectedSpeed=5
local speed=self:GetVelocity()
if speed<0.1 then
if ExpectedSpeed>0 and not self.stuckTimestamp then
self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected",speed,ExpectedSpeed))
self.stuckTimestamp=Tnow
self.stuckVec3=self:GetVec3()
end
else
self.stuckTimestamp=nil
end
local holdtime=nil
if self.stuckTimestamp then
holdtime=Tnow-self.stuckTimestamp
self:Stuck(holdtime)
if holdtime>=5*60 and holdtime<15*60 then
self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime))
elseif holdtime>=15*60 then
self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime))
local mission=self:GetMissionCurrent()
if mission then
self:T(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck",mission:GetName(),mission:GetType()))
self:MissionCancel(mission)
end
if self.stuckDespawn then
if self.legion then
self:T(self.lid..string.format("Asset is returned to its legion after being stuck!"))
self:ReturnToLegion()
else
self:T(self.lid..string.format("Despawning group after being stuck!"))
self:Despawn()
end
end
end
end
return holdtime
end
function FLIGHTGROUP:_UpdateMenu(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,FLIGHTGROUP._UpdateMenu,self)
else
local player=self:GetPlayerElement()
if player and player.status~=OPSGROUP.ElementStatus.DEAD then
if self.verbose>=2 then
local text=string.format("Updating MENU: State=%s, ATC=%s [%s]",self:GetState(),
self.flightcontrol and self.flightcontrol.airbasename or"None",self.flightcontrol and self.flightcontrol:GetFlightStatus(self)or"Unknown")
MESSAGE:New(text,5):ToGroup(self.group)
self:I(self.lid..text)
end
local position=self:GetCoordinate(nil,player.name)
local fc={}
for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS)do
local flightcontrol=_flightcontrol
local coord=flightcontrol:GetCoordinate()
local dist=coord:Get2DDistance(position)
table.insert(fc,{airbasename=airbasename,dist=dist})
end
local function _sort(a,b)
return a.dist<b.dist
end
table.sort(fc,_sort)
self.menu.atc.root:RemoveSubMenus()
self:_CreateMenuAtcHelp(self.menu.atc.root)
local N=7
local gotairbase=nil
if self.flightcontrol then
self.flightcontrol:_CreatePlayerMenu(self,self.menu.atc.root)
gotairbase=self.flightcontrol.airbasename
N=N-1
end
for i=1,math.min(#fc,N)do
local airbasename=fc[i].airbasename
if gotairbase==nil or airbasename~=gotairbase then
local flightcontrol=_DATABASE:GetFlightControl(airbasename)
flightcontrol:_CreatePlayerMenu(self,self.menu.atc.root)
end
end
else
self:E(self.lid.."ERROR: Player dead in update menu!")
end
end
end
function FLIGHTGROUP:_CreateMenuAtcHelp(rootmenu)
local helpmenu=MENU_GROUP:New(self.group,"Help",rootmenu)
local groupname=self.groupname
local skillmenu=MENU_GROUP:New(self.group,"Skill Level",helpmenu)
MENU_GROUP_COMMAND:New(self.group,"Student",skillmenu,self._PlayerSkill,self,FLIGHTGROUP.PlayerSkill.STUDENT)
MENU_GROUP_COMMAND:New(self.group,"Aviator",skillmenu,self._PlayerSkill,self,FLIGHTGROUP.PlayerSkill.AVIATOR)
MENU_GROUP_COMMAND:New(self.group,"Graduate",skillmenu,self._PlayerSkill,self,FLIGHTGROUP.PlayerSkill.GRADUATE)
MENU_GROUP_COMMAND:New(self.group,"Instructor",skillmenu,self._PlayerSkill,self,FLIGHTGROUP.PlayerSkill.INSTRUCTOR)
MENU_GROUP_COMMAND:New(self.group,"Subtitles On/Off",helpmenu,self._PlayerSubtitles,self)
MENU_GROUP_COMMAND:New(self.group,"My Voice On/Off",helpmenu,self._MenuNotImplemented,self,groupname)
MENU_GROUP_COMMAND:New(self.group,"Mark Parking",helpmenu,self._MarkParking,self)
MENU_GROUP_COMMAND:New(self.group,"Update Menu",helpmenu,self._UpdateMenu,self,0)
MENU_GROUP_COMMAND:New(self.group,"My Status",helpmenu,self._PlayerMyStatus,self,groupname)
end
function FLIGHTGROUP:_MenuNotImplemented(groupname)
local flight=_DATABASE:GetOpsGroup(groupname)
if flight then
local text=string.format("Sorry, this feature is not implemented yet!")
MESSAGE:New(text,10,nil,true):ToGroup(flight.group)
end
end
function FLIGHTGROUP:_PlayerMyStatus()
local fc=self.flightcontrol
local playerdata=self:_GetPlayerData()
local playerElement=self:GetPlayerElement()
local text=string.format("My Status:")
text=text..string.format("\nPlayer Name: %s",tostring(playerdata.name))
text=text..string.format("\nCallsign: %s",tostring(self:GetCallsignName()))
text=text..string.format("\nFlight status: %s",tostring(self:GetState()))
text=text..string.format("\nFlight control: %s [%s]",tostring(fc and fc.airbasename or"N/A"),tostring(fc and fc:GetFlightStatus(self)or"N/A"))
text=text..string.format("\nSubtitles: %s",tostring(playerdata.subtitles))
text=text..string.format("\nMy Voice: %s",tostring(playerdata.myvoice))
if fc then
if playerElement.parking then
local spot=fc:GetParkingSpotByID(playerElement.parking.TerminalID)
if spot then
text=text..string.format("\nParking spot: %d [%s]",spot.TerminalID,spot.Status or"Unknown")
end
end
end
MESSAGE:New(text,10,nil,true):ToGroup(self.group)
end
function FLIGHTGROUP:_PlayerSubtitles()
local playerData=self:_GetPlayerData()
if playerData then
playerData.subtitles=not playerData.subtitles
local onoff=playerData.subtitles==true and"ON"or"OFF"
MESSAGE:New(string.format("%s, subtitles are now %s",playerData.name,onoff),10,nil,true):ToGroup(self.group)
else
end
end
function FLIGHTGROUP:_MarkParking()
local playerElement=self:GetPlayerElement()
if playerElement then
local playerName=tostring(playerElement.playerName)
local message=string.format("No assigned parking spot for you could be found, %s",playerName)
if playerElement.parking then
local terminalID=playerElement.parking.TerminalID
local spotStatus=tostring(playerElement.parking.Status)
local text=string.format("Your parking spot, %s\nTerminal ID=%d [%s]",playerName,terminalID,spotStatus)
message=string.format("%s, your parking spot is Terminal ID=%d [%s]. Check the marker on the F10 map.",playerName,terminalID,spotStatus)
playerElement.parking.Coordinate:MarkToGroup(text,self.group)
end
MESSAGE:New(string.format(message,playerName),10):ToGroup(self.group)
end
end
function FLIGHTGROUP:_PlayerSkill(Skill)
local playerData=self:_GetPlayerData()
if playerData then
playerData.skill=Skill
MESSAGE:New(string.format("%s, your skill is %s",playerData.name,tostring(playerData.skill)),10,nil,true):ToGroup(self.group)
else
end
end
function FLIGHTGROUP:_InitPlayerData(PlayerName)
if PlayerName then
local playerData=FLIGHTGROUP.Players[PlayerName]
if not playerData then
local playerData={}
playerData.name=PlayerName
playerData.skill=FLIGHTGROUP.PlayerSkill.STUDENT
playerData.subtitles=true
playerData.myvoice=true
self:T(self.lid..string.format("Init player data for %s",PlayerName))
FLIGHTGROUP.Players[PlayerName]=playerData
end
return playerData
else
self:E(self.lid..string.format("ERROR: Player name is nil!"))
end
return nil
end
function FLIGHTGROUP:_GetPlayerData()
local playerElement=self:GetPlayerElement()
if playerElement and playerElement.playerName then
return FLIGHTGROUP.Players[playerElement.playerName]
end
return nil
end
function FLIGHTGROUP:_GetDistToParking(Spot,Coordinate)
local dist=99999
if Spot then
local airbase=AIRBASE:FindByName(Spot.AirbaseName)
if airbase:IsShip()then
local a=airbase:GetVec2()
local b=Spot.Coordinate:GetVec2()
local c=Coordinate:GetVec2()
local t=UTILS.Vec2Substract(c,a)
local unit=UNIT:FindByName(Spot.AirbaseName)
local hdg=unit:GetHeading()
t=UTILS.Vec2Rotate2D(t,-hdg)
dist=UTILS.VecDist2D(b,t)
else
dist=Coordinate:Get2DDistance(Spot.Coordinate)
end
end
return dist
end
FLOTILLA={
ClassName="FLOTILLA",
verbose=0,
weaponData={},
}
FLOTILLA.version="0.1.0"
function FLOTILLA:New(TemplateGroupName,Ngroups,FlotillaName)
local self=BASE:Inherit(self,COHORT:New(TemplateGroupName,Ngroups,FlotillaName))
self:AddMissionCapability(AUFTRAG.Type.NOTHING,50)
self.isNaval=true
self.ammo=self:_CheckAmmo()
return self
end
function FLOTILLA:SetFleet(Fleet)
self.legion=Fleet
return self
end
function FLOTILLA:GetFleet()
return self.legion
end
function FLOTILLA:onafterStart(From,Event,To)
local text=string.format("Starting %s v%s %s",self.ClassName,self.version,self.name)
self:I(self.lid..text)
self:__Status(-1)
end
function FLOTILLA:onafterStatus(From,Event,To)
if self.verbose>=1 then
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A"
local skill=self.skill and tostring(self.skill)or"N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0;local NassetsP=0;local NassetsQ=0
if self.legion then
NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self)
end
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate,self.aircrafttype,callsign,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ)
self:T(self.lid..text)
if self.verbose>=3 and self.weaponData then
local text="Weapon Data:"
for bit,_weapondata in pairs(self.weaponData)do
local weapondata=_weapondata
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bit,weapondata.RangeMin/1000,weapondata.RangeMax/1000)
end
self:I(self.lid..text)
end
self:_CheckAssetStatus()
end
if not self:IsStopped()then
self:__Status(-60)
end
end
INTEL={
ClassName="INTEL",
verbose=0,
lid=nil,
alias=nil,
filterCategory={},
detectionset=nil,
Contacts={},
ContactsLost={},
ContactsUnknown={},
Clusters={},
clustercounter=1,
clusterradius=15000,
clusteranalysis=true,
clustermarkers=false,
clusterarrows=false,
prediction=300,
detectStatics=false,
}
INTEL.Ctype={
GROUND="Ground",
NAVAL="Naval",
AIRCRAFT="Aircraft",
STRUCTURE="Structure"
}
INTEL.version="0.3.6"
function INTEL:New(DetectionSet,Coalition,Alias)
local self=BASE:Inherit(self,FSM:New())
self.detectionset=DetectionSet or SET_GROUP:New()
if Coalition and type(Coalition)=="string"then
if Coalition=="blue"then
Coalition=coalition.side.BLUE
elseif Coalition=="red"then
Coalition=coalition.side.RED
elseif Coalition=="neutral"then
Coalition=coalition.side.NEUTRAL
else
self:E("ERROR: Unknown coalition in INTEL!")
end
end
self.coalition=Coalition or DetectionSet:CountAlive()>0 and DetectionSet:GetFirst():GetCoalition()or nil
if self.coalition then
local coalitionname=UTILS.GetCoalitionName(self.coalition):lower()
self.detectionset:FilterCoalitions(coalitionname)
end
self.detectionset:FilterOnce()
if Alias then
self.alias=tostring(Alias)
else
self.alias="INTEL SPECTRE"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="INTEL KGB"
elseif self.coalition==coalition.side.BLUE then
self.alias="INTEL CIA"
end
end
end
self.DetectVisual=true
self.DetectOptical=true
self.DetectRadar=true
self.DetectIRST=true
self.DetectRWR=true
self.DetectDLINK=true
self.statusupdate=-60
self.lid=string.format("%s (%s) | ",self.alias,self.coalition and UTILS.GetCoalitionName(self.coalition)or"unknown")
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("*","Detect","*")
self:AddTransition("*","NewContact","*")
self:AddTransition("*","LostContact","*")
self:AddTransition("*","NewCluster","*")
self:AddTransition("*","LostCluster","*")
self:SetForgetTime()
self:SetAcceptZones()
self:SetRejectZones()
self:SetConflictZones()
return self
end
function INTEL:SetAcceptZones(AcceptZoneSet)
self.acceptzoneset=AcceptZoneSet or SET_ZONE:New()
return self
end
function INTEL:AddAcceptZone(AcceptZone)
self.acceptzoneset:AddZone(AcceptZone)
return self
end
function INTEL:RemoveAcceptZone(AcceptZone)
self.acceptzoneset:Remove(AcceptZone:GetName(),true)
return self
end
function INTEL:SetRejectZones(RejectZoneSet)
self.rejectzoneset=RejectZoneSet or SET_ZONE:New()
return self
end
function INTEL:AddRejectZone(RejectZone)
self.rejectzoneset:AddZone(RejectZone)
return self
end
function INTEL:RemoveRejectZone(RejectZone)
self.rejectzoneset:Remove(RejectZone:GetName(),true)
return self
end
function INTEL:SetConflictZones(ConflictZoneSet)
self.conflictzoneset=ConflictZoneSet or SET_ZONE:New()
return self
end
function INTEL:AddConflictZone(ConflictZone)
self.conflictzoneset:AddZone(ConflictZone)
return self
end
function INTEL:RemoveConflictZone(ConflictZone)
self.conflictzoneset:Remove(ConflictZone:GetName(),true)
return self
end
function INTEL:SetForgetTime(TimeInterval)
return self
end
function INTEL:SetFilterCategory(Categories)
if type(Categories)~="table"then
Categories={Categories}
end
self.filterCategory=Categories
local text="Filter categories: "
for _,category in pairs(self.filterCategory)do
text=text..string.format("%d,",category)
end
self:T(self.lid..text)
return self
end
function INTEL:SetRadarBlur(minheight,thresheight,thresblur,closing)
self.RadarBlur=true
self.RadarBlurMinHeight=minheight or 250
self.RadarBlurThresHeight=thresheight or 90
self.RadarBlurThresBlur=thresblur or 85
self.RadarBlurClosing=closing or 20
self.RadarBlurClosingSquare=self.RadarBlurClosing*self.RadarBlurClosing
return self
end
function INTEL:SetAcceptRange(Range)
self.RadarAcceptRange=true
self.RadarAcceptRangeKilometers=Range or 75
return self
end
function INTEL:FilterCategoryGroup(GroupCategories)
if type(GroupCategories)~="table"then
GroupCategories={GroupCategories}
end
self.filterCategoryGroup=GroupCategories
local text="Filter group categories: "
for _,category in pairs(self.filterCategoryGroup)do
text=text..string.format("%d,",category)
end
self:T(self.lid..text)
return self
end
function INTEL:AddAgent(AgentGroup)
if AgentGroup:IsInstanceOf("OPSGROUP")then
AgentGroup=AgentGroup:GetGroup()
end
self.detectionset:AddGroup(AgentGroup,true)
return self
end
function INTEL:SetClusterAnalysis(Switch,Markers,Arrows)
self.clusteranalysis=Switch
self.clustermarkers=Markers
self.clusterarrows=Arrows
return self
end
function INTEL:SetDetectStatics(Switch)
if Switch and Switch==true then
self.detectStatics=true
else
self.detectStatics=false
end
return self
end
function INTEL:SetVerbosity(Verbosity)
self.verbose=Verbosity or 2
return self
end
function INTEL:AddMissionToContact(Contact,Mission)
if Mission and Contact then
Contact.mission=Mission
end
return self
end
function INTEL:AddMissionToCluster(Cluster,Mission)
if Mission and Cluster then
Cluster.mission=Mission
end
return self
end
function INTEL:SetClusterRadius(radius)
self.clusterradius=(radius or 15)*1000
return self
end
function INTEL:SetDetectionTypes(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
self.DetectVisual=DetectVisual and true
self.DetectOptical=DetectOptical and true
self.DetectRadar=DetectRadar and true
self.DetectIRST=DetectIRST and true
self.DetectRWR=DetectRWR and true
self.DetectDLINK=DetectDLINK and true
return self
end
function INTEL:GetContactTable()
if self:Is("Running")then
return self.Contacts
else
return nil
end
end
function INTEL:GetClusterTable()
if self:Is("Running")and self.clusteranalysis then
return self.Clusters
else
return nil
end
end
function INTEL:GetContactName(Contact)
return Contact.groupname
end
function INTEL:GetContactGroup(Contact)
return Contact.group
end
function INTEL:GetContactThreatlevel(Contact)
return Contact.threatlevel
end
function INTEL:GetContactTypeName(Contact)
return Contact.typename
end
function INTEL:GetContactCategoryName(Contact)
return Contact.categoryname
end
function INTEL:GetContactCoordinate(Contact)
return Contact.position
end
function INTEL:onafterStart(From,Event,To)
local text=string.format("Starting INTEL v%s",self.version)
self:I(self.lid..text)
self:__Status(-math.random(10))
return self
end
function INTEL:onafterStatus(From,Event,To)
local fsmstate=self:GetState()
self.ContactsLost={}
self.ContactsUnknown={}
self:UpdateIntel()
local Ncontacts=#self.Contacts
local Nclusters=#self.Clusters
if self.verbose>=1 then
local text=string.format("Status %s [Agents=%s]: Contacts=%d, Clusters=%d, New=%d, Lost=%d",fsmstate,self.detectionset:CountAlive(),Ncontacts,Nclusters,#self.ContactsUnknown,#self.ContactsLost)
self:I(self.lid..text)
end
if self.verbose>=2 and Ncontacts>0 then
local text="Detected Contacts:"
for _,_contact in pairs(self.Contacts)do
local contact=_contact
local dT=timer.getAbsTime()-contact.Tdetected
text=text..string.format("\n- %s (%s): %s, units=%d, T=%d sec",contact.categoryname,contact.attribute,contact.groupname,contact.isStatic and 1 or contact.group:CountAliveUnits(),dT)
if contact.mission then
local mission=contact.mission
text=text..string.format(" mission name=%s type=%s target=%s",mission.name,mission.type,mission:GetTargetName()or"unknown")
end
end
self:I(self.lid..text)
end
self:__Status(self.statusupdate)
return self
end
function INTEL:UpdateIntel()
local DetectedUnits={}
local RecceDetecting={}
for _,_group in pairs(self.detectionset.Set or{})do
local group=_group
if group and group:IsAlive()then
for _,_recce in pairs(group:GetUnits())do
local recce=_recce
self:GetDetectedUnits(recce,DetectedUnits,RecceDetecting,self.DetectVisual,self.DetectOptical,self.DetectRadar,self.DetectIRST,self.DetectRWR,self.DetectDLINK)
end
end
end
local remove={}
for unitname,_unit in pairs(DetectedUnits)do
local unit=_unit
local inconflictzone=false
if self.conflictzoneset:Count()>0 then
for _,_zone in pairs(self.conflictzoneset.Set)do
local zone=_zone
if unit:IsInZone(zone)then
inconflictzone=true
break
end
end
end
if self.acceptzoneset:Count()>0 then
local inzone=false
for _,_zone in pairs(self.acceptzoneset.Set)do
local zone=_zone
if unit:IsInZone(zone)then
inzone=true
break
end
end
if(not inzone)and(not inconflictzone)then
table.insert(remove,unitname)
end
end
if self.rejectzoneset:Count()>0 then
local inzone=false
for _,_zone in pairs(self.rejectzoneset.Set)do
local zone=_zone
if unit:IsInZone(zone)then
inzone=true
break
end
end
if inzone and(not inconflictzone)then
table.insert(remove,unitname)
end
end
if#self.filterCategory>0 and unit:IsInstanceOf("UNIT")then
local unitcategory=unit:GetUnitCategory()
local keepit=false
for _,filtercategory in pairs(self.filterCategory)do
if unitcategory==filtercategory then
keepit=true
break
end
end
if not keepit then
self:T(self.lid..string.format("Removing unit %s category=%d",unitname,unit:GetCategory()))
table.insert(remove,unitname)
end
end
end
for _,unitname in pairs(remove)do
DetectedUnits[unitname]=nil
end
local DetectedGroups={}
local DetectedStatics={}
local RecceGroups={}
for unitname,_unit in pairs(DetectedUnits)do
local unit=_unit
if unit:IsInstanceOf("UNIT")then
local group=unit:GetGroup()
if group then
local groupname=group:GetName()
DetectedGroups[groupname]=group
RecceGroups[groupname]=RecceDetecting[unitname]
end
else
if self.detectStatics then
DetectedStatics[unitname]=unit
RecceGroups[unitname]=RecceDetecting[unitname]
end
end
end
self:CreateDetectedItems(DetectedGroups,DetectedStatics,RecceGroups)
if self.clusteranalysis then
self:PaintPicture()
end
return self
end
function INTEL:_UpdateContact(Contact)
if Contact.isStatic then
else
if Contact.group and Contact.group:IsAlive()then
Contact.Tdetected=timer.getAbsTime()
Contact.position=Contact.group:GetCoordinate()
Contact.velocity=Contact.group:GetVelocityVec3()
Contact.speed=Contact.group:GetVelocityMPS()
if Contact.group:IsAir()then
Contact.altitude=Contact.group:GetAltitude()
local oldheading=Contact.heading or 1
local newheading=Contact.group:GetHeading()
if newheading==0 then newheading=1 end
local changeh=math.abs(((oldheading-newheading)+360)%360)
Contact.heading=newheading
if changeh>10 then
Contact.maneuvering=true
else
Contact.maneuvering=false
end
end
end
end
return self
end
function INTEL:_CreateContact(Positionable,RecceName)
if Positionable and Positionable:IsAlive()then
local item={}
if Positionable:IsInstanceOf("GROUP")then
local group=Positionable
item.groupname=group:GetName()
item.group=group
item.Tdetected=timer.getAbsTime()
item.typename=group:GetTypeName()
item.attribute=group:GetAttribute()
item.category=group:GetCategory()
item.categoryname=group:GetCategoryName()
item.threatlevel=group:GetThreatLevel()
item.position=group:GetCoordinate()
item.velocity=group:GetVelocityVec3()
item.speed=group:GetVelocityMPS()
item.recce=RecceName
item.isground=group:IsGround()or false
item.isship=group:IsShip()or false
item.isStatic=false
if group:IsAir()then
item.platform=group:GetNatoReportingName()
item.heading=group:GetHeading()
item.maneuvering=false
item.altitude=group:GetAltitude()
else
item.platform="Unknown"
item.altitude=group:GetAltitude(true)
end
if item.category==Group.Category.AIRPLANE or item.category==Group.Category.HELICOPTER then
item.ctype=INTEL.Ctype.AIRCRAFT
elseif item.category==Group.Category.GROUND or item.category==Group.Category.TRAIN then
item.ctype=INTEL.Ctype.GROUND
elseif item.category==Group.Category.SHIP then
item.ctype=INTEL.Ctype.NAVAL
end
return item
elseif Positionable:IsInstanceOf("STATIC")then
local static=Positionable
item.groupname=static:GetName()
item.group=static
item.Tdetected=timer.getAbsTime()
item.typename=static:GetTypeName()or"Unknown"
item.attribute="Static"
item.category=3
item.categoryname=static:GetCategoryName()or"Unknown"
item.threatlevel=static:GetThreatLevel()or 0
item.position=static:GetCoordinate()
item.velocity=static:GetVelocityVec3()
item.speed=0
item.recce=RecceName
item.isground=true
item.isship=false
item.isStatic=true
item.ctype=INTEL.Ctype.STRUCTURE
return item
else
self:E(self.lid..string.format("ERROR: object needs to be a GROUP or STATIC!"))
end
end
return nil
end
function INTEL:CreateDetectedItems(DetectedGroups,DetectedStatics,RecceDetecting)
self:F({RecceDetecting=RecceDetecting})
local Tnow=timer.getAbsTime()
for groupname,_group in pairs(DetectedGroups)do
local group=_group
self:KnowObject(group,RecceDetecting[groupname])
end
for staticname,_static in pairs(DetectedStatics)do
local static=_static
self:KnowObject(static,RecceDetecting[staticname])
end
for i=#self.Contacts,1,-1 do
local item=self.Contacts[i]
if self:_CheckContactLost(item)then
self:LostContact(item)
self:RemoveContact(item)
end
end
return self
end
function INTEL:GetDetectedUnits(Unit,DetectedUnits,RecceDetecting,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
local reccename=Unit:GetName()
local detectedtargets=Unit:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)
for DetectionObjectID,Detection in pairs(detectedtargets or{})do
local DetectedObject=Detection.object
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
local status,name=pcall(
function()
local name=DetectedObject:getName()
return name
end)
if status then
local unit=UNIT:FindByName(name)
if unit and unit:IsAlive()then
local DetectionAccepted=true
if self.RadarAcceptRange then
local reccecoord=Unit:GetCoordinate()
local coord=unit:GetCoordinate()
local dist=math.floor(coord:Get2DDistance(reccecoord)/1000)
if dist>self.RadarAcceptRangeKilometers then DetectionAccepted=false end
end
if self.RadarBlur then
local reccecoord=Unit:GetCoordinate()
local coord=unit:GetCoordinate()
local dist=math.floor(coord:Get2DDistance(reccecoord)/1000)
local AGL=unit:GetAltitude(true)
local minheight=self.RadarBlurMinHeight or 250
local thresheight=self.RadarBlurThresHeight or 90
local thresblur=self.RadarBlurThresBlur or 85
if dist<=self.RadarBlurClosing then
thresheight=(((dist*dist)/self.RadarBlurClosingSquare)*thresheight)
thresblur=(((dist*dist)/self.RadarBlurClosingSquare)*thresblur)
end
local fheight=math.floor(math.random(1,10000)/100)
local fblur=math.floor(math.random(1,10000)/100)
if fblur>thresblur then DetectionAccepted=false end
if AGL<=minheight and fheight<thresheight then DetectionAccepted=false end
if self.debug or self.verbose>1 then
MESSAGE:New("Radar Blur",10):ToLogIf(self.debug):ToAllIf(self.verbose>1)
MESSAGE:New("Unit "..name.." is at "..math.floor(AGL).."m. Distance "..math.floor(dist).."km.",10):ToLogIf(self.debug):ToAllIf(self.verbose>1)
MESSAGE:New(string.format("fheight = %d/%d | fblur = %d/%d",fheight,thresheight,fblur,thresblur),10):ToLogIf(self.debug):ToAllIf(self.verbose>1)
MESSAGE:New("Detection Accepted = "..tostring(DetectionAccepted),10):ToLogIf(self.debug):ToAllIf(self.verbose>1)
end
end
if DetectionAccepted then
DetectedUnits[name]=unit
RecceDetecting[name]=reccename
self:T(string.format("Unit %s detect by %s",name,reccename))
end
else
if self.detectStatics then
local static=STATIC:FindByName(name,false)
if static then
DetectedUnits[name]=static
RecceDetecting[name]=reccename
end
end
end
else
self:T(self.lid..string.format("WARNING: Could not get name of detected object ID=%s! Detected by %s",DetectedObject.id_,reccename))
end
end
end
end
function INTEL:onafterNewContact(From,Event,To,Contact)
self:F(self.lid..string.format("NEW contact %s",Contact.groupname))
table.insert(self.ContactsUnknown,Contact)
return self
end
function INTEL:onafterLostContact(From,Event,To,Contact)
self:F(self.lid..string.format("LOST contact %s",Contact.groupname))
table.insert(self.ContactsLost,Contact)
return self
end
function INTEL:onafterNewCluster(From,Event,To,Cluster)
self:F(self.lid..string.format("NEW cluster #%d [%s] of size %d",Cluster.index,Cluster.ctype,Cluster.size))
self:_AddCluster(Cluster)
return self
end
function INTEL:onafterLostCluster(From,Event,To,Cluster,Mission)
local text=self.lid..string.format("LOST cluster #%d [%s]",Cluster.index,Cluster.ctype)
if Mission then
local mission=Mission
text=text..string.format(" mission name=%s type=%s target=%s",mission.name,mission.type,mission:GetTargetName()or"unknown")
end
self:T(text)
return self
end
function INTEL:KnowObject(Positionable,RecceName,Tdetected)
local Tnow=timer.getAbsTime()
Tdetected=Tdetected or Tnow
if Positionable and Positionable:IsAlive()then
if Tdetected>Tnow then
self:ScheduleOnce(Tdetected-Tnow,self.KnowObject,self,Positionable,RecceName)
else
local name=Positionable:GetName()
local contact=self:GetContactByName(name)
if contact then
self:_UpdateContact(contact)
else
contact=self:_CreateContact(Positionable,RecceName)
if contact then
self:T(string.format("%s contact detected by %s",contact.groupname,RecceName or"unknown"))
self:AddContact(contact)
self:NewContact(contact)
end
end
end
end
return self
end
function INTEL:GetContactByName(groupname)
for i,_contact in pairs(self.Contacts)do
local contact=_contact
if contact.groupname==groupname then
return contact
end
end
return nil
end
function INTEL:_IsContactKnown(Contact)
for i,_contact in pairs(self.Contacts)do
local contact=_contact
if contact.groupname==Contact.groupname then
return true
end
end
return false
end
function INTEL:AddContact(Contact)
if self:_IsContactKnown(Contact)then
self:E(self.lid..string.format("WARNING: Contact %s is already in the contact table!",tostring(Contact.groupname)))
else
self:T(self.lid..string.format("Adding new Contact %s to table",tostring(Contact.groupname)))
table.insert(self.Contacts,Contact)
end
return self
end
function INTEL:RemoveContact(Contact)
for i,_contact in pairs(self.Contacts)do
local contact=_contact
if contact.groupname==Contact.groupname then
table.remove(self.Contacts,i)
end
end
return self
end
function INTEL:_CheckContactLost(Contact)
if Contact.group==nil or not Contact.group:IsAlive()then
return true
end
if Contact.isStatic then
return false
end
local dT=timer.getAbsTime()-Contact.Tdetected
local dTforget=nil
if Contact.category==Group.Category.GROUND then
dTforget=60*60*2
elseif Contact.category==Group.Category.AIRPLANE then
dTforget=60*10
elseif Contact.category==Group.Category.HELICOPTER then
dTforget=60*20
elseif Contact.category==Group.Category.SHIP then
dTforget=60*60
elseif Contact.category==Group.Category.TRAIN then
dTforget=60*60
end
if dT>dTforget then
return true
else
return false
end
end
function INTEL:PaintPicture()
self:F(self.lid.."Painting Picture!")
for _,_contact in pairs(self.ContactsLost)do
local contact=_contact
local cluster=self:GetClusterOfContact(contact)
if cluster then
self:RemoveContactFromCluster(contact,cluster)
end
end
local ClusterSet={}
for _i,_cluster in pairs(self.Clusters)do
local cluster=_cluster
if cluster.size>0 and self:ClusterCountUnits(cluster)>0 then
table.insert(ClusterSet,_cluster)
else
if cluster.marker then
cluster.marker:Remove()
end
if cluster.markerID then
COORDINATE:RemoveMark(cluster.markerID)
end
self:LostCluster(cluster,cluster.mission)
end
end
self.Clusters=ClusterSet
self:_UpdateClusterPositions()
for _,_contact in pairs(self.Contacts)do
local contact=_contact
self:T(string.format("Paint Picture: checking for %s",contact.groupname))
local currentcluster=self:GetClusterOfContact(contact)
if currentcluster then
local isconnected=self:IsContactConnectedToCluster(contact,currentcluster)
if isconnected then
else
self:RemoveContactFromCluster(contact,currentcluster)
local cluster=self:_GetClosestClusterOfContact(contact)
if cluster then
self:AddContactToCluster(contact,cluster)
else
local newcluster=self:_CreateClusterFromContact(contact)
self:NewCluster(newcluster)
end
end
else
self:T(self.lid..string.format("Paint Picture: contact %s has NO current cluster",contact.groupname))
local cluster=self:_GetClosestClusterOfContact(contact)
if cluster then
self:T(self.lid..string.format("Paint Picture: contact %s has closest cluster #%d",contact.groupname,cluster.index))
self:AddContactToCluster(contact,cluster)
else
self:T(self.lid..string.format("Paint Picture: contact %s has no closest cluster ==> Create new cluster",contact.groupname))
local newcluster=self:_CreateClusterFromContact(contact)
self:NewCluster(newcluster)
end
end
end
self:_UpdateClusterPositions()
if self.clustermarkers then
for _,_cluster in pairs(self.Clusters)do
local cluster=_cluster
if self.verbose>=1 then
BASE:I("Updating cluster marker and future position")
end
self:UpdateClusterMarker(cluster)
self:CalcClusterFuturePosition(cluster,300)
end
end
return self
end
function INTEL:_CreateCluster()
local cluster={}
cluster.index=self.clustercounter
cluster.coordinate=COORDINATE:New(0,0,0)
cluster.threatlevelSum=0
cluster.threatlevelMax=0
cluster.size=0
cluster.Contacts={}
cluster.altitude=0
self.clustercounter=self.clustercounter+1
return cluster
end
function INTEL:_CreateClusterFromContact(Contact)
local cluster=self:_CreateCluster()
self:T(self.lid..string.format("Created NEW cluster #%d with first contact %s",cluster.index,Contact.groupname))
cluster.coordinate:UpdateFromCoordinate(Contact.position)
cluster.ctype=Contact.ctype
self:AddContactToCluster(Contact,cluster)
return cluster
end
function INTEL:_AddCluster(Cluster)
table.insert(self.Clusters,Cluster)
return self
end
function INTEL:AddContactToCluster(contact,cluster)
if contact and cluster then
table.insert(cluster.Contacts,contact)
cluster.threatlevelSum=cluster.threatlevelSum+contact.threatlevel
cluster.size=cluster.size+1
self:GetClusterAltitude(cluster,true)
self:T(self.lid..string.format("Adding contact %s to cluster #%d [%s] ==> New size=%d",contact.groupname,cluster.index,cluster.ctype,cluster.size))
end
return self
end
function INTEL:RemoveContactFromCluster(contact,cluster)
if contact and cluster then
for i=#cluster.Contacts,1,-1 do
local Contact=cluster.Contacts[i]
if Contact.groupname==contact.groupname then
cluster.threatlevelSum=cluster.threatlevelSum-contact.threatlevel
cluster.size=cluster.size-1
table.remove(cluster.Contacts,i)
self:T(self.lid..string.format("Removing contact %s from cluster #%d ==> New cluster size=%d",contact.groupname,cluster.index,cluster.size))
return self
end
end
end
return self
end
function INTEL:CalcClusterThreatlevelSum(cluster)
local threatlevel=0
for _,_contact in pairs(cluster.Contacts)do
local contact=_contact
threatlevel=threatlevel+contact.threatlevel
end
cluster.threatlevelSum=threatlevel
return threatlevel
end
function INTEL:CalcClusterThreatlevelAverage(cluster)
local threatlevel=self:CalcClusterThreatlevelSum(cluster)
threatlevel=threatlevel/cluster.size
cluster.threatlevelAve=threatlevel
return threatlevel
end
function INTEL:CalcClusterThreatlevelMax(cluster)
local threatlevel=0
for _,_contact in pairs(cluster.Contacts)do
local contact=_contact
if contact.threatlevel>threatlevel then
threatlevel=contact.threatlevel
end
end
cluster.threatlevelMax=threatlevel
return threatlevel
end
function INTEL:CalcClusterDirection(cluster)
local direction=0
local speedsum=0
local n=0
for _,_contact in pairs(cluster.Contacts)do
local contact=_contact
if(not contact.isStatic)and contact.group:IsAlive()then
local speed=contact.group:GetVelocityKNOTS()
direction=direction+(contact.group:GetHeading()*speed)
n=n+1
speedsum=speedsum+speed
end
end
if n==0 then
return 0
else
return math.floor(direction/(speedsum*n))
end
end
function INTEL:CalcClusterSpeed(cluster)
local velocity=0;local n=0
for _,_contact in pairs(cluster.Contacts)do
local contact=_contact
if(not contact.isStatic)and contact.group:IsAlive()then
velocity=velocity+contact.group:GetVelocityMPS()
n=n+1
end
end
if n==0 then
return 0
else
return math.floor(velocity/n)
end
end
function INTEL:CalcClusterVelocityVec3(cluster)
local v={x=0,y=0,z=0}
for _,_contact in pairs(cluster.Contacts)do
local contact=_contact
if(not contact.isStatic)and contact.group:IsAlive()then
local vec=contact.group:GetVelocityVec3()
v.x=v.x+vec.x
v.y=v.y+vec.y
v.z=v.y+vec.z
end
end
return v
end
function INTEL:CalcClusterFuturePosition(cluster,seconds)
local p=self:GetClusterCoordinate(cluster)
local v=self:CalcClusterVelocityVec3(cluster)
local t=seconds or self.prediction
local Vec3={x=p.x+v.x*t,y=p.y+v.y*t,z=p.z+v.z*t}
local futureposition=COORDINATE:NewFromVec3(Vec3)
if self.clustermarkers and self.clusterarrows then
if cluster.markerID then
COORDINATE:RemoveMark(cluster.markerID)
end
cluster.markerID=p:ArrowToAll(futureposition,self.coalition,{1,0,0},1,{1,1,0},0.5,2,true,"Position Calc")
end
return futureposition
end
function INTEL:CheckContactInClusters(contact)
for _,_cluster in pairs(self.Clusters)do
local cluster=_cluster
for _,_contact in pairs(cluster.Contacts)do
local Contact=_contact
if Contact.groupname==contact.groupname then
return true
end
end
end
return false
end
function INTEL:IsContactConnectedToCluster(contact,cluster)
if contact.ctype~=cluster.ctype then
return false,math.huge
end
for _,_contact in pairs(cluster.Contacts)do
local Contact=_contact
if Contact.groupname~=contact.groupname or cluster.size==1 then
local dist=Contact.position:DistanceFromPointVec2(contact.position)
local airprox=true
if contact.ctype==INTEL.Ctype.AIRCRAFT then
self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,contact.altitude))
local adist=math.abs(cluster.altitude-contact.altitude)
if adist>UTILS.FeetToMeters(10000)then
airprox=false
end
end
if dist<self.clusterradius and airprox then
return true,dist
end
end
end
return false
end
function INTEL:IsContactPartOfAnyClusters(contact)
for _,_cluster in pairs(self.Clusters)do
local cluster=_cluster
if self:IsContactConnectedToCluster(contact,cluster)then
return cluster
end
end
return nil
end
function INTEL:_GetDistContactToCluster(Contact,Cluster)
local distmin=math.huge
for _,_contact in pairs(Cluster.Contacts)do
local contact=_contact
if contact.group and contact.group:IsAlive()and Contact.groupname~=contact.groupname then
local dist=Contact.position:Get2DDistance(contact.position)
if dist<distmin then
distmin=dist
end
end
end
return distmin
end
function INTEL:_GetClosestClusterOfContact(Contact)
local Cluster=nil
local distmin=self.clusterradius
if not Contact.altitude then
Contact.altitude=Contact.group:GetAltitude()
end
for _,_cluster in pairs(self.Clusters)do
local cluster=_cluster
if cluster.ctype==Contact.ctype then
local dist=self:_GetDistContactToCluster(Contact,cluster)
local airprox=true
if Contact.ctype==INTEL.Ctype.AIRCRAFT then
if not cluster.altitude then
cluster.altitude=self:GetClusterAltitude(cluster,true)
end
local adist=math.abs(cluster.altitude-Contact.altitude)
self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,Contact.altitude))
if adist>UTILS.FeetToMeters(10000)then
airprox=false
end
end
if dist<distmin and airprox then
Cluster=cluster
distmin=dist
end
end
end
return Cluster,distmin
end
function INTEL:GetClusterOfContact(contact)
for _,_cluster in pairs(self.Clusters)do
local cluster=_cluster
for _,_contact in pairs(cluster.Contacts)do
local Contact=_contact
if Contact.groupname==contact.groupname then
return cluster
end
end
end
return nil
end
function INTEL:GetClusterAltitude(Cluster,Update)
local newalt=0
local n=0
for _,_contact in pairs(Cluster.Contacts)do
local contact=_contact
if contact.altitude then
newalt=newalt+contact.altitude
n=n+1
end
end
local avgalt=0
if n>0 then
avgalt=newalt/n
end
Cluster.altitude=avgalt
self:T(string.format("Updating Cluster Altitude: %d",Cluster.altitude))
return Cluster.altitude
end
function INTEL:GetClusterCoordinate(Cluster,Update)
local x=0;local y=0;local z=0;local n=0
for _,_contact in pairs(Cluster.Contacts)do
local contact=_contact
local vec3=nil
if Update and contact.group and contact.group:IsAlive()then
vec3=contact.group:GetVec3()
end
if not vec3 then
vec3=contact.position
end
if vec3 then
x=x+vec3.x
y=y+vec3.y
z=z+vec3.z
n=n+1
end
end
if n>0 then
local Vec3={x=x/n,y=y/n,z=z/n}
Cluster.coordinate:UpdateFromVec3(Vec3)
end
return Cluster.coordinate
end
function INTEL:_CheckClusterCoordinateChanged(Cluster,Coordinate,Threshold)
Threshold=Threshold or 100
Coordinate=Coordinate or Cluster.coordinate
local a=Coordinate:GetVec3()
local b=self:GetClusterCoordinate(Cluster,true):GetVec3()
local dist=UTILS.VecDist3D(a,b)
if dist>Threshold then
return true
else
return false
end
end
function INTEL:_UpdateClusterPositions()
for _,_cluster in pairs(self.Clusters)do
local cluster=_cluster
local coord=self:GetClusterCoordinate(cluster,true)
local alt=self:GetClusterAltitude(cluster,true)
self:T(self.lid..string.format("Updating Cluster position size: %s",cluster.size))
end
return self
end
function INTEL:ContactCountUnits(Contact)
if Contact.isStatic then
if Contact.group and Contact.group:IsAlive()then
return 1
else
return 0
end
else
if Contact.group then
local n=Contact.group:CountAliveUnits()
return n
else
return 0
end
end
end
function INTEL:ClusterCountUnits(Cluster)
local unitcount=0
for _,_contact in pairs(Cluster.Contacts)do
local contact=_contact
unitcount=unitcount+self:ContactCountUnits(contact)
end
return unitcount
end
function INTEL:UpdateClusterMarker(cluster)
local unitcount=self:ClusterCountUnits(cluster)
local text=string.format("Cluster #%d: %s\nSize %d\nUnits %d\nTLsum=%d",cluster.index,cluster.ctype,cluster.size,unitcount,cluster.threatlevelSum)
if not cluster.marker then
cluster.marker=MARKER:New(cluster.coordinate,text):ToCoalition(self.coalition)
else
local refresh=false
if cluster.marker.text~=text then
cluster.marker.text=text
refresh=true
end
local coordchange=self:_CheckClusterCoordinateChanged(cluster,cluster.marker.coordinate)
if coordchange then
cluster.marker.coordinate:UpdateFromCoordinate(cluster.coordinate)
refresh=true
end
if refresh then
cluster.marker:Refresh()
end
end
return self
end
function INTEL:GetHighestThreatContact(Cluster)
local threatlevel=-1
local rcontact=nil
for _,_contact in pairs(Cluster.Contacts)do
local contact=_contact
if contact.threatlevel>threatlevel then
threatlevel=contact.threatlevel
rcontact=contact
end
end
return rcontact
end
INTEL_DLINK={
ClassName="INTEL_DLINK",
verbose=0,
lid=nil,
alias=nil,
cachetime=300,
interval=20,
contacts={},
clusters={},
contactcoords={},
}
INTEL_DLINK.version="0.0.1"
function INTEL_DLINK:New(Intels,Alias,Interval,Cachetime)
local self=BASE:Inherit(self,FSM:New())
self.intels=Intels or{}
self.contacts={}
self.clusters={}
self.contactcoords={}
if Alias then
self.alias=tostring(Alias)
else
self.alias="SPECTRE"
end
self.cachetime=Cachetime or 300
self.interval=Interval or 20
self.lid=string.format("INTEL_DLINK %s | ",self.alias)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Collect","*")
self:AddTransition("*","Collected","*")
self:AddTransition("*","Stop","Stopped")
return self
end
function INTEL_DLINK:AddIntel(Intel)
self:T(self.lid.."AddIntel")
if Intel then
table.insert(self.intels,Intel)
end
return self
end
function INTEL_DLINK:onafterStart(From,Event,To)
self:T({From,Event,To})
local text=string.format("Version %s started.",self.version)
self:I(self.lid..text)
self:__Collect(-math.random(1,10))
return self
end
function INTEL_DLINK:onbeforeCollect(From,Event,To)
self:T({From,Event,To})
self:T("Contacts Data Gathering")
local newcontacts={}
local intels=self.intels
for _,_intel in pairs(intels)do
_intel=_intel
if _intel:Is("Running")then
local ctable=_intel:GetContactTable()or{}
for _,_contact in pairs(ctable)do
local _ID=string.format("%s-%d",_contact.groupname,_contact.Tdetected)
self:T(string.format("Adding %s",_ID))
newcontacts[_ID]=_contact
end
end
end
self:T("Cleanup")
local contacttable={}
local coordtable={}
local TNow=timer.getAbsTime()
local Tcache=self.cachetime
for _ind,_contact in pairs(newcontacts)do
if TNow-_contact.Tdetected<Tcache then
if(not contacttable[_contact.groupname])or(contacttable[_contact.groupname]and contacttable[_contact.groupname].Tdetected<_contact.Tdetected)then
self:T(string.format("Adding %s",_contact.groupname))
contacttable[_contact.groupname]=_contact
table.insert(coordtable,_contact.position)
end
end
end
self:T("Clusters Data Gathering")
local newclusters={}
local intels=self.intels
for _,_intel in pairs(intels)do
_intel=_intel
if _intel:Is("Running")then
local ctable=_intel:GetClusterTable()or{}
for _,_cluster in pairs(ctable)do
local _ID=string.format("%s-%d",_intel.alias,_cluster.index)
self:T(string.format("Adding %s",_ID))
table.insert(newclusters,_cluster)
end
end
end
self.contacts=contacttable
self.contactcoords=coordtable
self.clusters=newclusters
self:__Collected(1,contacttable,newclusters)
local interv=self.interval*-1
self:__Collect(interv)
return self
end
function INTEL_DLINK:onbeforeCollected(From,Event,To,Contacts,Clusters)
self:T({From,Event,To})
return self
end
function INTEL_DLINK:onafterStop(From,Event,To)
self:T({From,Event,To})
local text=string.format("Version %s stopped.",self.version)
self:I(self.lid..text)
return self
end
function INTEL_DLINK:GetContactTable()
self:T(self.lid.."GetContactTable")
return self.contacts
end
function INTEL_DLINK:GetClusterTable()
self:T(self.lid.."GetClusterTable")
return self.clusters
end
function INTEL_DLINK:GetDetectedItemCoordinates()
self:T(self.lid.."GetDetectedItemCoordinates")
return self.contactcoords
end
LEGION={
ClassName="LEGION",
verbose=0,
lid=nil,
missionqueue={},
transportqueue={},
cohorts={},
}
LEGION.RandomAssetScore=1
LEGION.version="0.5.1"
function LEGION:New(WarehouseName,LegionName)
local self=BASE:Inherit(self,WAREHOUSE:New(WarehouseName,LegionName))
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!",WarehouseName))
return nil
end
self.lid=string.format("LEGION %s | ",self.alias)
self:SetMarker(false)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.Dead)
self:AddTransition("*","MissionRequest","*")
self:AddTransition("*","MissionCancel","*")
self:AddTransition("*","MissionAssign","*")
self:AddTransition("*","TransportRequest","*")
self:AddTransition("*","TransportCancel","*")
self:AddTransition("*","TransportAssign","*")
self:AddTransition("*","OpsOnMission","*")
self:AddTransition("*","LegionAssetReturned","*")
return self
end
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)
Mission:AddLegion(self)
if Mission.type==AUFTRAG.Type.ALERT5 then
Mission:_TargetFromObject(self:GetCoordinate())
end
table.insert(self.missionqueue,Mission)
local text=string.format("Added mission %s (type=%s). Starting at %s. Stopping at %s",
tostring(Mission.name),tostring(Mission.type),UTILS.SecondsToClock(Mission.Tstart,true),Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop,true)or"INF")
self:T(self.lid..text)
return self
end
function LEGION:RemoveMission(Mission)
for i,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.auftragsnummer==Mission.auftragsnummer then
mission:RemoveLegion(self)
table.remove(self.missionqueue,i)
break
end
end
return self
end
function LEGION:AddOpsTransport(OpsTransport)
OpsTransport:Queued()
OpsTransport:SetLegionStatus(self,AUFTRAG.Status.QUEUED)
table.insert(self.transportqueue,OpsTransport)
OpsTransport:AddLegion(self)
local text=string.format("Added Transport %s. Starting at %s-%s",
tostring(OpsTransport.uid),UTILS.SecondsToClock(OpsTransport.Tstart,true),OpsTransport.Tstop and UTILS.SecondsToClock(OpsTransport.Tstop,true)or"INF")
self:T(self.lid..text)
return self
end
function LEGION:AddCohort(Cohort)
if self:IsCohort(Cohort.name)then
self:E(self.lid..string.format("ERROR: A cohort with name %s already exists in this legion. Cohorts must have UNIQUE names!"))
else
table.insert(self.cohorts,Cohort)
end
return self
end
function LEGION:DelCohort(Cohort)
for i=#self.cohorts,1,-1 do
local cohort=self.cohorts[i]
if cohort.name==Cohort.name then
self:T(self.lid..string.format("Removing Cohort %s",tostring(cohort.name)))
table.remove(self.cohorts,i)
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)
else
if Legion:IsCohort(Cohort.name)then
self:E(self.lid..string.format("ERROR: Cohort %s is already part of new legion %s ==> CANNOT Relocate!",Cohort.name,Legion.alias))
return self
else
table.insert(Legion.cohorts,Cohort)
end
if not self:IsCohort(Cohort.name)then
self:E(self.lid..string.format("ERROR: Cohort %s is NOT part of this legion %s ==> CANNOT Relocate!",Cohort.name,self.alias))
return self
end
if self.alias==Legion.alias then
self:E(self.lid..string.format("ERROR: old legion %s is same as new legion %s ==> CANNOT Relocate!",self.alias,Legion.alias))
return self
end
Cohort:Relocate()
local mission=AUFTRAG:_NewRELOCATECOHORT(Legion,Cohort)
if false then
mission:_AddAssets(Cohort.assets)
self:I(self.lid..string.format("Relocating Cohort %s [nassets=%d] to legion %s",Cohort.name,#Cohort.assets,Legion.alias))
self:MissionAssign(mission,{self})
else
mission:AssignCohort(Cohort)
mission:SetRequiredAssets(#Cohort.assets)
if NcarriersMin and NcarriersMin>0 then
mission:SetRequiredTransport(Legion.spawnzone,NcarriersMin,NcarriersMax)
end
if TransportLegions then
for _,legion in pairs(TransportLegions)do
mission:AssignTransportLegion(legion)
end
end
mission:SetMissionRange(10000)
self:AddMission(mission)
end
end
return self
end
function LEGION:_GetCohort(CohortName)
for _,_cohort in pairs(self.cohorts)do
local cohort=_cohort
if cohort.name==CohortName then
return cohort
end
end
return nil
end
function LEGION:IsCohort(CohortName)
for _,_cohort in pairs(self.cohorts)do
local cohort=_cohort
if cohort.name==CohortName then
return true
end
end
return false
end
function LEGION:GetName()
return self.alias
end
function LEGION:_GetCohortOfAsset(Asset)
local cohort=self:_GetCohort(Asset.squadname)
return cohort
end
function LEGION:IsBrigade()
local is=self.ClassName==BRIGADE.ClassName
return is
end
function LEGION:IsAirwing()
local is=self.ClassName==AIRWING.ClassName
return is
end
function LEGION:IsFleet()
local is=self.ClassName==FLEET.ClassName
return is
end
function LEGION:onafterStart(From,Event,To)
self:GetParent(self,LEGION).onafterStart(self,From,Event,To)
self:T3(self.lid..string.format("Starting LEGION v%s",LEGION.version))
end
function LEGION:CheckMissionQueue()
local Nmissions=#self.missionqueue
if Nmissions==0 then
return nil
end
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:IsNotOver()and mission:IsReadyToCancel()then
mission:Cancel()
end
end
if self:IsAirwing()then
if self:IsRunwayOperational()==false then
return nil
end
local airboss=self.airboss
if airboss then
if not airboss:IsIdle()then
return nil
end
end
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.Tstart<taskB.Tstart)
end
table.sort(self.missionqueue,_sort)
local vip=math.huge
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.importance and mission.importance<vip then
vip=mission.importance
end
end
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
local reinforce=false
if mission:IsExecuting()and mission.reinforce and mission.reinforce>0 then
local N=mission.Nassigned-mission.Ndead
if N<mission.NassetsMin then
reinforce=true
end
self:T(self.lid..string.format("Checking Reinforcement Nreinf=%d, Nops=%d, Nassigned=%d, Ndead=%d, Nmin=%d ==> Reinforce=%s",
mission.reinforce,N,mission.Nassigned,mission.Ndead,mission.NassetsMin,tostring(reinforce)))
end
if(mission:IsQueued(self)or reinforce)and mission:IsReadyToGo()and(mission.importance==nil or mission.importance<=vip)then
local recruited,assets,legions=self:RecruitAssetsForMission(mission)
if recruited then
local EscortAvail=self:RecruitAssetsForEscort(mission,assets)
local TransportAvail=true
if EscortAvail then
local Transport=nil
if mission.NcarriersMin then
local Legions=mission.transportLegions or{self}
TransportAvail,Transport=self:AssignAssetsForTransport(Legions,assets,mission.NcarriersMin,mission.NcarriersMax,mission.transportDeployZone,mission.transportDisembarkZone,mission.carrierCategories,mission.carrierAttributes,mission.carrierProperties)
end
if TransportAvail and Transport then
mission.opstransport=Transport
end
end
if EscortAvail and TransportAvail then
self:MissionRequest(mission,assets)
if reinforce then
mission.reinforce=mission.reinforce-#assets
self:I(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce))
end
return true
else
LEGION.UnRecruitAssets(assets,mission)
end
end
end
end
return nil
end
function LEGION:CheckTransportQueue()
local Ntransports=#self.transportqueue
if Ntransports==0 then
return nil
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.Tstart<taskB.Tstart)
end
table.sort(self.transportqueue,_sort)
local vip=math.huge
for _,_transport in pairs(self.transportqueue)do
local transport=_transport
if transport.importance and transport.importance<vip then
vip=transport.importance
end
end
for _,_transport in pairs(self.transportqueue)do
local transport=_transport
if transport:IsQueued(self)and transport:IsReadyToGo()and(transport.importance==nil or transport.importance<=vip)then
local recruited,assets,_=self:RecruitAssetsForTransport(transport)
if recruited then
for _,_asset in pairs(assets)do
local asset=_asset
transport:AddAsset(asset)
end
self:TransportRequest(transport)
return true
end
end
end
return nil
end
function LEGION:onafterMissionAssign(From,Event,To,Mission,Legions)
for _,_Legion in pairs(Legions)do
local Legion=_Legion
self:T(self.lid..string.format("Assigning mission %s (%s) to legion %s",Mission.name,Mission.type,Legion.alias))
Legion:AddMission(Mission)
Legion:MissionRequest(Mission)
end
end
function LEGION:_AddRequest(AssetDescriptor,AssetDescriptorValue,nAsset,Prio,Assignment)
nAsset=nAsset or 1
Prio=Prio or 50
self.queueid=self.queueid+1
local request={
uid=self.queueid,
prio=Prio,
warehouse=self,
assetdesc=AssetDescriptor,
assetdescval=AssetDescriptorValue,
nasset=nAsset,
transporttype=WAREHOUSE.TransportType.SELFPROPELLED,
ntransport=0,
assignment=tostring(Assignment),
airbase=self:GetAirbase(),
category=self:GetAirbaseCategory(),
ndelivered=0,
ntransporthome=0,
assets={},
toself=true,
}
table.insert(self.queue,request)
local descval="assetlist"
if request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST then
else
descval=tostring(request.assetdescval)
end
local text=string.format("Warehouse %s: New request from warehouse %s.\nDescriptor %s=%s, #assets=%s; Transport=%s, #transports=%s.",
self.alias,self.alias,request.assetdesc,descval,tostring(request.nasset),request.transporttype,tostring(request.ntransport))
self:_DebugMessage(text,5)
return request
end
function LEGION:onafterMissionRequest(From,Event,To,Mission,Assets)
self:T(self.lid..string.format("MissionRequest for mission %s [%s]",Mission:GetName(),Mission:GetType()))
Assets=Assets or Mission.assets
Mission:Requested()
Mission:SetLegionStatus(self,AUFTRAG.Status.REQUESTED)
local Assetlist={}
for _,_asset in pairs(Assets)do
local asset=_asset
if asset.wid==self.uid then
if asset.spawned then
if asset.flightgroup and not asset.flightgroup:IsMissionInQueue(Mission)then
asset.flightgroup:AddMission(Mission)
local currM=asset.flightgroup:GetMissionCurrent()
if currM then
local cancel=false
local pause=false
if(currM.type==AUFTRAG.Type.GCICAP or currM.type==AUFTRAG.Type.PATROLRACETRACK)and Mission.type==AUFTRAG.Type.INTERCEPT then
pause=true
elseif(currM.type==AUFTRAG.Type.ONGUARD or currM.type==AUFTRAG.Type.PATROLZONE)and(Mission.type==AUFTRAG.Type.ARTY or Mission.type==AUFTRAG.Type.GROUNDATTACK)then
pause=true
elseif currM.type==AUFTRAG.Type.NOTHING then
pause=true
end
if currM.type==AUFTRAG.Type.ALERT5 then
cancel=true
end
if Mission.type==AUFTRAG.Type.RELOCATECOHORT then
cancel=true
local request=currM:_GetRequest(self)
if request then
self:T2(self.lid.."Removing group from cargoset")
request.cargogroupset:Remove(asset.spawngroupname,true)
else
self:E(self.lid.."ERROR: no request for spawned asset!")
end
end
if cancel then
self:T(self.lid..string.format("Cancel current mission %s [%s] to send group on mission %s [%s]",currM.name,currM.type,Mission.name,Mission.type))
asset.flightgroup:MissionCancel(currM)
elseif pause then
self:T(self.lid..string.format("Pausing current mission %s [%s] to send group on mission %s [%s]",currM.name,currM.type,Mission.name,Mission.type))
asset.flightgroup:PauseMission()
end
asset.isReserved=false
end
Mission:AddAsset(asset)
self:__OpsOnMission(2,asset.flightgroup,Mission)
else
self:T(self.lid.."ERROR: OPSGROUP for asset does NOT exist but it seems to be SPAWNED (asset.spawned=true)!")
end
else
table.insert(Assetlist,asset)
end
end
end
if#Assetlist>0 then
for i,_asset in pairs(Assetlist)do
local asset=_asset
asset.requested=true
if asset.spawned then
asset.requested=false
end
asset.isReserved=false
if Mission.missionTask then
asset.missionTask=Mission.missionTask
end
if Mission.type==AUFTRAG.Type.ALERT5 then
asset.takeoffType=COORDINATE.WaypointType.TakeOffParking
end
Mission:AddAsset(asset)
end
local assignment=string.format("Mission-%d",Mission.auftragsnummer)
local request=self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST,Assetlist,#Assetlist,Mission.prio,assignment)
self:T(self.lid..string.format("Added request=%d for Nasssets=%d",request.uid,#Assetlist))
Mission:_SetRequestID(self,self.queueid)
self:T(self.lid..string.format("Mission %s [%s] got Request ID=%d",Mission:GetName(),Mission:GetType(),self.queueid))
if request then
if self:IsShip()then
self:T(self.lid.."Warehouse physical structure is SHIP. Requestes assets will be late activated!")
request.lateActivation=true
end
end
end
end
function LEGION:onafterTransportAssign(From,Event,To,Transport,Legions)
for _,_Legion in pairs(Legions)do
local Legion=_Legion
self:T(self.lid..string.format("Assigning transport %d to legion %s",Transport.uid,Legion.alias))
Legion:AddOpsTransport(Transport)
Legion:TransportRequest(Transport)
end
end
function LEGION:onafterTransportRequest(From,Event,To,OpsTransport)
local AssetList={}
for i,_asset in pairs(OpsTransport.assets)do
local asset=_asset
if asset.wid==self.uid then
asset.requested=true
asset.isReserved=false
asset.missionTask=ENUMS.MissionTask.TRANSPORT
table.insert(AssetList,asset)
end
end
if#AssetList>0 then
OpsTransport:Requested()
OpsTransport:SetLegionStatus(self,OPSTRANSPORT.Status.REQUESTED)
local assignment=string.format("Transport-%d",OpsTransport.uid)
self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST,AssetList,#AssetList,OpsTransport.prio,assignment)
OpsTransport.requestID[self.alias]=self.queueid
end
end
function LEGION:onafterTransportCancel(From,Event,To,Transport)
self:T(self.lid..string.format("Cancel transport UID=%d",Transport.uid))
Transport:SetLegionStatus(self,OPSTRANSPORT.Status.CANCELLED)
for i=#Transport.assets,1,-1 do
local asset=Transport.assets[i]
if asset.wid==self.uid then
local opsgroup=asset.flightgroup
if opsgroup then
opsgroup:TransportCancel(Transport)
end
local cargos=Transport:GetCargoOpsGroups(false)
for _,_cargo in pairs(cargos)do
local cargo=_cargo
cargo:_DelMyLift(Transport)
local legion=cargo.legion
if legion then
legion:T(self.lid..string.format("Adding cargo group %s back to legion",cargo:GetName()))
legion:__AddAsset(0.1,cargo.group,1)
end
end
Transport:DelAsset(asset)
asset.requested=nil
asset.isReserved=nil
end
end
if Transport.requestID[self.alias]then
self:_DeleteQueueItemByID(Transport.requestID[self.alias],self.queue)
end
end
function LEGION:onafterMissionCancel(From,Event,To,Mission)
self:T(self.lid..string.format("Cancel mission %s",Mission.name))
Mission:SetLegionStatus(self,AUFTRAG.Status.CANCELLED)
for i=#Mission.assets,1,-1 do
local asset=Mission.assets[i]
if asset.wid==self.uid then
local opsgroup=asset.flightgroup
if opsgroup then
opsgroup:MissionCancel(Mission)
end
Mission:DelAsset(asset)
asset.requested=nil
asset.isReserved=nil
end
end
local requestID=Mission:_GetRequestID(self)
if requestID then
self:_DeleteQueueItemByID(requestID,self.queue)
end
end
function LEGION:onafterOpsOnMission(From,Event,To,OpsGroup,Mission)
self:T2(self.lid..string.format("Group %s on mission %s [%s]",OpsGroup:GetName(),Mission:GetName(),Mission:GetType()))
if self:IsAirwing()then
self:FlightOnMission(OpsGroup,Mission)
elseif self:IsBrigade()then
self:ArmyOnMission(OpsGroup,Mission)
else
self:NavyOnMission(OpsGroup,Mission)
end
if self:IsBrigade()and self:IsShip()then
OpsGroup:PauseMission()
self.warehouseOpsGroup:Load(OpsGroup,self.warehouseOpsElement)
end
if self.chief then
self.chief:OpsOnMission(OpsGroup,Mission)
end
if self.commander then
self.commander:OpsOnMission(OpsGroup,Mission)
end
end
function LEGION:onafterNewAsset(From,Event,To,asset,assignment)
self:GetParent(self,LEGION).onafterNewAsset(self,From,Event,To,asset,assignment)
local text=string.format("New asset %s with assignment %s and request assignment %s",asset.spawngroupname,tostring(asset.assignment),tostring(assignment))
self:T(self.lid..text)
local cohort=self:_GetCohort(asset.assignment)
if cohort then
if asset.assignment==assignment then
local nunits=#asset.template.units
local text=string.format("Adding asset to cohort %s: assignment=%s, type=%s, attribute=%s, nunits=%d ngroup=%s",cohort.name,assignment,asset.unittype,asset.attribute,nunits,tostring(cohort.ngrouping))
self:T(self.lid..text)
if cohort.ngrouping then
local template=asset.template
local N=math.max(#template.units,cohort.ngrouping)
asset.weight=0
asset.cargobaytot=0
for i=1,N do
local unit=template.units[i]
if i>nunits then
table.insert(template.units,UTILS.DeepCopy(template.units[1]))
asset.cargobaytot=asset.cargobaytot+asset.cargobay[1]
asset.weight=asset.weight+asset.weights[1]
template.units[i].x=template.units[1].x+5*(i-nunits)
template.units[i].y=template.units[1].y+5*(i-nunits)
else
if i<=cohort.ngrouping then
asset.weight=asset.weight+asset.weights[i]
asset.cargobaytot=asset.cargobaytot+asset.cargobay[i]
end
end
if i>cohort.ngrouping then
template.units[i]=nil
end
end
asset.nunits=cohort.ngrouping
self:T(self.lid..string.format("After regrouping: Nunits=%d, weight=%.1f cargobaytot=%.1f kg",#asset.template.units,asset.weight,asset.cargobaytot))
end
asset.takeoffType=cohort.takeoffType~=nil and cohort.takeoffType or self.takeoffType
asset.parkingIDs=cohort.parkingIDs
cohort:GetCallsign(asset)
cohort:GetModex(asset)
asset.spawngroupname=string.format("%s_AID-%d",cohort.name,asset.uid)
cohort:AddAsset(asset)
else
self:T(self.lid..string.format("Asset returned to legion ==> calling LegionAssetReturned event"))
asset.takeoffType=cohort.takeoffType
self:LegionAssetReturned(cohort,asset)
end
end
end
function LEGION:onafterLegionAssetReturned(From,Event,To,Cohort,Asset)
self:T(self.lid..string.format("Asset %s from Cohort %s returned! asset.assignment=\"%s\"",Asset.spawngroupname,Cohort.name,tostring(Asset.assignment)))
if Asset.flightgroup and not Asset.flightgroup:IsStopped()then
Asset.flightgroup:Stop()
end
if Asset.flightgroup:IsFlightgroup()then
self:ReturnPayloadFromAsset(Asset)
end
if Asset.tacan then
Cohort:ReturnTacan(Asset.tacan)
end
Asset.Treturned=timer.getAbsTime()
end
function LEGION:onafterAssetSpawned(From,Event,To,group,asset,request)
self:T({From,Event,To,group:GetName(),asset.assignment,request.assignment})
self:GetParent(self,LEGION).onafterAssetSpawned(self,From,Event,To,group,asset,request)
local cohort=self:_GetCohortOfAsset(asset)
if cohort then
self:T(self.lid..string.format("Cohort asset spawned %s",asset.spawngroupname))
local flightgroup=self:_CreateFlightGroup(asset)
asset.flightgroup=flightgroup
asset.requested=nil
asset.Treturned=nil
local Tacan=cohort:FetchTacan()
if Tacan then
asset.tacan=Tacan
flightgroup:SwitchTACAN(Tacan,Morse,UnitName,Band)
end
local radioFreq,radioModu=cohort:GetRadio()
if radioFreq then
flightgroup:SwitchRadio(radioFreq,radioModu)
end
if cohort.fuellow then
flightgroup:SetFuelLowThreshold(cohort.fuellow)
end
if cohort.fuellowRefuel then
flightgroup:SetFuelLowRefuel(cohort.fuellowRefuel)
end
local assignment=request.assignment
if self:IsFleet()then
flightgroup:SetPathfinding(self.pathfinding)
end
if string.find(assignment,"Mission-")then
local uid=UTILS.Split(assignment,"-")[2]
local mission=self:GetMissionByID(uid)
local despawnLanding=cohort.despawnAfterLanding~=nil and cohort.despawnAfterLanding or self.despawnAfterLanding
if despawnLanding then
flightgroup:SetDespawnAfterLanding()
end
local despawnHolding=cohort.despawnAfterHolding~=nil and cohort.despawnAfterHolding or self.despawnAfterHolding
if despawnHolding then
flightgroup:SetDespawnAfterHolding()
end
if mission then
if Tacan then
end
flightgroup:AddMission(mission)
if self:IsBrigade()or self:IsFleet()then
flightgroup:SetReturnOnOutOfAmmo()
end
self:__OpsOnMission(5,flightgroup,mission)
else
if Tacan then
end
end
local chief=self.chief or(self.commander and self.commander.chief or nil)
if chief then
self:T(self.lid..string.format("Adding group %s to agents of CHIEF",group:GetName()))
chief.detectionset:AddGroup(asset.flightgroup.group)
end
elseif string.find(assignment,"Transport-")then
local uid=UTILS.Split(assignment,"-")[2]
local transport=self:GetTransportByID(uid)
if transport then
flightgroup:AddOpsTransport(transport)
end
end
end
end
function LEGION:onafterAssetDead(From,Event,To,asset,request)
self:GetParent(self,LEGION).onafterAssetDead(self,From,Event,To,asset,request)
if self.commander and self.commander.chief then
self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname})
end
self:DelAsset(asset)
end
function LEGION:onafterDestroyed(From,Event,To)
self:T(self.lid.."Legion warehouse destroyed!")
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
mission:Cancel()
end
for _,_cohort in pairs(self.cohorts)do
local cohort=_cohort
cohort:Stop()
end
self:GetParent(self,LEGION).onafterDestroyed(self,From,Event,To)
end
function LEGION:onafterRequest(From,Event,To,Request)
if Request.toself then
local assets=Request.cargoassets
local Mission=self:GetMissionByID(Request.assignment)
if Mission and assets then
for _,_asset in pairs(assets)do
local asset=_asset
end
end
end
self:GetParent(self,LEGION).onafterRequest(self,From,Event,To,Request)
end
function LEGION:onafterSelfRequest(From,Event,To,groupset,request)
self:GetParent(self,LEGION).onafterSelfRequest(self,From,Event,To,groupset,request)
local mission=self:GetMissionByID(request.assignment)
for _,_asset in pairs(request.assets)do
local asset=_asset
end
for _,_group in pairs(groupset:GetSet())do
local group=_group
end
end
function LEGION:onafterRequestSpawned(From,Event,To,Request,CargoGroupSet,TransportGroupSet)
self:GetParent(self,LEGION).onafterRequestSpawned(self,From,Event,To,Request,CargoGroupSet,TransportGroupSet)
end
function LEGION:onafterCaptured(From,Event,To,Coalition,Country)
self:GetParent(self,LEGION).onafterCaptured(self,From,Event,To,Coalition,Country)
if self.chief then
self.chief.commander:LegionLost(self,Coalition,Country)
self.chief:LegionLost(self,Coalition,Country)
self.chief:RemoveLegion(self)
elseif self.commander then
self.commander:LegionLost(self,Coalition,Country)
self.commander:RemoveLegion(self)
end
end
function LEGION:_CreateFlightGroup(asset)
local opsgroup=nil
if self:IsAirwing()then
opsgroup=FLIGHTGROUP:New(asset.spawngroupname)
elseif self:IsBrigade()then
opsgroup=ARMYGROUP:New(asset.spawngroupname)
elseif self:IsFleet()then
opsgroup=NAVYGROUP:New(asset.spawngroupname)
else
self:E(self.lid.."ERROR: not airwing or brigade!")
end
opsgroup:_SetLegion(self)
opsgroup.cohort=self:_GetCohortOfAsset(asset)
opsgroup.homebase=self.airbase
opsgroup.destbase=self.airbase
opsgroup.homezone=self.spawnzone
if opsgroup.cohort.weaponData then
local text="Weapon data for group:"
opsgroup.weaponData=opsgroup.weaponData or{}
for bittype,_weapondata in pairs(opsgroup.cohort.weaponData)do
local weapondata=_weapondata
opsgroup.weaponData[bittype]=UTILS.DeepCopy(weapondata)
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bittype,weapondata.RangeMin/1000,weapondata.RangeMax/1000)
end
self:T3(self.lid..text)
end
return opsgroup
end
function LEGION:_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)
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
MissionTypes={MissionTypes}
end
else
MissionTypes=AUFTRAG.Type
end
if asset.flightgroup and asset.flightgroup:IsAlive()then
for _,_mission in pairs(asset.flightgroup.missionqueue or{})do
local mission=_mission
if mission:IsNotOver()then
local status=mission:GetGroupStatus(asset.flightgroup)
if(status==AUFTRAG.GroupStatus.STARTED or status==AUFTRAG.GroupStatus.EXECUTING)and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then
return true
end
end
end
end
return false
end
function LEGION:GetAssetCurrentMission(asset)
if asset.flightgroup then
return asset.flightgroup:GetMissionCurrent()
end
return nil
end
function LEGION:CountPayloadsInStock(MissionTypes,UnitTypes,Payloads)
if MissionTypes then
if type(MissionTypes)=="string"then
MissionTypes={MissionTypes}
end
end
if UnitTypes then
if type(UnitTypes)=="string"then
UnitTypes={UnitTypes}
end
end
local function _checkUnitTypes(payload)
if UnitTypes then
for _,unittype in pairs(UnitTypes)do
if unittype==payload.aircrafttype then
return true
end
end
else
return true
end
return false
end
local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads)do
if Payload.uid==payload.uid then
return true
end
end
else
return nil
end
return false
end
local n=0
for _,_payload in pairs(self.payloads or{})do
local payload=_payload
for _,MissionType in pairs(MissionTypes)do
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType,payload.capabilities)
local goforit=specialpayload or(specialpayload==nil and compatible)
if goforit and _checkUnitTypes(payload)then
if payload.unlimited then
return 999
else
n=n+payload.navail
end
end
end
end
return n
end
function LEGION:CountMissionsInQueue(MissionTypes)
MissionTypes=MissionTypes or AUFTRAG.Type
local N=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:IsNotOver()and AUFTRAG.CheckMissionType(mission.type,MissionTypes)then
N=N+1
end
end
return N
end
function LEGION:CountAssets(InStock,MissionTypes,Attributes)
local N=0
for _,_cohort in pairs(self.cohorts)do
local cohort=_cohort
N=N+cohort:CountAssets(InStock,MissionTypes,Attributes)
end
return N
end
function LEGION:GetOpsGroups(MissionTypes,Attributes)
local setLegion=SET_OPSGROUP:New()
for _,_cohort in pairs(self.cohorts)do
local cohort=_cohort
local setCohort=cohort:GetOpsGroups(MissionTypes,Attributes)
self:T2(self.lid..string.format("Found %d opsgroups of cohort %s",setCohort:Count(),cohort.name))
setLegion:AddSet(setCohort)
end
return setLegion
end
function LEGION:CountAssetsWithPayloadsInStock(Payloads,MissionTypes,Attributes)
local N=0
local Npayloads={}
for _,_cohort in pairs(self.cohorts)do
local cohort=_cohort
if Npayloads[cohort.aircrafttype]==nil then
Npayloads[cohort.aircrafttype]=self:CountPayloadsInStock(MissionTypes,cohort.aircrafttype,Payloads)
self:T3(self.lid..string.format("Got Npayloads=%d for type=%s",Npayloads[cohort.aircrafttype],cohort.aircrafttype))
end
end
for _,_cohort in pairs(self.cohorts)do
local cohort=_cohort
local n=cohort:CountAssets(true,MissionTypes,Attributes)
local p=Npayloads[cohort.aircrafttype]or 0
local m=math.min(n,p)
N=N+m
Npayloads[cohort.aircrafttype]=Npayloads[cohort.aircrafttype]-m
end
return N
end
function LEGION:CountAssetsOnMission(MissionTypes,Cohort)
local Nq=0
local Np=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if AUFTRAG.CheckMissionType(mission.type,MissionTypes or AUFTRAG.Type)then
for _,_asset in pairs(mission.assets or{})do
local asset=_asset
if asset.wid==self.uid then
if Cohort==nil or Cohort.name==asset.squadname then
local request,isqueued=self:GetRequestByID(mission.requestID[self.alias])
if isqueued then
Nq=Nq+1
else
Np=Np+1
end
end
end
end
end
end
return Np+Nq,Np,Nq
end
function LEGION:GetAssetsOnMission(MissionTypes)
local assets={}
local Np=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if AUFTRAG.CheckMissionType(mission.type,MissionTypes)then
for _,_asset in pairs(mission.assets or{})do
local asset=_asset
if asset.wid==self.uid then
table.insert(assets,asset)
end
end
end
end
return assets
end
function LEGION:GetAircraftTypes(onlyactive,cohorts)
local unittypes={}
for _,_cohort in pairs(cohorts or self.cohorts)do
local cohort=_cohort
if(not onlyactive)or cohort:IsOnDuty()then
local gotit=false
for _,unittype in pairs(unittypes)do
if cohort.aircrafttype==unittype then
gotit=true
break
end
end
if not gotit then
table.insert(unittypes,cohort.aircrafttype)
end
end
end
return unittypes
end
function LEGION:_CountPayloads(MissionType,Cohorts,Payloads)
local Npayloads={}
for _,_cohort in pairs(Cohorts)do
local cohort=_cohort
if Npayloads[cohort.aircrafttype]==nil then
Npayloads[cohort.aircrafttype]=cohort.legion:IsAirwing()and self:CountPayloadsInStock(MissionType,cohort.aircrafttype,Payloads)or 999
self:T2(self.lid..string.format("Got N=%d payloads for mission type=%s and unit type=%s",Npayloads[cohort.aircrafttype],MissionType,cohort.aircrafttype))
end
end
return Npayloads
end
function LEGION:RecruitAssetsForMission(Mission)
local NreqMin,NreqMax=Mission:GetRequiredAssets()
local TargetVec2=Mission:GetTargetVec2()
local Payloads=Mission.payloads
local MaxWeight=nil
if Mission.NcarriersMin then
local legions={self}
local cohorts=self.cohorts
if Mission.transportLegions or Mission.transportCohorts then
legions=Mission.transportLegions
cohorts=Mission.transportCohorts
end
local Cohorts=LEGION._GetCohorts(legions,cohorts)
local transportcohorts={}
for _,_cohort in pairs(Cohorts)do
local cohort=_cohort
local can=LEGION._CohortCan(cohort,AUFTRAG.Type.OPSTRANSPORT,Mission.carrierCategories,Mission.carrierAttributes,Mission.carrierProperties,nil,TargetVec2)
if can and(MaxWeight==nil or cohort.cargobayLimit>MaxWeight)then
MaxWeight=cohort.cargobayLimit
end
end
self:T(self.lid..string.format("Largest cargo bay available=%.1f",MaxWeight or 0))
end
local legions={self}
local cohorts=self.cohorts
if Mission.specialLegions or Mission.specialCohorts then
legions=Mission.specialLegions
cohorts=Mission.specialCohorts
end
local Cohorts=LEGION._GetCohorts(legions,cohorts,Operation,OpsQueue)
local recruited,assets,legions=LEGION.RecruitCohortAssets(Cohorts,Mission.type,Mission.alert5MissionType,NreqMin,NreqMax,TargetVec2,Payloads,
Mission.engageRange,Mission.refuelSystem,nil,nil,MaxWeight,nil,Mission.attributes,Mission.properties,{Mission.engageWeaponType})
return recruited,assets,legions
end
function LEGION:RecruitAssetsForTransport(Transport)
local cargoOpsGroups=Transport:GetCargoOpsGroups(false)
local weightGroup=0
local TotalWeight=nil
if#cargoOpsGroups>0 then
TotalWeight=0
for _,_opsgroup in pairs(cargoOpsGroups)do
local opsgroup=_opsgroup
local weight=opsgroup:GetWeightTotal()
if weight>weightGroup then
weightGroup=weight
end
TotalWeight=TotalWeight+weight
end
else
return false
end
local TargetVec2=Transport:GetDeployZone():GetVec2()
local NreqMin,NreqMax=Transport:GetRequiredCarriers()
local recruited,assets,legions=LEGION.RecruitCohortAssets(self.cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NreqMin,NreqMax,TargetVec2,nil,nil,nil,weightGroup,TotalWeight)
return recruited,assets,legions
end
function LEGION:RecruitAssetsForEscort(Mission,Assets)
if Mission.NescortMin and Mission.NescortMax and(Mission.NescortMin>0 or Mission.NescortMax>0)then
self:T(self.lid..string.format("Requested escort for mission %s [%s]. Required assets=%d-%d",Mission:GetName(),Mission:GetType(),Mission.NescortMin,Mission.NescortMax))
local Cohorts={}
for _,_legion in pairs(Mission.escortLegions or{})do
local legion=_legion
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
table.insert(Cohorts,cohort)
end
end
for _,_cohort in pairs(Mission.escortCohorts or{})do
local cohort=_cohort
table.insert(Cohorts,cohort)
end
if#Cohorts==0 then
Cohorts=self.cohorts
end
local assigned=LEGION.AssignAssetsForEscort(self,Cohorts,Assets,Mission.NescortMin,Mission.NescortMax,Mission.escortMissionType,Mission.escortTargetTypes)
return assigned
end
return true
end
function LEGION._GetCohorts(Legions,Cohorts,Operation,OpsQueue)
OpsQueue=OpsQueue or{}
local function CheckOperation(LegionOrCohort)
if#OpsQueue==0 then
return true
end
local isAvail=true
if Operation then
isAvail=false
end
for _,_operation in pairs(OpsQueue)do
local operation=_operation
local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort)
if isOps and operation:IsRunning()then
isAvail=false
if Operation==nil then
return false
else
if Operation.uid==operation.uid then
return true
end
end
end
end
return isAvail
end
local cohorts={}
if(Legions and#Legions>0)or(Cohorts and#Cohorts>0)then
for _,_legion in pairs(Legions or{})do
local legion=_legion
local Runway=legion:IsAirwing()and legion:IsRunwayOperational()or true
if legion:IsRunning()and Runway then
for _,_cohort in pairs(legion.cohorts)do
local cohort=_cohort
if(CheckOperation(cohort.legion)or CheckOperation(cohort))and not UTILS.IsInTable(cohorts,cohort,"name")then
table.insert(cohorts,cohort)
end
end
end
end
for _,_cohort in pairs(Cohorts or{})do
local cohort=_cohort
if CheckOperation(cohort)and not UTILS.IsInTable(cohorts,cohort,"name")then
table.insert(cohorts,cohort)
end
end
end
return cohorts
end
function LEGION._CohortCan(Cohort,MissionType,Categories,Attributes,Properties,WeaponTypes,TargetVec2,RangeMax,RefuelSystem,CargoWeight,MaxWeight)
local function CheckCategory(_cohort)
local cohort=_cohort
if Categories and#Categories>0 then
for _,category in pairs(Categories)do
if category==cohort.category then
return true
end
end
else
return true
end
end
local function CheckAttribute(_cohort)
local cohort=_cohort
if Attributes and#Attributes>0 then
for _,attribute in pairs(Attributes)do
if attribute==cohort.attribute then
return true
end
end
else
return true
end
end
local function CheckProperty(_cohort)
local cohort=_cohort
if Properties and#Properties>0 then
for _,Property in pairs(Properties)do
for property,value in pairs(cohort.properties)do
if Property==property then
return true
end
end
end
else
return true
end
end
local function CheckWeapon(_cohort)
local cohort=_cohort
if WeaponTypes and#WeaponTypes>0 then
for _,WeaponType in pairs(WeaponTypes)do
if WeaponType==ENUMS.WeaponFlag.Auto then
return true
else
for _,_weaponData in pairs(cohort.weaponData or{})do
local weaponData=_weaponData
if weaponData.BitType==WeaponType then
return true
end
end
end
end
return false
else
return true
end
end
local function CheckRange(_cohort)
local cohort=_cohort
local TargetDistance=TargetVec2 and UTILS.VecDist2D(TargetVec2,cohort.legion:GetVec2())or 0
local Rmax=cohort:GetMissionRange(WeaponTypes)
local RangeMax=RangeMax or 0
local InRange=(RangeMax and math.max(RangeMax,Rmax)or Rmax)>=TargetDistance
return InRange
end
local function CheckRefueling(_cohort)
local cohort=_cohort
if RefuelSystem then
if cohort.tankerSystem then
return RefuelSystem==cohort.tankerSystem
else
return false
end
else
return true
end
end
local function CheckCargoWeight(_cohort)
local cohort=_cohort
if CargoWeight~=nil then
return cohort.cargobayLimit>=CargoWeight
else
return true
end
end
local function CheckMaxWeight(_cohort)
local cohort=_cohort
if MaxWeight~=nil then
cohort:T(string.format("Cohort weight=%.1f | max weight=%.1f",cohort.weightAsset,MaxWeight))
return cohort.weightAsset<=MaxWeight
else
return true
end
end
local can=AUFTRAG.CheckMissionCapability(MissionType,Cohort.missiontypes)
if can then
can=CheckCategory(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of mission types",Cohort.name))
return false
end
if can then
if MissionType==AUFTRAG.Type.RELOCATECOHORT then
can=Cohort:IsRelocating()
else
can=Cohort:IsOnDuty()
end
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of category",Cohort.name))
return false
end
if can then
can=CheckAttribute(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of readyiness",Cohort.name))
return false
end
if can then
can=CheckProperty(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of attribute",Cohort.name))
return false
end
if can then
can=CheckWeapon(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of property",Cohort.name))
return false
end
if can then
can=CheckRange(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of weapon type",Cohort.name))
return false
end
if can then
can=CheckRefueling(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of range",Cohort.name))
return false
end
if can then
can=CheckCargoWeight(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of refueling system",Cohort.name))
return false
end
if can then
can=CheckMaxWeight(Cohort)
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of cargo weight",Cohort.name))
return false
end
if can then
return true
else
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of max weight",Cohort.name))
return false
end
return nil
end
function LEGION.RecruitCohortAssets(Cohorts,MissionTypeRecruit,MissionTypeOpt,NreqMin,NreqMax,TargetVec2,Payloads,RangeMax,RefuelSystem,CargoWeight,TotalWeight,MaxWeight,Categories,Attributes,Properties,WeaponTypes)
local Assets={}
local Legions={}
if MissionTypeOpt==nil then
MissionTypeOpt=MissionTypeRecruit
end
for _,_cohort in pairs(Cohorts)do
local cohort=_cohort
local can=LEGION._CohortCan(cohort,MissionTypeRecruit,Categories,Attributes,Properties,WeaponTypes,TargetVec2,RangeMax,RefuelSystem,CargoWeight,MaxWeight)
if can then
local assets,npayloads=cohort:RecruitAssets(MissionTypeRecruit,999)
for _,asset in pairs(assets)do
table.insert(Assets,asset)
end
end
end
if#Assets==0 then
return false,{},{}
end
LEGION._OptimizeAssetSelection(Assets,MissionTypeOpt,TargetVec2,false,TotalWeight)
for _,_asset in pairs(Assets)do
local asset=_asset
if asset.legion:IsAirwing()and not asset.payload then
asset.payload=asset.legion:FetchPayloadFromStock(asset.unittype,MissionTypeOpt,Payloads)
end
end
for i=#Assets,1,-1 do
local asset=Assets[i]
if asset.legion:IsAirwing()and not asset.payload then
table.remove(Assets,i)
end
end
LEGION._OptimizeAssetSelection(Assets,MissionTypeOpt,TargetVec2,true,TotalWeight)
local Nassets=math.min(#Assets,NreqMax)
if#Assets>=NreqMin then
local cargobay=0
for i=1,Nassets do
local asset=Assets[i]
asset.isReserved=true
Legions[asset.legion.alias]=asset.legion
if TotalWeight then
local N=math.floor(asset.cargobaytot/asset.nunits/CargoWeight)*asset.nunits
cargobay=cargobay+N*CargoWeight
if cargobay>=TotalWeight then
Nassets=i
break
end
end
end
for i=#Assets,Nassets+1,-1 do
local asset=Assets[i]
if asset.legion:IsAirwing()and not asset.spawned then
asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname))
asset.legion:ReturnPayloadFromAsset(asset)
end
table.remove(Assets,i)
end
return true,Assets,Legions
else
for i=1,#Assets do
local asset=Assets[i]
if asset.legion:IsAirwing()and not asset.spawned then
asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname))
asset.legion:ReturnPayloadFromAsset(asset)
end
end
return false,{},{}
end
return false,{},{}
end
function LEGION.UnRecruitAssets(Assets,Mission)
for i=1,#Assets do
local asset=Assets[i]
asset.isReserved=false
if asset.legion:IsAirwing()and not asset.spawned then
asset.legion:T2(asset.legion.lid..string.format("Returning payload from asset %s",asset.spawngroupname))
asset.legion:ReturnPayloadFromAsset(asset)
end
if Mission then
Mission:DelAsset(asset)
end
end
end
function LEGION:AssignAssetsForEscort(Cohorts,Assets,NescortMin,NescortMax,MissionType,TargetTypes,EngageRange)
if NescortMin and NescortMax and(NescortMin>0 or NescortMax>0)then
self:T(self.lid..string.format("Requested escort for %d assets from %d cohorts. Required escort assets=%d-%d",#Assets,#Cohorts,NescortMin,NescortMax))
local Escorts={}
local EscortAvail=true
for _,_asset in pairs(Assets)do
local asset=_asset
local TargetVec2=asset.legion:GetVec2()
local Categories={Group.Category.HELICOPTER}
local targetTypes={"Ground Units"}
if asset.category==Group.Category.AIRPLANE then
Categories={Group.Category.AIRPLANE}
targetTypes={"Air"}
end
TargetTypes=TargetTypes or targetTypes
local Erecruited,eassets,elegions=LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.ESCORT,MissionType,NescortMin,NescortMax,TargetVec2,nil,nil,nil,nil,nil,nil,Categories)
if Erecruited then
Escorts[asset.spawngroupname]={EscortLegions=elegions,EscortAssets=eassets,ecategory=asset.category}
else
EscortAvail=false
break
end
end
if EscortAvail then
local N=0
for groupname,value in pairs(Escorts)do
local Elegions=value.EscortLegions
local Eassets=value.EscortAssets
local ecategory=value.ecategory
for _,_legion in pairs(Elegions)do
local legion=_legion
local OffsetVector=nil
if ecategory==Group.Category.GROUND then
OffsetVector={}
OffsetVector.x=0
OffsetVector.y=UTILS.FeetToMeters(1000)
OffsetVector.z=0
elseif MissionType==AUFTRAG.Type.SEAD then
OffsetVector={}
OffsetVector.x=-100
OffsetVector.y=500
OffsetVector.z=500
end
local escort=AUFTRAG:NewESCORT(groupname,OffsetVector,EngageRange,TargetTypes)
if MissionType==AUFTRAG.Type.SEAD then
escort.missionTask=ENUMS.MissionTask.SEAD
local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil)
table.insert(escort.enrouteTasks,DCStask)
end
for _,_asset in pairs(Eassets)do
local asset=_asset
escort:AddAsset(asset)
N=N+1
end
self:MissionAssign(escort,{legion})
end
end
self:T(self.lid..string.format("Recruited %d escort assets",N))
return true
else
self:T(self.lid..string.format("Could not get at least one escort!"))
for groupname,value in pairs(Escorts)do
local Eassets=value.EscortAssets
LEGION.UnRecruitAssets(Eassets)
end
return false
end
else
self:T(self.lid..string.format("No escort required! NescortMin=%s, NescortMax=%s",tostring(NescortMin),tostring(NescortMax)))
return true
end
end
function LEGION:AssignAssetsForTransport(Legions,CargoAssets,NcarriersMin,NcarriersMax,DeployZone,DisembarkZone,Categories,Attributes,Properties)
if NcarriersMin and NcarriersMax and(NcarriersMin>0 or NcarriersMax>0)then
local Cohorts=LEGION._GetCohorts(Legions)
local CargoLegions={};local CargoWeight=nil;local TotalWeight=0
for _,_asset in pairs(CargoAssets)do
local asset=_asset
CargoLegions[asset.legion.alias]=asset.legion
if CargoWeight==nil or asset.weight>CargoWeight then
CargoWeight=asset.weight
end
TotalWeight=TotalWeight+asset.weight
end
self:T(self.lid..string.format("Cargo weight=%.1f",CargoWeight))
self:T(self.lid..string.format("Total weight=%.1f",TotalWeight))
local TargetVec2=DeployZone:GetVec2()
local TransportAvail,CarrierAssets,CarrierLegions=
LEGION.RecruitCohortAssets(Cohorts,AUFTRAG.Type.OPSTRANSPORT,nil,NcarriersMin,NcarriersMax,TargetVec2,nil,nil,nil,CargoWeight,TotalWeight,nil,Categories,Attributes,Properties)
if TransportAvail then
local Transport=OPSTRANSPORT:New(nil,nil,DeployZone)
if DisembarkZone then
Transport:SetDisembarkZone(DisembarkZone)
end
self:T(self.lid..string.format("Transport available with %d carrier assets",#CarrierAssets))
for _,_legion in pairs(CargoLegions)do
local legion=_legion
local pickupzone=legion.spawnzone
local tpz=Transport:AddTransportZoneCombo(nil,pickupzone,Transport:GetDeployZone())
tpz.PickupAirbase=legion:IsRunwayOperational()and legion.airbase or nil
Transport:SetEmbarkZone(legion.spawnzone,tpz)
for _,_asset in pairs(CargoAssets)do
local asset=_asset
if asset.legion.alias==legion.alias then
Transport:AddAssetCargo(asset,tpz)
end
end
end
for _,_asset in pairs(CarrierAssets)do
local asset=_asset
Transport:AddAsset(asset)
end
self:TransportAssign(Transport,CarrierLegions)
return true,Transport
else
self:T(self.lid..string.format("Transport assets could not be allocated ==> Unrecruiting assets"))
LEGION.UnRecruitAssets(CarrierAssets)
return false,nil
end
return nil,nil
end
return true,nil
end
function LEGION.CalculateAssetMissionScore(asset,MissionType,TargetVec2,IncludePayload,TotalWeight)
local score=0
if asset.skill==AI.Skill.AVERAGE then
score=score+0
elseif asset.skill==AI.Skill.GOOD then
score=score+10
elseif asset.skill==AI.Skill.HIGH then
score=score+20
elseif asset.skill==AI.Skill.EXCELLENT then
score=score+30
end
score=score+asset.cohort:GetMissionPeformance(MissionType)
local function scorePayload(Payload,MissionType)
for _,Capability in pairs(Payload.capabilities)do
local capability=Capability
if capability.MissionType==MissionType then
return capability.Performance
end
end
return 0
end
if IncludePayload and asset.payload then
score=score+scorePayload(asset.payload,MissionType)
end
local OrigVec2=asset.flightgroup and asset.flightgroup:GetVec2()or asset.legion:GetVec2()
local distance=0
if TargetVec2 and OrigVec2 then
distance=UTILS.MetersToNM(UTILS.VecDist2D(OrigVec2,TargetVec2))
if asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
distance=UTILS.Round(distance/10,0)
else
distance=UTILS.Round(distance,0)
end
end
score=score-distance
if asset.spawned and asset.flightgroup and asset.flightgroup:IsAlive()then
local currmission=asset.flightgroup:GetMissionCurrent()
if currmission then
if currmission.type==AUFTRAG.Type.ALERT5 and currmission.alert5MissionType==MissionType then
score=score+25
elseif(currmission.type==AUFTRAG.Type.GCICAP or currmission.type==AUFTRAG.Type.PATROLRACETRACK)and MissionType==AUFTRAG.Type.INTERCEPT then
score=score+35
elseif(currmission.type==AUFTRAG.Type.ONGUARD or currmission.type==AUFTRAG.Type.PATROLZONE)and(MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK)then
score=score+25
elseif currmission.type==AUFTRAG.Type.NOTHING then
score=score+30
end
end
if MissionType==AUFTRAG.Type.OPSTRANSPORT or MissionType==AUFTRAG.Type.AMMOSUPPLY or MissionType==AUFTRAG.Type.AWACS or MissionType==AUFTRAG.Type.FUELSUPPLY or MissionType==AUFTRAG.Type.TANKER then
score=score-10
else
if asset.flightgroup:IsOutOfAmmo()then
score=score-1000
end
end
end
if MissionType==AUFTRAG.Type.OPSTRANSPORT then
if TotalWeight then
if asset.cargobaymax<TotalWeight then
score=score+UTILS.Round(asset.cargobaymax/10,0)
else
score=score+UTILS.Round(TotalWeight/10,0)
end
else
score=score+UTILS.Round(asset.cargobaymax/10,0)
end
end
if asset.legion and asset.legion.verbose>=2 then
asset.legion:I(asset.legion.lid..string.format("Asset %s [spawned=%s] score=%d",asset.spawngroupname,tostring(asset.spawned),score))
end
return score
end
function LEGION._OptimizeAssetSelection(assets,MissionType,TargetVec2,IncludePayload,TotalWeight)
for _,_asset in pairs(assets)do
local asset=_asset
asset.score=LEGION.CalculateAssetMissionScore(asset,MissionType,TargetVec2,IncludePayload,TotalWeight)
if IncludePayload then
local RandomScoreMax=asset.legion and asset.legion.RandomAssetScore or LEGION.RandomAssetScore
local RandomScore=math.random(0,RandomScoreMax)
asset.score=asset.score+RandomScore
end
end
local function optimize(a,b)
local assetA=a
local assetB=b
return(assetA.score>assetB.score)
end
table.sort(assets,optimize)
if LEGION.verbose>0 then
local text=string.format("Optimized %d assets for %s mission/transport (payload=%s):",#assets,MissionType,tostring(IncludePayload))
for i,Asset in pairs(assets)do
local asset=Asset
text=text..string.format("\n%d. %s [%s]: score=%d",i,asset.spawngroupname,asset.squadname,asset.score or-1)
asset.score=nil
end
env.info(text)
end
end
function LEGION:GetMissionByID(mid)
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.auftragsnummer==tonumber(mid)then
return mission
end
end
return nil
end
function LEGION:GetTransportByID(uid)
for _,_transport in pairs(self.transportqueue)do
local transport=_transport
if transport.uid==tonumber(uid)then
return transport
end
end
return nil
end
function LEGION:GetMissionFromRequestID(RequestID)
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
local mid=mission.requestID[self.alias]
if mid and mid==RequestID then
return mission
end
end
return nil
end
function LEGION:GetMissionFromRequest(Request)
return self:GetMissionFromRequestID(Request.uid)
end
function LEGION:FetchPayloadFromStock(UnitType,MissionType,Payloads)
return nil
end
function LEGION:ReturnPayloadFromAsset(asset)
return nil
end
NAVYGROUP={
ClassName="NAVYGROUP",
turning=false,
intowind=nil,
intowindcounter=0,
Qintowind={},
pathCorridor=400,
engage={},
}
NAVYGROUP.version="1.0.3"
function NAVYGROUP:New(group)
local og=_DATABASE:GetOpsGroup(group)
if og then
og:I(og.lid..string.format("WARNING: OPS group already exists in data base!"))
return og
end
local self=BASE:Inherit(self,OPSGROUP:New(group))
self.lid=string.format("NAVYGROUP %s | ",self.groupname)
self:SetDefaultROE()
self:SetDefaultAlarmstate()
self:SetDefaultEPLRS(self.isEPLRS)
self:SetDefaultEmission()
self:SetDetection()
self:SetPatrolAdInfinitum(true)
self:SetPathfinding(false)
self:AddTransition("*","FullStop","Holding")
self:AddTransition("*","Cruise","Cruising")
self:AddTransition("*","RTZ","Returning")
self:AddTransition("Returning","Returned","Returned")
self:AddTransition("*","Detour","Cruising")
self:AddTransition("*","DetourReached","*")
self:AddTransition("*","Retreat","Retreating")
self:AddTransition("Retreating","Retreated","Retreated")
self:AddTransition("Cruising","EngageTarget","Engaging")
self:AddTransition("Holding","EngageTarget","Engaging")
self:AddTransition("OnDetour","EngageTarget","Engaging")
self:AddTransition("Engaging","Disengage","Cruising")
self:AddTransition("*","TurnIntoWind","Cruising")
self:AddTransition("*","TurnedIntoWind","*")
self:AddTransition("*","TurnIntoWindStop","*")
self:AddTransition("*","TurnIntoWindOver","*")
self:AddTransition("*","TurningStarted","*")
self:AddTransition("*","TurningStopped","*")
self:AddTransition("*","CollisionWarning","*")
self:AddTransition("*","ClearAhead","*")
self:AddTransition("Cruising","Dive","Cruising")
self:AddTransition("Engaging","Dive","Engaging")
self:AddTransition("Cruising","Surface","Cruising")
self:AddTransition("Engaging","Surface","Engaging")
self:_InitWaypoints()
self:_InitGroup()
self:HandleEvent(EVENTS.Birth,self.OnEventBirth)
self:HandleEvent(EVENTS.Dead,self.OnEventDead)
self:HandleEvent(EVENTS.RemoveUnit,self.OnEventRemoveUnit)
self: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)
_DATABASE:AddOpsGroup(self)
return self
end
function NAVYGROUP:SetPatrolAdInfinitum(switch)
if switch==false then
self.adinfinitum=false
else
self.adinfinitum=true
end
return self
end
function NAVYGROUP:SetPathfinding(Switch,CorridorWidth)
self.pathfindingOn=Switch
self.pathCorridor=CorridorWidth or 400
return self
end
function NAVYGROUP:SetPathfindingOn(CorridorWidth)
self:SetPathfinding(true,CorridorWidth)
return self
end
function NAVYGROUP:SetPathfindingOff()
self:SetPathfinding(false,self.pathCorridor)
return self
end
function NAVYGROUP: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)
return task
end
function NAVYGROUP:AddTaskWaypointFireAtPoint(Coordinate,Waypoint,Radius,Nshots,WeaponType,Prio,Duration)
Waypoint=Waypoint or self:GetWaypointNext()
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,Coordinate:GetVec2(),Radius,Nshots,WeaponType)
local task=self:AddTaskWaypoint(DCStask,Waypoint,nil,Prio,Duration)
return task
end
function NAVYGROUP:AddTaskAttackGroup(TargetGroup,WeaponExpend,WeaponType,Clock,Prio)
local DCStask=CONTROLLABLE.TaskAttackGroup(nil,TargetGroup,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit,GroupAttack)
local task=self:AddTask(DCStask,Clock,nil,Prio)
return task
end
function NAVYGROUP:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset)
local Tnow=timer.getAbsTime()
if starttime and type(starttime)=="number"then
starttime=UTILS.SecondsToClock(Tnow+starttime)
end
starttime=starttime or UTILS.SecondsToClock(Tnow)
local Tstart=UTILS.ClockToSeconds(starttime)
if uturn==nil then
uturn=true
end
local Tstop=Tstart+90*60
if stoptime==nil then
Tstop=Tstart+90*60
elseif type(stoptime)=="number"then
Tstop=Tstart+stoptime
else
Tstop=UTILS.ClockToSeconds(stoptime)
end
if Tstart>Tstop then
self:E(string.format("ERROR:Into wind stop time %s lies before start time %s. Input rejected!",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop)))
return self
end
if Tstop<=Tnow then
self:E(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow)))
return self
end
self.intowindcounter=self.intowindcounter+1
local recovery={}
recovery.Tstart=Tstart
recovery.Tstop=Tstop
recovery.Open=false
recovery.Over=false
recovery.Speed=speed or 20
recovery.Uturn=uturn and uturn or false
recovery.Offset=offset or 0
recovery.Id=self.intowindcounter
return recovery
end
function NAVYGROUP:AddTurnIntoWind(starttime,stoptime,speed,uturn,offset)
local recovery=self:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset)
table.insert(self.Qintowind,recovery)
return recovery
end
function NAVYGROUP: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()
return
end
for i,_tiw in pairs(self.Qintowind)do
local tiw=_tiw
if tiw.Id==IntoWindData.Id then
table.remove(self.Qintowind,i)
break
end
end
return self
end
function NAVYGROUP:IsHolding()
return self:Is("Holding")
end
function NAVYGROUP:IsCruising()
return self:Is("Cruising")
end
function NAVYGROUP:IsOnDetour()
return self:Is("OnDetour")
end
function NAVYGROUP:IsDiving()
return self:Is("Diving")
end
function NAVYGROUP:IsTurning()
return self.turning
end
function NAVYGROUP:IsSteamingIntoWind()
if self.intowind then
return true
else
return false
end
end
function NAVYGROUP:IsRecovering()
if self.intowind then
if self.intowind.Recovery==true then
return true
else
return false
end
else
return false
end
end
function NAVYGROUP:IsLaunching()
if self.intowind then
if self.intowind.Recovery==false then
return true
else
return false
end
else
return false
end
end
function NAVYGROUP:Status()
local fsmstate=self:GetState()
local alive=self:IsAlive()
local freepath=0
if alive then
self:_UpdatePosition()
self:_CheckDetectedUnits()
self:_CheckTurning()
local disttoWP=math.min(self:GetDistanceToWaypoint(),UTILS.NMToMeters(10))
freepath=disttoWP
if not self:IsTurning()then
freepath=self:_CheckFreePath(freepath,100)
if disttoWP>1 and freepath<disttoWP then
if not self.collisionwarning then
self:CollisionWarning(freepath)
end
if self.pathfindingOn and not self.ispathfinding then
self.ispathfinding=self:_FindPathToNextWaypoint()
end
end
end
self:_CheckTurnsIntoWind()
self:_CheckAmmoStatus()
self:_CheckDamage()
self:_CheckStuck()
if self:IsWaiting()then
if self.Twaiting and self.dTwait then
if timer.getAbsTime()>self.Twaiting+self.dTwait then
self.Twaiting=nil
self.dTwait=nil
if self:_CountPausedMissions()>0 then
self:UnpauseMission()
else
self:Cruise()
end
end
end
end
local mission=self:GetMissionCurrent()
if mission and mission.updateDCSTask then
if mission.type==AUFTRAG.Type.CAPTUREZONE then
local Task=mission:GetGroupWaypointTask(self)
if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then
self:_UpdateTask(Task,mission)
end
end
end
else
self:_CheckDamage()
end
if alive~=nil then
if self.verbose>=1 then
local nelem=self:CountElements()
local Nelem=#self.elements
local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks()
local nMissions=self:CountRemainingMissison()
local roe=self:GetROE()or-1
local als=self:GetAlarmstate()or-1
local wpidxCurr=self.currentwp
local wpuidCurr=self:GetWaypointUIDFromIndex(wpidxCurr)or 0
local wpidxNext=self:GetWaypointIndexNext()or 0
local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext)or 0
local wpN=#self.waypoints or 0
local wpF=tostring(self.passedfinalwp)
local speed=UTILS.MpsToKnots(self.velocity or 0)
local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed())
local alt=self.position and self.position.y or 0
local hdg=self.heading or 0
local life=self.life or 0
local ammo=self:GetAmmoTot().Total
local ndetected=self.detectionOn and tostring(self.detectedunits:Count())or"Off"
local cargo=0
for _,_element in pairs(self.elements)do
local element=_element
cargo=cargo+element.weightCargo
end
local intowind=self:IsSteamingIntoWind()and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(),true)or"N/A"
local turning=tostring(self:IsTurning())
local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Turn=%s Collision=%d IntoWind=%s",
fsmstate,nelem,Nelem,roe,als,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,wpN,wpF,life,speed,speedEx,hdg,ammo,ndetected,cargo,turning,freepath,intowind)
self:I(self.lid..text)
end
else
local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive()))
self:T(self.lid..text)
end
if alive and self.verbose>=2 and#self.Qintowind>0 then
local text=string.format(self.lid.."Turn into wind time windows:")
if#self.Qintowind==0 then
text=text.." none!"
end
for i,_recovery in pairs(self.Qintowind)do
local recovery=_recovery
local Cstart=UTILS.SecondsToClock(recovery.Tstart)
local Cstop=UTILS.SecondsToClock(recovery.Tstop)
text=text..string.format("\n[%d] ID=%d Start=%s Stop=%s Open=%s Over=%s",i,recovery.Id,Cstart,Cstop,tostring(recovery.Open),tostring(recovery.Over))
end
self:I(self.lid..text)
end
if self.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
self:I(self.lid..string.format("Engaging target group %s at distance %d meters",targetgroup:GetName(),targetdist))
self:EngageTarget(targetgroup)
end
end
self:_CheckCargoTransport()
self:_PrintTaskAndMissionStatus()
end
function NAVYGROUP:onafterElementSpawned(From,Event,To,Element)
self:T(self.lid..string.format("Element spawned %s",Element.name))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED)
end
function NAVYGROUP:onafterSpawned(From,Event,To)
self:T(self.lid..string.format("Group spawned!"))
if self.verbose>=1 then
local text=string.format("Initialized Navy Group %s [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))
text=text..string.format("Weight       = %.1f kg\n",self:GetWeightTotal())
text=text..string.format("Cargo bay    = %.1f kg\n",self:GetFreeCargobay())
text=text..string.format("Has EPLRS    = %s\n",tostring(self.isEPLRS))
text=text..string.format("Is Submarine = %s\n",tostring(self.isSubmarine))
text=text..string.format("Elements     = %d\n",#self.elements)
text=text..string.format("Waypoints    = %d\n",#self.waypoints)
text=text..string.format("Radio        = %.1f MHz %s %s\n",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu),tostring(self.radio.On))
text=text..string.format("Ammo         = %d (G=%d/R=%d/M=%d/T=%d)\n",self.ammo.Total,self.ammo.Guns,self.ammo.Rockets,self.ammo.Missiles,self.ammo.Torpedos)
text=text..string.format("FSM state    = %s\n",self:GetState())
text=text..string.format("Is alive     = %s\n",tostring(self:IsAlive()))
text=text..string.format("LateActivate = %s\n",tostring(self:IsLateActivated()))
self:I(self.lid..text)
end
self:_UpdatePosition()
self.isDead=false
self.isDestroyed=false
if self.isAI then
self:SwitchROE(self.option.ROE)
self:SwitchAlarmstate(self.option.Alarm)
self:SwitchEmission(self.option.Emission)
self:SwitchEPLRS(self.option.EPLRS)
self:SwitchInvisible(self.option.Invisible)
self:SwitchImmortal(self.option.Immortal)
self:_SwitchTACAN()
self:_SwitchICLS()
if self.radioDefault then
else
self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,false)
end
if#self.waypoints>1 then
self:__Cruise(-0.1)
else
self:FullStop()
end
end
end
function NAVYGROUP:onbeforeUpdateRoute(From,Event,To,n,Speed,Depth)
local allowed=true
local trepeat=nil
if self:IsWaiting()then
self:T(self.lid.."Update route denied. Group is WAITING!")
return false
elseif self:IsInUtero()then
self:T(self.lid.."Update route denied. Group is INUTERO!")
return false
elseif self:IsDead()then
self:T(self.lid.."Update route denied. Group is DEAD!")
return false
elseif self:IsStopped()then
self:T(self.lid.."Update route denied. Group is STOPPED!")
return false
elseif self:IsHolding()then
self:T(self.lid.."Update route denied. Group is holding position!")
return false
elseif self:IsEngaging()then
self:T(self.lid.."Update route allowed. Group is engaging!")
return true
end
if self.taskcurrent>0 then
local task=self:GetTaskByID(self.taskcurrent)
if task then
if task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
self:T2(self.lid.."Allowing update route for Task: PatrolZone")
elseif task.dcstask.id==AUFTRAG.SpecialTask.RECON then
self:T2(self.lid.."Allowing update route for Task: ReconMission")
elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then
self:T2(self.lid.."Allowing update route for Task: Relocate Cohort")
elseif task.dcstask.id==AUFTRAG.SpecialTask.REARMING then
self:T2(self.lid.."Allowing update route for Task: Rearming")
else
local taskname=task and task.description or"No description"
self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s",self.taskcurrent,tostring(taskname)))
allowed=false
end
else
self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d (>0!) but no task?!",self.taskcurrent))
allowed=false
end
end
if not self.isAI then
allowed=false
end
self:T2(self.lid..string.format("Onbefore Updateroute in state %s: allowed=%s (repeat in %s)",self:GetState(),tostring(allowed),tostring(trepeat)))
if trepeat then
self:__UpdateRoute(trepeat,n)
end
return allowed
end
function NAVYGROUP:onafterUpdateRoute(From,Event,To,n,N,Speed,Depth)
n=n or self:GetWaypointIndexNext()
N=N or#self.waypoints
N=math.min(N,#self.waypoints)
local waypoints={}
for i=n,N do
local wp=UTILS.DeepCopy(self.waypoints[i])
if Speed then
wp.speed=UTILS.KnotsToMps(Speed)
else
if wp.speed<0.1 then
wp.speed=UTILS.KmphToMps(self.speedCruise)
end
end
if Depth then
wp.alt=-Depth
elseif self.depth then
wp.alt=-self.depth
else
wp.alt=wp.alt or 0
end
if i==n then
self.speedWp=wp.speed
self.altWp=wp.alt
end
table.insert(waypoints,wp)
end
local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp),self.altWp)
table.insert(waypoints,1,current)
if self:IsEngaging()or not self.passedfinalwp then
if self.verbose>=10 then
for i=1,#waypoints do
local wp=waypoints[i]
local text=string.format("%s Waypoint [%d] UID=%d speed=%d m/s",self.groupname,i-1,wp.uid or-1,wp.speed)
self:I(self.lid..text)
COORDINATE:NewFromWaypoint(wp):MarkToAll(text)
end
end
self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Depth=%d m",self.currentwp,n,#waypoints,#self.waypoints,UTILS.MpsToKnots(self.speedWp),self.altWp))
self:Route(waypoints)
else
self:E(self.lid..string.format("WARNING: Passed final WP ==> Full Stop!"))
self:FullStop()
end
end
function NAVYGROUP:onafterDetour(From,Event,To,Coordinate,Speed,Depth,ResumeRoute)
Depth=Depth or 0
Speed=Speed or self:GetSpeedCruise()
local uid=self:GetWaypointCurrent().uid
local wp=self:AddWaypoint(Coordinate,Speed,uid,Depth,true)
if ResumeRoute then
wp.detour=1
else
wp.detour=0
end
end
function NAVYGROUP:onafterDetourReached(From,Event,To)
self:T(self.lid.."Group reached detour coordinate.")
end
function NAVYGROUP:onafterTurnIntoWind(From,Event,To,IntoWind)
local heading,speed=self:GetHeadingIntoWind(IntoWind.Offset,IntoWind.Speed)
IntoWind.Heading=heading
IntoWind.Open=true
IntoWind.Coordinate=self:GetCoordinate(true)
self.intowind=IntoWind
self:T(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f, Tstart=%d Tstop=%d",IntoWind.Heading,speed,IntoWind.Tstart,IntoWind.Tstop))
local distance=UTILS.NMToMeters(1000)
local coord=self:GetCoordinate()
local Coord=coord:Translate(distance,IntoWind.Heading)
local uid=self:GetWaypointCurrent().uid
local wptiw=self:AddWaypoint(Coord,speed,uid)
wptiw.intowind=true
IntoWind.waypoint=wptiw
if IntoWind.Uturn and false then
IntoWind.Coordinate:MarkToAll("Return coord")
end
end
function NAVYGROUP:onbeforeTurnIntoWindStop(From,Event,To)
if self.intowind then
return true
else
return false
end
end
function NAVYGROUP:onafterTurnIntoWindStop(From,Event,To)
self:TurnIntoWindOver(self.intowind)
end
function NAVYGROUP:onafterTurnIntoWindOver(From,Event,To,IntoWindData)
if IntoWindData and self.intowind and IntoWindData.Id==self.intowind.Id then
self:T2(self.lid.."Turn Into Wind Over!")
self.intowind.Over=true
self.intowind.Open=false
self:RemoveWaypointByID(self.intowind.waypoint.uid)
if self.intowind.Uturn then
self:T(self.lid.."FF Turn Into Wind Over ==> Uturn!")
local uid=self:GetWaypointCurrent().uid
local wp=self:AddWaypoint(self.intowind.Coordinate,self:GetSpeedCruise(),uid);wp.temp=true
else
local indx=self:GetWaypointIndexNext()
local speed=self:GetSpeedToWaypoint(indx)
self:T(self.lid..string.format("FF Turn Into Wind Over ==> Next WP Index=%d at %.1f knots via update route!",indx,speed))
self:__UpdateRoute(-1,indx,nil,speed)
end
self.intowind=nil
self:RemoveTurnIntoWind(IntoWindData)
end
end
function NAVYGROUP:onafterFullStop(From,Event,To)
self:T(self.lid.."Full stop ==> holding")
local pos=self:GetCoordinate()
local wp=pos:WaypointNaval(0)
self:Route({wp})
end
function NAVYGROUP:onafterCruise(From,Event,To,Speed)
self.Twaiting=nil
self.dTwait=nil
self.depth=nil
self:__UpdateRoute(-0.1,nil,nil,Speed)
end
function NAVYGROUP:onafterDive(From,Event,To,Depth,Speed)
Depth=Depth or 50
self:I(self.lid..string.format("Diving to %d meters",Depth))
self.depth=Depth
self:__UpdateRoute(-1,nil,nil,Speed)
end
function NAVYGROUP:onafterSurface(From,Event,To,Speed)
self.depth=0
self:__UpdateRoute(-1,nil,nil,Speed)
end
function NAVYGROUP:onafterTurningStarted(From,Event,To)
self.turning=true
end
function NAVYGROUP:onafterTurningStopped(From,Event,To)
self.turning=false
self.collisionwarning=false
if self:IsSteamingIntoWind()then
self:TurnedIntoWind()
end
end
function NAVYGROUP:onafterCollisionWarning(From,Event,To,Distance)
self:T(self.lid..string.format("Iceberg ahead in %d meters!",Distance or-1))
self.collisionwarning=true
end
function NAVYGROUP:onafterEngageTarget(From,Event,To,Target)
self:T(self.lid.."Engaging Target")
if Target:IsInstanceOf("TARGET")then
self.engage.Target=Target
else
self.engage.Target=TARGET:New(Target)
end
self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate())
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9)
self.engage.roe=self:GetROE()
self.engage.alarmstate=self:GetAlarmstate()
self:SwitchAlarmstate(ENUMS.AlarmState.Auto)
self:SwitchROE(ENUMS.ROE.OpenFire)
local uid=self:GetWaypointCurrent().uid
self.engage.Waypoint=self:AddWaypoint(intercoord,nil,uid,Formation,true)
self.engage.Waypoint.detour=1
end
function NAVYGROUP:_UpdateEngageTarget()
if self.engage.Target and self.engage.Target:IsAlive()then
local vec3=self.engage.Target:GetVec3()
if vec3 then
local dist=UTILS.VecDist3D(vec3,self.engage.Coordinate:GetVec3())
if dist>100 then
self.engage.Coordinate:UpdateFromVec3(vec3)
local uid=self:GetWaypointCurrent().uid
self:RemoveWaypointByID(self.engage.Waypoint.uid)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate,0.9)
self.engage.Waypoint=self:AddWaypoint(intercoord,nil,uid,Formation,true)
self.engage.Waypoint.detour=0
end
else
self:Disengage()
end
else
self:Disengage()
end
end
function NAVYGROUP:onafterDisengage(From,Event,To)
self:T(self.lid.."Disengage Target")
self:SwitchROE(self.engage.roe)
self:SwitchAlarmstate(self.engage.alarmstate)
local task=self:GetTaskCurrent()
if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then
self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!")
self:TaskDone(task)
end
if self.engage.Waypoint then
self:RemoveWaypointByID(self.engage.Waypoint.uid)
end
self:_CheckGroupDone(1)
end
function NAVYGROUP:onafterOutOfAmmo(From,Event,To)
self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime()))
if self.retreatOnOutOfAmmo then
self:__Retreat(-1)
return
end
if self.rtzOnOutOfAmmo then
self:__RTZ(-1)
end
local task=self:GetTaskCurrent()
if task then
if task.dcstask.id=="FireAtPoint"or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then
self:T(self.lid..string.format("Cancelling current %s task because out of ammo!",task.dcstask.id))
self:TaskCancel(task)
end
end
end
function NAVYGROUP:onafterRTZ(From,Event,To,Zone,Formation)
local zone=Zone or self.homezone
self:CancelAllMissions()
if zone then
if self:IsInZone(zone)then
self:Returned()
else
self:T(self.lid..string.format("RTZ to Zone %s",zone:GetName()))
local Coordinate=zone:GetRandomCoordinate()
local uid=self:GetWaypointCurrentUID()
local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true)
wp.detour=0
end
else
self:T(self.lid.."ERROR: No RTZ zone given!")
end
end
function NAVYGROUP:onafterReturned(From,Event,To)
self:T(self.lid..string.format("Group returned"))
if self.legion then
self:T(self.lid..string.format("Adding group back to warehouse stock"))
self.legion:__AddAsset(10,self.group,1)
end
end
function NAVYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Depth,Updateroute)
local coordinate=self:_CoordinateFromObject(Coordinate)
local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID)
Speed=Speed or self:GetSpeedCruise()
local wp=coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed),Depth)
local waypoint=self:_CreateWaypoint(wp)
if Depth then
waypoint.alt=UTILS.FeetToMeters(Depth)
end
self:_AddWaypoint(waypoint,wpnumber)
self:T(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d",wpnumber,waypoint.uid,Speed,self.currentwp,#self.waypoints))
if Updateroute==nil or Updateroute==true then
self:__UpdateRoute(-0.01)
end
return waypoint
end
function NAVYGROUP:_InitGroup(Template,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
end
local template=Template or self:_GetTemplate()
self.isAI=true
self.isLateActivated=template.lateActivation
self.isUncontrolled=false
self.speedMax=self.group:GetSpeedMax()
if self.speedMax and self.speedMax>3.6 then
self.isMobile=true
else
self.isMobile=false
self.speedMax=0
end
self.speedCruise=self.speedMax*0.7
self.ammo=self:GetAmmoTot()
self.radio.On=true
self.radio.Freq=tonumber(template.units[1].frequency)/1000000
self.radio.Modu=tonumber(template.units[1].modulation)
self.optionDefault.Formation="Off Road"
self.option.Formation=self.optionDefault.Formation
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()
if#units~=size0 then
self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!",#units,size0))
end
for _,unit in pairs(units)do
self:_AddElementByName(unit:GetName())
end
self.groupinitialized=true
end
return self
end
function NAVYGROUP:_CheckFreePath(DistanceMax,dx)
local distance=DistanceMax or 5000
local dx=dx or 100
if self:IsTurning()then
return distance
end
local offsetY=0.1
if UTILS.GetDCSMap()==DCSMAP.Caucasus then
offsetY=5.01
end
local vec3=self:GetVec3()
vec3.y=offsetY
local heading=self:GetHeading()
local function LoS(dist)
local checkvec3=UTILS.VecTranslate(vec3,dist,heading)
local los=land.isVisible(vec3,checkvec3)
return los
end
if LoS(DistanceMax)then
return DistanceMax
end
local function check()
local xmin=0
local xmax=DistanceMax
local Nmax=100
local eps=100
local N=1
while N<=Nmax do
local d=xmax-xmin
local x=xmin+d/2
local los=LoS(x)
self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s",N,xmin,xmax,x,d,tostring(los)))
if los and d<=eps then
return x
end
if los then
xmin=x
else
xmax=x
end
N=N+1
end
return 0
end
local _check=check()
return _check
end
function NAVYGROUP:_CheckTurning()
local unit=self.group:GetUnit(1)
if unit and unit:IsAlive()then
local vNew=self.orientX
local vLast=self.orientXLast
vNew.y=0;vLast.y=0
local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast)))
local turning=math.abs(deltaLast)>=2
if self.turning and not turning then
self:TurningStopped()
elseif turning and not self.turning then
self:TurningStarted()
end
self.turning=turning
end
end
function NAVYGROUP:_CheckTurnsIntoWind()
local time=timer.getAbsTime()
if self.intowind then
if time>=self.intowind.Tstop then
self:TurnIntoWindOver(self.intowind)
end
else
local IntoWind=self:GetTurnIntoWindNext()
if IntoWind then
self:TurnIntoWind(IntoWind)
end
end
end
function NAVYGROUP:GetTurnIntoWindNext()
if#self.Qintowind>0 then
local time=timer.getAbsTime()
table.sort(self.Qintowind,function(a,b)return a.Tstart<b.Tstart end)
for _,_recovery in pairs(self.Qintowind)do
local recovery=_recovery
if time>=recovery.Tstart and time<recovery.Tstop and not(recovery.Open or recovery.Over)then
return recovery
end
end
end
return nil
end
function NAVYGROUP:GetTurnIntoWindCurrent()
return self.intowind
end
function NAVYGROUP:GetWind(Altitude)
local coord=self:GetCoordinate()
local Wdir,Wspeed=coord:GetWind(Altitude or 18)
return Wdir,Wspeed
end
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+adjustDegreesForWindSpeed(vwind)
if vwind<0.1 then
intowind=self:GetHeading()
end
if intowind<0 then
intowind=intowind+360
end
local vtot=math.max(vdeck-UTILS.MpsToKnots(vwind),4)
return intowind,vtot
end
function NAVYGROUP:GetHeadingIntoWind_new(Offset,vdeck)
Offset=Offset or 0
local windfrom,vwind=self:GetWind(18)
vwind=UTILS.MpsToKnots(vwind)
local windto=(windfrom+180)%360
local alpha=math.rad(-Offset)
local Vmin=4
local Vmax=UTILS.KmphToKnots(self.speedMax)
local C=math.sqrt(math.cos(alpha)^2/math.sin(alpha)^2+1)
local vdeckMax=vwind+math.cos(alpha)*Vmax
local vdeckMin=vwind+math.cos(alpha)*Vmin
local v=0
local theta=0
if vdeck>vdeckMax then
v=Vmax
theta=math.asin(v/(vwind*C))-math.asin(-1/C)
elseif vdeck<vdeckMin then
v=Vmin
theta=math.asin(v/(vwind*C))-math.asin(-1/C)
elseif vdeck*math.sin(alpha)>vwind then
theta=math.pi/2
v=math.sqrt(vdeck^2-vwind^2)
else
theta=math.asin(vdeck*math.sin(alpha)/vwind)
v=vdeck*math.cos(alpha)-vwind*math.cos(theta)
end
local intowind=(540+(windto+math.deg(theta)))%360
self:T(self.lid..string.format("Heading into Wind: vship=%.1f, vwind=%.1f, WindTo=%03d°, Theta=%03d°, Heading=%03d",v,vwind,windto,theta,intowind))
return intowind,v
end
function NAVYGROUP: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()
local position=self:GetCoordinate()
local wpnext=self:GetWaypointNext()
if wpnext==nil then
return
end
local nextwp=wpnext.coordinate
if wpnext.intowind then
local hdg=self:GetHeading()
nextwp=position:Translate(UTILS.NMToMeters(20),hdg,true)
end
local speed=UTILS.MpsToKnots(wpnext.speed)
astar:SetStartCoordinate(position)
astar:SetEndCoordinate(nextwp)
local dist=position:Get2DDistance(nextwp)
if dist<5 then
return
end
local boxwidth=dist*2
local spacex=dist*0.1
local delta=dist/10
astar:CreateGrid({land.SurfaceType.WATER},boxwidth,spacex,delta,delta,self.verbose>10)
astar:SetValidNeighbourLoS(self.pathCorridor)
local function findpath()
local path=astar:GetPath(true,true)
if path then
local uid=self:GetWaypointCurrent().uid
for i,_node in ipairs(path)do
local node=_node
local wp=self:AddWaypoint(node.coordinate,speed,uid)
wp.astar=true
uid=wp.uid
if self.verbose>=10 then
node.coordinate:MarkToAll(string.format("Path node #%d",i))
end
end
return#path>0
else
return false
end
end
return findpath()
end
OPERATION={
ClassName="OPERATION",
verbose=0,
branches={},
counterPhase=0,
counterBranch=0,
counterEdge=0,
cohorts={},
legions={},
targets={},
missions={},
}
_OPERATIONID=0
OPERATION.PhaseStatus={
PLANNED="Planned",
ACTIVE="Active",
OVER="Over",
}
OPERATION.version="0.2.0"
function OPERATION:New(Name)
local self=BASE:Inherit(self,FSM:New())
_OPERATIONID=_OPERATIONID+1
self.uid=_OPERATIONID
self.name=Name or string.format("Operation-%02d",_OPERATIONID)
self.lid=string.format("%s | ",self.name)
self:SetStartState("Planned")
self.branchMaster=self:AddBranch("Master")
self.conditionStart=CONDITION:New("Operation %s start",self.name)
self.conditionStart:SetNoneResult(false)
self.conditionStart:SetDefaultPersistence(false)
self.conditionOver=CONDITION:New("Operation %s over",self.name)
self.conditionOver:SetNoneResult(false)
self.conditionOver:SetDefaultPersistence(false)
self.branchActive=self.branchMaster
self:AddTransition("*","Start","Running")
self:AddTransition("*","StatusUpdate","*")
self:AddTransition("Running","Pause","Paused")
self:AddTransition("Paused","Unpause","Running")
self:AddTransition("*","PhaseOver","*")
self:AddTransition("*","PhaseNext","*")
self:AddTransition("*","PhaseChange","*")
self:AddTransition("*","BranchSwitch","*")
self:AddTransition("*","Over","Over")
self:AddTransition("*","Stop","Stopped")
self:__StatusUpdate(-1)
return self
end
function OPERATION:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function OPERATION:SetTime(ClockStart,ClockStop)
local Tnow=timer.getAbsTime()
local Tstart=Tnow+5
if ClockStart and type(ClockStart)=="number"then
Tstart=Tnow+ClockStart
elseif ClockStart and type(ClockStart)=="string"then
Tstart=UTILS.ClockToSeconds(ClockStart)
end
local Tstop=nil
if ClockStop and type(ClockStop)=="number"then
Tstop=Tnow+ClockStop
elseif ClockStop and type(ClockStop)=="string"then
Tstop=UTILS.ClockToSeconds(ClockStop)
end
self.Tstart=Tstart
self.Tstop=Tstop
if Tstop then
self.duration=self.Tstop-self.Tstart
end
return self
end
function OPERATION:AddConditonOverAll(Function,...)
local cf=self.conditionOver:AddFunctionAll(Function,...)
return cf
end
function OPERATION:AddConditonOverAny(Phase,Function,...)
local cf=self.conditionOver:AddFunctionAny(Function,...)
return cf
end
function OPERATION:AddPhase(Name,Branch,Duration)
Branch=Branch or self.branchMaster
local phase=self:_CreatePhase(Name)
phase.branch=Branch
phase.duration=Duration
self:T(self.lid..string.format("Adding phase %s to branch %s",phase.name,Branch.name))
table.insert(Branch.phases,phase)
return phase
end
function OPERATION:InsertPhaseAfter(PhaseAfter,Name)
for i=1,#self.phases do
local phase=self.phases[i]
if PhaseAfter.uid==phase.uid then
local phase=self:_CreatePhase(Name)
end
end
return nil
end
function OPERATION:GetName()
return self.name or"Unknown"
end
function OPERATION:GetPhaseByName(Name)
for _,_branch in pairs(self.branches)do
local branch=_branch
for _,_phase in pairs(branch.phases or{})do
local phase=_phase
if phase.name==Name then
return phase
end
end
end
return nil
end
function OPERATION:SetPhaseStatus(Phase,Status)
if Phase then
self:T(self.lid..string.format("Phase %s status: %s-->%s",tostring(Phase.name),tostring(Phase.status),tostring(Status)))
Phase.status=Status
if Phase.status==OPERATION.PhaseStatus.ACTIVE then
Phase.Tstart=timer.getAbsTime()
Phase.nActive=Phase.nActive+1
elseif Phase.status==OPERATION.PhaseStatus.OVER then
self:PhaseOver(Phase)
end
end
return self
end
function OPERATION:GetPhaseStatus(Phase)
return Phase.status
end
function OPERATION:SetPhaseConditonOver(Phase,Condition)
if Phase then
self:T(self.lid..string.format("Setting phase %s conditon over %s",self:GetPhaseName(Phase),Condition and Condition.name or"None"))
Phase.conditionOver=Condition
end
return self
end
function OPERATION:AddPhaseConditonOverAll(Phase,Function,...)
if Phase then
local cf=Phase.conditionOver:AddFunctionAll(Function,...)
return cf
end
return nil
end
function OPERATION:AddPhaseConditonOverAny(Phase,Function,...)
if Phase then
local cf=Phase.conditionOver:AddFunctionAny(Function,...)
return cf
end
return nil
end
function OPERATION:SetConditionFunctionPersistence(ConditionFunction,IsPersistent)
ConditionFunction.persistence=IsPersistent
return self
end
function OPERATION:AddPhaseConditonRepeatAll(Phase,Function,...)
if Phase then
Phase.conditionRepeat:AddFunctionAll(Function,...)
end
return self
end
function OPERATION:GetPhaseConditonOver(Phase,Condition)
return Phase.conditionOver
end
function OPERATION:GetPhaseNactive(Phase)
return Phase.nActive
end
function OPERATION:GetPhaseName(Phase)
Phase=Phase or self.phase
if Phase then
return Phase.name
end
return"None"
end
function OPERATION:GetPhaseActive()
return self.phase
end
function OPERATION:GetPhaseIndex(Phase)
local branch=Phase.branch
for i,_phase in pairs(branch.phases)do
local phase=_phase
if phase.uid==Phase.uid then
return i,branch
end
end
return nil
end
function OPERATION:GetPhaseNext(Branch,PhaseStatus)
Branch=Branch or self:GetBranchActive()
local phases=Branch.phases or{}
local phase=nil
if self.phase and self.phase.branch.uid==Branch.uid then
phase=self.phase
end
local N=#phases
self:T(self.lid..string.format("Getting next phase! Branch=%s, Phases=%d, Status=%s",Branch.name,N,tostring(PhaseStatus)))
if N>0 then
if phase==nil and PhaseStatus==nil then
return phases[1]
end
local n=1
if phase then
n=self:GetPhaseIndex(phase)+1
end
for i=n,N do
local phase=phases[i]
if PhaseStatus==nil or PhaseStatus==phase.status then
return phase
end
end
end
return nil
end
function OPERATION:CountPhases(Status,Branch)
Branch=Branch or self.branchActive
local N=0
for _,_phase in pairs(Branch.phases)do
local phase=_phase
if Status==nil or Status==phase.status then
N=N+1
end
end
return N
end
function OPERATION:AddBranch(Name)
local branch=self:_CreateBranch(Name)
table.insert(self.branches,branch)
return branch
end
function OPERATION:GetBranchMaster()
return self.branchMaster
end
function OPERATION:GetBranchActive()
return self.branchActive or self.branchMaster
end
function OPERATION:GetBranchName(Branch)
Branch=Branch or self:GetBranchActive()
if Branch then
return Branch.name
end
return"None"
end
function OPERATION:AddEdge(PhaseFrom,PhaseTo,ConditionSwitch)
local edge={}
edge.phaseFrom=PhaseFrom
edge.phaseTo=PhaseTo
edge.branchFrom=PhaseFrom.branch
edge.branchTo=PhaseTo.branch
if ConditionSwitch then
edge.conditionSwitch=ConditionSwitch
else
edge.conditionSwitch=CONDITION:New("Edge")
edge.conditionSwitch:SetNoneResult(true)
end
table.insert(edge.branchFrom.edges,edge)
return edge
end
function OPERATION:AddEdgeConditonSwitchAll(Edge,Function,...)
if Edge then
local cf=Edge.conditionSwitch:AddFunctionAll(Function,...)
return cf
end
return nil
end
function OPERATION:AddMission(Mission,Phase)
Mission.phase=Phase
Mission.operation=self
table.insert(self.missions,Mission)
return self
end
function OPERATION:AddTarget(Target,Phase)
Target.phase=Phase
Target.operation=self
table.insert(self.targets,Target)
return self
end
function OPERATION:GetTargets(Phase)
local N={}
for _,_target in pairs(self.targets)do
local target=_target
if target:IsAlive()and(Phase==nil or target.phase==Phase)then
table.insert(N,target)
end
end
return N
end
function OPERATION:CountTargets(Phase)
local N=0
for _,_target in pairs(self.targets)do
local target=_target
if target:IsAlive()and(Phase==nil or target.phase==Phase)then
N=N+1
end
end
return N
end
function OPERATION:AssignCohort(Cohort)
self:T(self.lid..string.format("Assiging Cohort %s to operation",Cohort.name))
self.cohorts[Cohort.name]=Cohort
end
function OPERATION:AssignLegion(Legion)
self.legions[Legion.alias]=Legion
end
function OPERATION:IsAssignedLegion(Legion)
local legion=self.legions[Legion.alias]
if legion then
self:T(self.lid..string.format("Legion %s is assigned to this operation",Legion.alias))
return true
else
self:T(self.lid..string.format("Legion %s is NOT assigned to this operation",Legion.alias))
return false
end
end
function OPERATION:IsAssignedCohort(Cohort)
local cohort=self.cohorts[Cohort.name]
if cohort then
self:T(self.lid..string.format("Cohort %s is assigned to this operation",Cohort.name))
return true
else
local Legion=Cohort.legion
if Legion and self:IsAssignedLegion(Legion)then
self:T(self.lid..string.format("Legion %s of Cohort %s is assigned to this operation",Legion.alias,Cohort.name))
return true
end
self:T(self.lid..string.format("Cohort %s is NOT assigned to this operation",Cohort.name))
return false
end
return nil
end
function OPERATION:IsAssignedCohortOrLegion(Object)
local isAssigned=nil
if Object:IsInstanceOf("COHORT")then
isAssigned=self:IsAssignedCohort(Object)
elseif Object:IsInstanceOf("LEGION")then
isAssigned=self:IsAssignedLegion(Object)
else
self:E(self.lid.."ERROR: Unknown Object!")
end
return isAssigned
end
function OPERATION:IsPlanned()
local is=self:is("Planned")
return is
end
function OPERATION:IsRunning()
local is=self:is("Running")
return is
end
function OPERATION:IsPaused()
local is=self:is("Paused")
return is
end
function OPERATION:IsOver()
local is=self:is("Over")
return is
end
function OPERATION:IsStopped()
local is=self:is("Stopped")
return is
end
function OPERATION:IsNotOver()
local is=not(self:IsOver()or self:IsStopped())
return is
end
function OPERATION:IsPhaseActive(Phase)
if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.ACTIVE then
return true
end
return false
end
function OPERATION:IsPhaseActive(Phase)
local phase=self:GetPhaseActive()
if phase and phase.uid==Phase.uid then
return true
else
return false
end
return nil
end
function OPERATION:IsPhasePlanned(Phase)
if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.PLANNED then
return true
end
return false
end
function OPERATION:IsPhaseOver(Phase)
if Phase and Phase.status and Phase.status==OPERATION.PhaseStatus.OVER then
return true
end
return false
end
function OPERATION:onafterStart(From,Event,To)
self:T(self.lid..string.format("Starting Operation!"))
return self
end
function OPERATION:onafterStatusUpdate(From,Event,To)
local Tnow=timer.getAbsTime()
local fsmstate=self:GetState()
if self:IsPlanned()then
if(self.Tstart and Tnow>self.Tstart or self.Tstart==nil)and(self.conditionStart==nil or self.conditionStart:Evaluate())then
self:Start()
end
elseif self:IsNotOver()then
if(self.Tstop and Tnow>self.Tstop or self.Tstop==nil)and(self.conditionOver==nil or self.conditionOver:Evaluate())then
self:Over()
end
end
if self:IsRunning()then
self:_CheckPhases()
end
if self.verbose>=1 then
local phaseName=self:GetPhaseName()
local branchName=self:GetBranchName()
local NphaseTot=self:CountPhases()
local NphaseAct=self:CountPhases(OPERATION.PhaseStatus.ACTIVE)
local NphasePla=self:CountPhases(OPERATION.PhaseStatus.PLANNED)
local NphaseOvr=self:CountPhases(OPERATION.PhaseStatus.OVER)
local text=string.format("State=%s: Phase=%s [%s], Phases=%d [Active=%d, Planned=%d, Over=%d]",fsmstate,phaseName,branchName,NphaseTot,NphaseAct,NphasePla,NphaseOvr)
self:I(self.lid..text)
end
if self.verbose>=2 then
local text="Phases:"
for i,_phase in pairs(self.branchActive.phases)do
local phase=_phase
text=text..string.format("\n[%d] %s [uid=%d]: status=%s Nact=%d",i,phase.name,phase.uid,tostring(phase.status),phase.nActive)
end
if text=="Phases:"then text=text.." None"end
self:I(self.lid..text)
end
self:__StatusUpdate(-30)
return self
end
function OPERATION:onafterPhaseNext(From,Event,To)
local Phase=self:GetPhaseNext()
if Phase then
self:PhaseChange(Phase)
else
self:Over()
end
return self
end
function OPERATION:onafterPhaseChange(From,Event,To,Phase)
local oldphase="None"
if self.phase then
if self.phase.status~=OPERATION.PhaseStatus.OVER then
self:SetPhaseStatus(self.phase,OPERATION.PhaseStatus.OVER)
end
oldphase=self.phase.name
end
self:I(self.lid..string.format("Phase change: %s --> %s",oldphase,Phase.name))
self.phase=Phase
self:SetPhaseStatus(Phase,OPERATION.PhaseStatus.ACTIVE)
return self
end
function OPERATION:onafterPhaseOver(From,Event,To,Phase)
Phase.conditionOver:RemoveNonPersistant()
end
function OPERATION:onafterBranchSwitch(From,Event,To,Branch,Phase)
self:T(self.lid..string.format("Switching to branch %s",Branch.name))
self.branchActive=Branch
self:PhaseChange(Phase)
return self
end
function OPERATION:onafterOver(From,Event,To)
self:T(self.lid..string.format("Operation is over!"))
self.phase=nil
for _,_branch in pairs(self.branches)do
local branch=_branch
for _,_phase in pairs(branch.phases)do
local phase=_phase
if not self:IsPhaseOver(phase)then
self:SetPhaseStatus(phase,OPERATION.PhaseStatus.OVER)
end
end
end
return self
end
function OPERATION:_CheckPhases()
local phase=self:GetPhaseActive()
if phase and phase.conditionOver then
local isOver=phase.conditionOver:Evaluate()
local Tnow=timer.getAbsTime()
if phase.duration and phase.Tstart and Tnow-phase.Tstart>phase.duration then
isOver=true
end
if isOver then
self:SetPhaseStatus(phase,OPERATION.PhaseStatus.OVER)
end
end
if phase==nil or phase.status==OPERATION.PhaseStatus.OVER then
for _,_edge in pairs(self.branchActive.edges)do
local edge=_edge
if phase then
end
if(edge.phaseFrom==nil)or(phase and edge.phaseFrom.uid==phase.uid)then
local switch=edge.conditionSwitch:Evaluate()
if switch then
local phaseTo=edge.phaseTo or self:GetPhaseNext(edge.branchTo,nil)
if phaseTo then
self:BranchSwitch(edge.branchTo,phaseTo)
else
self:Over()
end
return
end
end
end
self:PhaseNext()
end
end
function OPERATION:_CreatePhase(Name)
self.counterPhase=self.counterPhase+1
local phase={}
phase.uid=self.counterPhase
phase.name=Name or string.format("Phase-%02d",self.counterPhase)
phase.conditionOver=CONDITION:New(Name.." Over")
phase.conditionOver:SetDefaultPersistence(false)
phase.status=OPERATION.PhaseStatus.PLANNED
phase.nActive=0
return phase
end
function OPERATION:_CreateBranch(Name)
self.counterBranch=self.counterBranch+1
local branch={}
branch.uid=self.counterBranch
branch.name=Name or string.format("Branch-%02d",self.counterBranch)
branch.phases={}
branch.edges={}
return branch
end
OPSGROUP={
ClassName="OPSGROUP",
verbose=0,
lid=nil,
groupname=nil,
group=nil,
template=nil,
isLateActivated=nil,
waypoints=nil,
waypoints0=nil,
currentwp=1,
elements={},
taskqueue={},
taskcounter=nil,
taskcurrent=nil,
taskenroute=nil,
taskpaused={},
missionqueue={},
currentmission=nil,
detectedunits={},
detectedgroups={},
attribute=nil,
checkzones=nil,
inzones=nil,
groupinitialized=nil,
wpcounter=1,
radio={},
option={},
optionDefault={},
tacan={},
icls={},
callsign={},
Ndestroyed=0,
Nkills=0,
Nhit=0,
weaponData={},
cargoqueue={},
cargoBay={},
mycarrier={},
carrierLoader={},
carrierUnloader={},
useMEtasks=false,
pausedmissions={},
}
OPSGROUP.ElementStatus={
INUTERO="InUtero",
SPAWNED="Spawned",
PARKING="Parking",
ENGINEON="Engine On",
TAXIING="Taxiing",
TAKEOFF="Takeoff",
AIRBORNE="Airborne",
LANDING="Landing",
LANDED="Landed",
ARRIVED="Arrived",
DEAD="Dead",
}
OPSGROUP.GroupStatus={
INUTERO="InUtero",
PARKING="Parking",
TAXIING="Taxiing",
AIRBORNE="Airborne",
INBOUND="Inbound",
LANDING="Landing",
LANDED="Landed",
ARRIVED="Arrived",
DEAD="Dead",
}
OPSGROUP.TaskStatus={
SCHEDULED="scheduled",
EXECUTING="executing",
PAUSED="paused",
DONE="done",
}
OPSGROUP.TaskType={
SCHEDULED="scheduled",
WAYPOINT="waypoint",
}
OPSGROUP.CarrierStatus={
NOTCARRIER="not carrier",
PICKUP="pickup",
LOADING="loading",
LOADED="loaded",
TRANSPORTING="transporting",
UNLOADING="unloading",
}
OPSGROUP.CargoStatus={
AWAITING="Awaiting carrier",
NOTCARGO="not cargo",
ASSIGNED="assigned to carrier",
BOARDING="boarding",
LOADED="loaded",
}
OPSGROUP.version="1.0.4"
function OPSGROUP:New(group)
local self=BASE:Inherit(self,FSM:New())
if type(group)=="string"then
self.groupname=group
self.group=GROUP:FindByName(self.groupname)
else
self.group=group
self.groupname=group:GetName()
end
self.lid=string.format("OPSGROUP %s | ",tostring(self.groupname))
if self.group then
if not self:IsExist()then
self:E(self.lid.."ERROR: GROUP does not exist! Returning nil")
return nil
end
end
if UTILS.IsInstanceOf(group,"OPSGROUP")then
self:E(self.lid.."ERROR: GROUP is already an OPSGROUP: "..tostring(self.groupname).."!")
return group
end
self:_SetTemplate()
self.dcsgroup=self:GetDCSGroup()
self.controller=self.dcsgroup:getController()
self.category=self.dcsgroup:getCategory()
if self.category==Group.Category.GROUND then
self.isArmygroup=true
elseif self.category==Group.Category.TRAIN then
self.isArmygroup=true
self.isTrain=true
elseif self.category==Group.Category.SHIP then
self.isNavygroup=true
elseif self.category==Group.Category.AIRPLANE then
self.isFlightgroup=true
elseif self.category==Group.Category.HELICOPTER then
self.isFlightgroup=true
self.isHelo=true
else
end
self.attribute=self.group:GetAttribute()
local units=self.group:GetUnits()
if units then
local masterunit=units[1]
if masterunit then
self.descriptors=masterunit:GetDesc()
self.actype=masterunit:GetTypeName()
self.isSubmarine=masterunit:HasAttribute("Submarines")
self.isEPLRS=masterunit:HasAttribute("Datalink")
if self:IsFlightgroup()then
self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000
self.ceiling=self.descriptors.Hmax
self.tankertype=select(2,masterunit:IsTanker())
self.refueltype=select(2,masterunit:IsRefuelable())
end
end
end
self.detectedunits=SET_UNIT:New()
self.detectedgroups=SET_GROUP:New()
self.inzones=SET_ZONE:New()
self:SetDefaultAltitude()
self:SetReturnToLegion()
self.spot={}
self.spot.On=false
self.spot.timer=TIMER:New(self._UpdateLaser,self)
self.spot.Coordinate=COORDINATE:New(0,0,0)
self:SetLaser(1688,true,false,0.5)
self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO
self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER
self:SetCarrierLoaderAllAspect()
self:SetCarrierUnloaderAllAspect()
self.taskcurrent=0
self.taskcounter=0
self:SetStartState("InUtero")
self:AddTransition("InUtero","Spawned","Spawned")
self:AddTransition("*","Respawn","InUtero")
self:AddTransition("*","Dead","InUtero")
self:AddTransition("*","InUtero","InUtero")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("*","Hit","*")
self:AddTransition("*","Damaged","*")
self:AddTransition("*","Destroyed","*")
self:AddTransition("*","UpdateRoute","*")
self:AddTransition("*","PassingWaypoint","*")
self:AddTransition("*","PassedFinalWaypoint","*")
self:AddTransition("*","GotoWaypoint","*")
self:AddTransition("*","Wait","*")
self:AddTransition("*","Stuck","*")
self:AddTransition("*","DetectedUnit","*")
self:AddTransition("*","DetectedUnitNew","*")
self:AddTransition("*","DetectedUnitKnown","*")
self:AddTransition("*","DetectedUnitLost","*")
self:AddTransition("*","DetectedGroup","*")
self:AddTransition("*","DetectedGroupNew","*")
self:AddTransition("*","DetectedGroupKnown","*")
self:AddTransition("*","DetectedGroupLost","*")
self:AddTransition("*","OutOfAmmo","*")
self:AddTransition("*","OutOfGuns","*")
self:AddTransition("*","OutOfRockets","*")
self:AddTransition("*","OutOfBombs","*")
self:AddTransition("*","OutOfMissiles","*")
self:AddTransition("*","OutOfTorpedos","*")
self:AddTransition("*","OutOfMissilesAA","*")
self:AddTransition("*","OutOfMissilesAG","*")
self:AddTransition("*","OutOfMissilesAS","*")
self:AddTransition("*","EnterZone","*")
self:AddTransition("*","LeaveZone","*")
self:AddTransition("*","LaserOn","*")
self:AddTransition("*","LaserOff","*")
self:AddTransition("*","LaserCode","*")
self:AddTransition("*","LaserPause","*")
self:AddTransition("*","LaserResume","*")
self:AddTransition("*","LaserLostLOS","*")
self:AddTransition("*","LaserGotLOS","*")
self:AddTransition("*","TaskExecute","*")
self:AddTransition("*","TaskPause","*")
self:AddTransition("*","TaskCancel","*")
self:AddTransition("*","TaskDone","*")
self:AddTransition("*","MissionStart","*")
self:AddTransition("*","MissionExecute","*")
self:AddTransition("*","MissionCancel","*")
self:AddTransition("*","PauseMission","*")
self:AddTransition("*","UnpauseMission","*")
self:AddTransition("*","MissionDone","*")
self:AddTransition("*","ElementInUtero","*")
self:AddTransition("*","ElementSpawned","*")
self:AddTransition("*","ElementDestroyed","*")
self:AddTransition("*","ElementDead","*")
self:AddTransition("*","ElementDamaged","*")
self:AddTransition("*","ElementHit","*")
self:AddTransition("*","Board","*")
self:AddTransition("*","Embarked","*")
self:AddTransition("*","Disembarked","*")
self:AddTransition("*","Pickup","*")
self:AddTransition("*","Loading","*")
self:AddTransition("*","Load","*")
self:AddTransition("*","Loaded","*")
self:AddTransition("*","LoadingDone","*")
self:AddTransition("*","Transport","*")
self:AddTransition("*","Unloading","*")
self:AddTransition("*","Unload","*")
self:AddTransition("*","Unloaded","*")
self:AddTransition("*","UnloadingDone","*")
self:AddTransition("*","Delivered","*")
self:AddTransition("*","TransportCancel","*")
self:AddTransition("*","HoverStart","*")
self:AddTransition("*","HoverEnd","*")
return self
end
function OPSGROUP:GetCoalition()
return self.group:GetCoalition()
end
function OPSGROUP:GetLifePoints(Element)
local life=0
local life0=0
if Element then
local unit=Element.unit
if unit then
life=unit:GetLife()
life0=unit:GetLife0()
life=math.min(life,life0)
end
else
for _,element in pairs(self.elements)do
local l,l0=self:GetLifePoints(element)
life=life+l
life0=life0+l0
end
end
return life,life0
end
function OPSGROUP:GetAttribute()
return self.attribute
end
function OPSGROUP:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function OPSGROUP:_SetLegion(Legion)
self:T2(self.lid..string.format("Adding opsgroup to legion %s",Legion.alias))
self.legion=Legion
return self
end
function OPSGROUP:SetReturnToLegion(Switch)
if Switch==false then
self.legionReturn=false
else
self.legionReturn=true
end
self:T(self.lid..string.format("Setting ReturnToLegion=%s",tostring(self.legionReturn)))
return self
end
function OPSGROUP:SetDefaultSpeed(Speed)
if Speed then
self.speedCruise=UTILS.KnotsToKmph(Speed)
end
return self
end
function OPSGROUP:GetSpeedCruise()
local speed=UTILS.KmphToKnots(self.speedCruise or self.speedMax*0.7)
return speed
end
function OPSGROUP:SetDefaultAltitude(Altitude)
if Altitude then
self.altitudeCruise=UTILS.FeetToMeters(Altitude)
else
if self:IsFlightgroup()then
if self.isHelo then
self.altitudeCruise=UTILS.FeetToMeters(1500)
else
self.altitudeCruise=UTILS.FeetToMeters(10000)
end
else
self.altitudeCruise=0
end
end
return self
end
function OPSGROUP:GetCruiseAltitude()
local alt=UTILS.MetersToFeet(self.altitudeCruise)
return alt
end
function OPSGROUP:SetAltitude(Altitude,Keep,RadarAlt)
if Altitude then
Altitude=UTILS.FeetToMeters(Altitude)
else
if self:IsFlightgroup()then
if self.isHelo then
Altitude=UTILS.FeetToMeters(1500)
else
Altitude=UTILS.FeetToMeters(10000)
end
else
Altitude=0
end
end
local AltType="BARO"
if RadarAlt then
AltType="RADIO"
end
if self.controller then
self.controller:setAltitude(Altitude,Keep,AltType)
end
return self
end
function OPSGROUP:GetAltitude()
local alt=0
if self.group then
alt=self.group:GetAltitude()
alt=UTILS.MetersToFeet(alt)
end
return alt
end
function OPSGROUP:SetSpeed(Speed,Keep,AltCorrected)
if Speed then
else
Speed=UTILS.KmphToKnots(self.speedMax)
end
if AltCorrected then
local altitude=self:GetAltitude()
Speed=UTILS.KnotsToAltKIAS(Speed,altitude)
end
Speed=UTILS.KnotsToMps(Speed)
if self.controller then
self.controller:setSpeed(Speed,Keep)
end
return self
end
function OPSGROUP:SetDetection(Switch)
self:T(self.lid..string.format("Detection is %s",tostring(Switch)))
self.detectionOn=Switch
return self
end
function OPSGROUP:GetDCSObject()
return self.dcsgroup
end
function OPSGROUP:KnowTarget(TargetObject,KnowType,KnowDist,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.KnowTarget,self,TargetObject,KnowType,KnowDist,0)
else
if TargetObject:IsInstanceOf("GROUP")then
TargetObject=TargetObject:GetUnit(1)
elseif TargetObject:IsInstanceOf("OPSGROUP")then
TargetObject=TargetObject.group:GetUnit(1)
end
local object=TargetObject:GetDCSObject()
for _,_element in pairs(self.elements)do
local element=_element
if element.controller then
element.controller:knowTarget(object,true,true)
end
end
self:T(self.lid..string.format("We should now know target %s",TargetObject:GetName()))
end
return self
end
function OPSGROUP:IsTargetDetected(TargetObject)
local objects={}
if TargetObject:IsInstanceOf("GROUP")then
for _,unit in pairs(TargetObject:GetUnits())do
table.insert(objects,unit:GetDCSObject())
end
elseif TargetObject:IsInstanceOf("OPSGROUP")then
for _,unit in pairs(TargetObject.group:GetUnits())do
table.insert(objects,unit:GetDCSObject())
end
elseif TargetObject:IsInstanceOf("UNIT")or TargetObject:IsInstanceOf("STATIC")then
table.insert(objects,TargetObject:GetDCSObject())
end
for _,object in pairs(objects or{})do
local detected,visible,lastTime,type,distance,lastPos,lastVel=self.controller:isTargetDetected(object,1,2,4,8,16,32)
if detected then
return true
end
for _,_element in pairs(self.elements)do
local element=_element
if element.controller then
local detected,visible,lastTime,type,distance,lastPos,lastVel=
element.controller:isTargetDetected(object,1,2,4,8,16,32)
if detected then
return true
end
end
end
end
return false
end
function OPSGROUP:InWeaponRange(TargetCoord,WeaponBitType,RefCoord)
RefCoord=RefCoord or self:GetCoordinate()
local dist=TargetCoord:Get2DDistance(RefCoord)
if WeaponBitType then
local weapondata=self:GetWeaponData(WeaponBitType)
if weapondata then
if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then
return true
else
return false
end
end
else
for _,_weapondata in pairs(self.weaponData or{})do
local weapondata=_weapondata
if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then
return true
end
end
return false
end
return nil
end
function OPSGROUP:GetCoordinateInRange(TargetCoord,WeaponBitType,RefCoord,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=TargetCoord:HeadingTo(RefCoord)
local dist=RefCoord:Get2DDistance(TargetCoord)
local range=nil
if dist>weapondata.RangeMax then
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<weapondata.RangeMin then
range=weapondata.RangeMin
self:T(self.lid..string.format("Out of min range = %.1f km by %.1f km for weapon %s",weapondata.RangeMin/1000,(weapondata.RangeMin-dist)/1000,tostring(WeaponBitType)))
end
if range then
for _,delta in pairs(dh)do
local h=heading+delta
coordInRange=TargetCoord:Translate(range,h)
if _checkSurface(coordInRange)then
break
end
end
else
self:T(self.lid..string.format("Already in range for weapon %s",tostring(WeaponBitType)))
end
else
self:T(self.lid..string.format("No weapon data for weapon type %s",tostring(WeaponBitType)))
end
return coordInRange
end
function OPSGROUP:SetLaser(Code,CheckLOS,IROff,UpdateTime)
self.spot.Code=Code or 1688
if CheckLOS~=nil then
self.spot.CheckLOS=CheckLOS
else
self.spot.CheckLOS=true
end
self.spot.IRon=not IROff
self.spot.dt=UpdateTime or 0.5
return self
end
function OPSGROUP:GetLaserCode()
return self.spot.Code
end
function OPSGROUP:GetLaserCoordinate()
return self.spot.Coordinate
end
function OPSGROUP:GetLaserTarget()
return self.spot.TargetUnit
end
function OPSGROUP:SetCheckZones(CheckZonesSet)
self.checkzones=CheckZonesSet
return self
end
function OPSGROUP:AddCheckZone(CheckZone)
if not self.checkzones then
self.checkzones=SET_ZONE:New()
end
self.checkzones:AddZone(CheckZone)
return self
end
function OPSGROUP:AddWeaponRange(RangeMin,RangeMax,BitType,ConversionToMeters)
ConversionToMeters=ConversionToMeters or UTILS.NMToMeters
RangeMin=ConversionToMeters(RangeMin or 0)
RangeMax=ConversionToMeters(RangeMax or 10)
local weapon={}
weapon.BitType=BitType or ENUMS.WeaponFlag.Auto
weapon.RangeMax=RangeMax
weapon.RangeMin=RangeMin
self.weaponData=self.weaponData or{}
self.weaponData[tostring(weapon.BitType)]=weapon
return self
end
function OPSGROUP:GetWeaponData(BitType)
BitType=tostring(BitType or ENUMS.WeaponFlag.Auto)
if self.weaponData[BitType]then
return self.weaponData[BitType]
else
return self.weaponData[tostring(ENUMS.WeaponFlag.Auto)]
end
end
function OPSGROUP:GetDetectedUnits()
return self.detectedunits or{}
end
function OPSGROUP:GetDetectedGroups()
return self.detectedgroups or{}
end
function OPSGROUP:GetAmmo0()
return self.ammo
end
function OPSGROUP:GetThreat(ThreatLevelMin,ThreatLevelMax)
ThreatLevelMin=ThreatLevelMin or 1
ThreatLevelMax=ThreatLevelMax or 10
local threat=nil
local level=0
for _,_unit in pairs(self.detectedunits:GetSet())do
local unit=_unit
local threatlevel=unit:GetThreatLevel()
if threatlevel>=ThreatLevelMin and threatlevel<=ThreatLevelMax then
if threatlevel<level then
level=threatlevel
threat=unit
end
end
end
return threat,level
end
function OPSGROUP:GetHighestThreat()
local threat=nil
local levelmax=-1
for _,_unit in pairs(self.detectedunits:GetSet())do
local unit=_unit
local threatlevel=unit:GetThreatLevel()
if threatlevel>levelmax then
threat=unit
levelmax=threatlevel
end
end
return threat,levelmax
end
function OPSGROUP:SetEngageDetectedOn(RangeMax,TargetTypes,EngageZoneSet,NoEngageZoneSet)
if TargetTypes then
if type(TargetTypes)~="table"then
TargetTypes={TargetTypes}
end
else
TargetTypes={"All"}
end
if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE")then
local zoneset=SET_ZONE:New():AddZone(EngageZoneSet)
EngageZoneSet=zoneset
end
if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE")then
local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet)
NoEngageZoneSet=zoneset
end
self.engagedetectedOn=true
self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25)
self.engagedetectedTypes=TargetTypes
self.engagedetectedEngageZones=EngageZoneSet
self.engagedetectedNoEngageZones=NoEngageZoneSet
self:T(self.lid..string.format("Engage detected ON: Rmax=%d NM",UTILS.MetersToNM(self.engagedetectedRmax)))
self:SetDetection(true)
return self
end
function OPSGROUP:SetEngageDetectedOff()
self:T(self.lid..string.format("Engage detected OFF"))
self.engagedetectedOn=false
return self
end
function OPSGROUP:SetRearmOnOutOfAmmo()
self.rearmOnOutOfAmmo=true
return self
end
function OPSGROUP:SetRetreatOnOutOfAmmo()
self.retreatOnOutOfAmmo=true
return self
end
function OPSGROUP:SetReturnOnOutOfAmmo()
self.rtzOnOutOfAmmo=true
return self
end
function OPSGROUP:SetCargoBayLimit(Weight,UnitName)
for _,_element in pairs(self.elements)do
local element=_element
if UnitName==nil or UnitName==element.name then
element.weightMaxCargo=Weight
if element.unit then
element.unit:SetCargoBayWeightLimit(Weight)
end
end
end
return self
end
function OPSGROUP:HasLoS(Coordinate,Element,OffsetElement,OffsetCoordinate)
if Coordinate then
local Vec3={x=Coordinate.x,y=Coordinate.y,z=Coordinate.z}
if OffsetCoordinate then
Vec3=UTILS.VecAdd(Vec3,OffsetCoordinate)
end
local function checklos(vec3)
if vec3 then
if OffsetElement then
vec3=UTILS.VecAdd(vec3,OffsetElement)
end
local _los=land.isVisible(vec3,Vec3)
return _los
end
return nil
end
if Element then
if Element.unit and Element.unit:IsAlive()then
local vec3=Element.unit:GetVec3()
local los=checklos(vec3)
return los
end
else
local gotit=false
for _,_element in pairs(self.elements)do
local element=_element
if element and element.unit and element.unit:IsAlive()then
gotit=true
local vec3=element.unit:GetVec3()
local los=checklos(vec3)
if los then
return true
end
end
end
if gotit then
return false
end
end
end
return nil
end
function OPSGROUP:GetGroup()
return self.group
end
function OPSGROUP:GetName()
return self.groupname
end
function OPSGROUP:GetDCSGroup()
local DCSGroup=Group.getByName(self.groupname)
return DCSGroup
end
function OPSGROUP:GetUnit(UnitNumber)
local DCSUnit=self:GetDCSUnit(UnitNumber)
if DCSUnit then
local unit=UNIT:Find(DCSUnit)
return unit
end
return nil
end
function OPSGROUP:GetDCSUnit(UnitNumber)
local DCSGroup=self:GetDCSGroup()
if DCSGroup then
local unit=DCSGroup:getUnit(UnitNumber or 1)
return unit
else
self:E(self.lid..string.format("ERROR: DCS group does not exist! Cannot get unit"))
end
return nil
end
function OPSGROUP:GetDCSUnits()
local DCSGroup=self:GetDCSGroup()
if DCSGroup then
local units=DCSGroup:getUnits()
return units
end
return nil
end
function OPSGROUP:GetVec2(UnitName)
local vec3=self:GetVec3(UnitName)
if vec3 then
local vec2={x=vec3.x,y=vec3.z}
return vec2
end
return nil
end
function OPSGROUP:GetVec3(UnitName)
local vec3=nil
local carrier=self:_GetMyCarrierElement()
if carrier and carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded()then
local unit=carrier.unit
if unit and unit:IsExist()then
vec3=unit:GetVec3()
return vec3
end
end
if self:IsExist()then
local unit=nil
if UnitName then
unit=Unit.getByName(UnitName)
else
unit=self:GetDCSUnit()
end
if unit then
local vec3=unit:getPoint()
return vec3
end
end
if self.position then
return self.position
end
return nil
end
function OPSGROUP:GetCoordinate(NewObject,UnitName)
local vec3=self:GetVec3(UnitName)or self.position
if vec3 then
self.coordinate=self.coordinate or COORDINATE:New(0,0,0)
self.coordinate.x=vec3.x
self.coordinate.y=vec3.y
self.coordinate.z=vec3.z
if NewObject then
local coord=COORDINATE:NewFromCoordinate(self.coordinate)
return coord
else
return self.coordinate
end
else
self:T(self.lid.."WARNING: Cannot get coordinate!")
end
return nil
end
function OPSGROUP:GetVelocity(UnitName)
if self:IsExist()then
local unit=nil
if UnitName then
unit=Unit.getByName(UnitName)
else
unit=self:GetDCSUnit()
end
if unit then
local velvec3=unit:getVelocity()
local vel=UTILS.VecNorm(velvec3)
return vel
else
self:T(self.lid.."WARNING: Unit does not exist. Cannot get velocity!")
end
else
self:T(self.lid.."WARNING: Group does not exist. Cannot get velocity!")
end
return nil
end
function OPSGROUP:GetHeading(UnitName)
if self:IsExist()then
local unit=nil
if UnitName then
unit=Unit.getByName(UnitName)
else
unit=self:GetDCSUnit()
end
if unit then
local pos=unit:getPosition()
local heading=math.atan2(pos.x.z,pos.x.x)
if heading<0 then
heading=heading+2*math.pi
end
heading=math.deg(heading)
return heading
end
else
self:T(self.lid.."WARNING: Group does not exist. Cannot get heading!")
end
return nil
end
function OPSGROUP:GetOrientation(UnitName)
if self:IsExist()then
local unit=nil
if UnitName then
unit=Unit.getByName(UnitName)
else
unit=self:GetDCSUnit()
end
if unit then
local pos=unit:getPosition()
return pos.x,pos.y,pos.z
end
else
self:T(self.lid.."WARNING: Group does not exist. Cannot get orientation!")
end
return nil
end
function OPSGROUP:GetOrientationX(UnitName)
local X,Y,Z=self:GetOrientation(UnitName)
return X
end
function OPSGROUP:CheckTaskDescriptionUnique(description)
for _,_task in pairs(self.taskqueue)do
local task=_task
if task.description==description then
return false
end
end
return true
end
function OPSGROUP:DespawnUnit(UnitName,Delay,NoEventRemoveUnit)
self:T(self.lid.."Despawn element "..tostring(UnitName))
local element=self:GetElementByName(UnitName)
if element then
local DCSunit=Unit.getByName(UnitName)
if DCSunit then
DCSunit:destroy()
self:ElementInUtero(element)
if not NoEventRemoveUnit then
self:CreateEventRemoveUnit(timer.getTime(),DCSunit)
end
end
end
end
function OPSGROUP:DespawnElement(Element,Delay,NoEventRemoveUnit)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.DespawnElement,self,Element,0,NoEventRemoveUnit)
else
if Element then
local DCSunit=Unit.getByName(Element.name)
if DCSunit then
DCSunit:destroy()
if not NoEventRemoveUnit then
self:CreateEventRemoveUnit(timer.getTime(),DCSunit)
end
end
end
end
return self
end
function OPSGROUP:Despawn(Delay,NoEventRemoveUnit)
if Delay and Delay>0 then
self.scheduleIDDespawn=self:ScheduleOnce(Delay,OPSGROUP.Despawn,self,0,NoEventRemoveUnit)
else
self:T(self.lid..string.format("Despawning Group!"))
local DCSGroup=self:GetDCSGroup()
if DCSGroup then
local units=self:GetDCSUnits()
for i=1,#units do
local unit=units[i]
if unit then
local name=unit:getName()
if name then
self:DespawnUnit(name,0,NoEventRemoveUnit)
end
end
end
end
end
return self
end
function OPSGROUP:ReturnToLegion(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.ReturnToLegion,self)
else
if self.legion then
self:T(self.lid..string.format("Adding asset back to LEGION"))
self.legion:AddAsset(self.group,1)
else
self:E(self.lid..string.format("ERROR: Group does not belong to a LEGION!"))
end
end
return self
end
function OPSGROUP:DestroyUnit(UnitName,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.DestroyUnit,self,UnitName,0)
else
local unit=Unit.getByName(UnitName)
if unit then
local EventTime=timer.getTime()
if self:IsFlightgroup()then
self:CreateEventUnitLost(EventTime,unit)
else
self:CreateEventDead(EventTime,unit)
end
unit:destroy()
end
end
end
function OPSGROUP:Destroy(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.Destroy,self,0)
else
self:T(self.lid.."Destroying group!")
local units=self:GetDCSUnits()
if units then
for _,unit in pairs(units)do
if unit then
self:DestroyUnit(unit:getName())
end
end
end
end
return self
end
function OPSGROUP:Activate(delay)
if delay and delay>0 then
self:T2(self.lid..string.format("Activating late activated group in %d seconds",delay))
self:ScheduleOnce(delay,OPSGROUP.Activate,self)
else
if self:IsAlive()==false then
self:T(self.lid.."Activating late activated group")
self.group:Activate()
self.isLateActivated=false
elseif self:IsAlive()==true then
self:T(self.lid.."WARNING: Activating group that is already activated")
else
self:T(self.lid.."ERROR: Activating group that is does not exist!")
end
end
return self
end
function OPSGROUP:Deactivate(delay)
if delay and delay>0 then
self:ScheduleOnce(delay,OPSGROUP.Deactivate,self)
else
if self:IsAlive()==true then
self.template.lateActivation=true
local template=UTILS.DeepCopy(self.template)
self:_Respawn(0,template)
end
end
return self
end
function OPSGROUP:SelfDestruction(Delay,ExplosionPower,ElementName)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.SelfDestruction,self,0,ExplosionPower,ElementName)
else
for i,_element in pairs(self.elements)do
local element=_element
if ElementName==nil or ElementName==element.name then
local unit=element.unit
if unit and unit:IsAlive()then
unit:Explode(ExplosionPower or 100)
end
end
end
end
return self
end
function OPSGROUP:SetSRS(PathToSRS,Gender,Culture,Voice,Port,PathToGoogleKey,Label,Volume)
self.useSRS=true
local path=PathToSRS or MSRS.path
local port=Port or MSRS.port
self.msrs=MSRS:New(path,self.frequency,self.modulation)
self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice)
self.msrs:SetPort(port)
self.msrs:SetLabel(Label)
if PathToGoogleKey then
self.msrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey)
self.msrs:SetProvider(MSRS.Provider.GOOGLE)
end
self.msrs:SetCoalition(self:GetCoalition())
self.msrs:SetVolume(Volume)
return self
end
function OPSGROUP:RadioTransmission(Text,Delay,SayCallsign,Frequency)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.RadioTransmission,self,Text,0,SayCallsign)
else
if self.useSRS and self.msrs then
local freq,modu,radioon=self:GetRadio()
local coord=self:GetCoordinate()
self.msrs:SetCoordinate(coord)
if Frequency then
self.msrs:SetFrequencies(Frequency)
else
self.msrs:SetFrequencies(freq)
end
self.msrs:SetModulations(modu)
if SayCallsign then
local callsign=self:GetCallsignName()
Text=string.format("%s, %s",callsign,Text)
end
self:T(self.lid..string.format("Radio transmission on %.3f MHz %s: %s",freq,UTILS.GetModulationName(modu),Text))
self.msrs:PlayText(Text)
end
end
return self
end
function OPSGROUP:SetCarrierLoaderAllAspect(Length,Width)
self.carrierLoader.type="front"
self.carrierLoader.length=Length or 50
self.carrierLoader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierLoaderFront(Length,Width)
self.carrierLoader.type="front"
self.carrierLoader.length=Length or 50
self.carrierLoader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierLoaderBack(Length,Width)
self.carrierLoader.type="back"
self.carrierLoader.length=Length or 50
self.carrierLoader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierLoaderStarboard(Length,Width)
self.carrierLoader.type="right"
self.carrierLoader.length=Length or 50
self.carrierLoader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierLoaderPort(Length,Width)
self.carrierLoader.type="left"
self.carrierLoader.length=Length or 50
self.carrierLoader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierUnloaderAllAspect(Length,Width)
self.carrierUnloader.type="front"
self.carrierUnloader.length=Length or 50
self.carrierUnloader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierUnloaderFront(Length,Width)
self.carrierUnloader.type="front"
self.carrierUnloader.length=Length or 50
self.carrierUnloader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierUnloaderBack(Length,Width)
self.carrierUnloader.type="back"
self.carrierUnloader.length=Length or 50
self.carrierUnloader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierUnloaderStarboard(Length,Width)
self.carrierUnloader.type="right"
self.carrierUnloader.length=Length or 50
self.carrierUnloader.width=Width or 20
return self
end
function OPSGROUP:SetCarrierUnloaderPort(Length,Width)
self.carrierUnloader.type="left"
self.carrierUnloader.length=Length or 50
self.carrierUnloader.width=Width or 20
return self
end
function OPSGROUP:IsInZone(Zone)
local vec2=self:GetVec2()
local is=false
if vec2 then
is=Zone:IsVec2InZone(vec2)
else
self:T3(self.lid.."WARNING: Cannot get vec2 at IsInZone()!")
end
return is
end
function OPSGROUP:Get2DDistance(Coordinate)
local a=self:GetVec2()
local b={}
if Coordinate.z then
b.x=Coordinate.x
b.y=Coordinate.z
else
b.x=Coordinate.x
b.y=Coordinate.y
end
local dist=UTILS.VecDist2D(a,b)
return dist
end
function OPSGROUP:IsFlightgroup()
return self.isFlightgroup
end
function OPSGROUP:IsArmygroup()
return self.isArmygroup
end
function OPSGROUP:IsNavygroup()
return self.isNavygroup
end
function OPSGROUP:IsExist()
local DCSGroup=self:GetDCSGroup()
if DCSGroup then
local exists=DCSGroup:isExist()
return exists
end
return nil
end
function OPSGROUP:IsActive()
if self.group then
local active=self.group:IsActive()
return active
end
return nil
end
function OPSGROUP:IsAlive()
if self.group then
local alive=self.group:IsAlive()
return alive
end
return nil
end
function OPSGROUP:IsLateActivated()
return self.isLateActivated
end
function OPSGROUP:IsInUtero()
local is=self:Is("InUtero")and not self:IsDead()
return is
end
function OPSGROUP:IsSpawned()
local is=self:Is("Spawned")
return is
end
function OPSGROUP:IsDead()
return self.isDead
end
function OPSGROUP:IsDestroyed()
return self.isDestroyed
end
function OPSGROUP:IsStopped()
local is=self:Is("Stopped")
return is
end
function OPSGROUP:IsUncontrolled()
return self.isUncontrolled
end
function OPSGROUP:HasPassedFinalWaypoint()
return self.passedfinalwp
end
function OPSGROUP:IsRearming()
local rearming=self:Is("Rearming")or self:Is("Rearm")
return rearming
end
function OPSGROUP:IsOutOfAmmo()
return self.outofAmmo
end
function OPSGROUP:IsOutOfBombs()
return self.outofBombs
end
function OPSGROUP:IsOutOfGuns()
return self.outofGuns
end
function OPSGROUP:IsOutOfMissiles()
return self.outofMissiles
end
function OPSGROUP:IsOutOfTorpedos()
return self.outofTorpedos
end
function OPSGROUP:IsLasing()
return self.spot.On
end
function OPSGROUP:IsRetreating()
local is=self:is("Retreating")or self:is("Retreated")
return is
end
function OPSGROUP:IsRetreated()
local is=self:is("Retreated")
return is
end
function OPSGROUP:IsReturning()
local is=self:is("Returning")
return is
end
function OPSGROUP:IsEngaging()
local is=self:is("Engaging")
return is
end
function OPSGROUP:IsWaiting()
if self.Twaiting then
return true
end
return false
end
function OPSGROUP:IsNotCarrier()
return self.carrierStatus==OPSGROUP.CarrierStatus.NOTCARRIER
end
function OPSGROUP:IsCarrier()
return not self:IsNotCarrier()
end
function OPSGROUP:IsPickingup()
return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP
end
function OPSGROUP:IsLoading()
return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING
end
function OPSGROUP:IsTransporting()
return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING
end
function OPSGROUP:IsUnloading()
return self.carrierStatus==OPSGROUP.CarrierStatus.UNLOADING
end
function OPSGROUP:IsCargo(CheckTransport)
return not self:IsNotCargo(CheckTransport)
end
function OPSGROUP:IsNotCargo(CheckTransport)
local notcargo=self.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO
if notcargo then
return true
else
if CheckTransport then
if self.cargoTransportUID==nil then
return true
else
return false
end
else
return false
end
end
return notcargo
end
function OPSGROUP:_AddMyLift(Transport)
self.mylifts=self.mylifts or{}
self.mylifts[Transport.uid]=true
return self
end
function OPSGROUP:_DelMyLift(Transport)
if self.mylifts then
self.mylifts[Transport.uid]=nil
end
return self
end
function OPSGROUP:IsAwaitingLift(Transport)
if self.mylifts then
for uid,iswaiting in pairs(self.mylifts)do
if Transport==nil or Transport.uid==uid then
if iswaiting==true then
return true
end
end
end
end
return false
end
function OPSGROUP:_GetPausedMission()
if self.pausedmissions and#self.pausedmissions>0 then
for _,mid in pairs(self.pausedmissions)do
if mid then
local mission=self:GetMissionByID(mid)
if mission and mission:IsNotOver()then
return mission
end
end
end
end
return nil
end
function OPSGROUP:_CountPausedMissions()
local N=0
if self.pausedmissions and#self.pausedmissions>0 then
for _,mid in pairs(self.pausedmissions)do
local mission=self:GetMissionByID(mid)
if mission and mission:IsNotOver()then
N=N+1
end
end
end
return N
end
function OPSGROUP:_RemovePausedMission(AuftragsNummer)
if self.pausedmissions and#self.pausedmissions>0 then
for i=#self.pausedmissions,1,-1 do
local mid=self.pausedmissions[i]
if mid==AuftragsNummer then
table.remove(self.pausedmissions,i)
return self
end
end
end
return self
end
function OPSGROUP:IsBoarding(CarrierGroupName)
if CarrierGroupName then
local carrierGroup=self:_GetMyCarrierGroup()
if carrierGroup and carrierGroup.groupname~=CarrierGroupName then
return false
end
end
return self.cargoStatus==OPSGROUP.CargoStatus.BOARDING
end
function OPSGROUP:IsLoaded(CarrierGroupName)
local isloaded=self.cargoStatus==OPSGROUP.CargoStatus.LOADED
if not isloaded then
return false
end
if CarrierGroupName then
if type(CarrierGroupName)~="table"then
CarrierGroupName={CarrierGroupName}
end
for _,CarrierName in pairs(CarrierGroupName)do
local carrierGroup=self:_GetMyCarrierGroup()
if carrierGroup and carrierGroup.groupname==CarrierName then
return isloaded
end
end
return false
end
return isloaded
end
function OPSGROUP:IsBusy()
if self:IsBoarding()then
return true
end
if self:IsRearming()then
return true
end
if self:IsReturning()then
return true
end
if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsUnloading()then
return true
end
if self:IsEngaging()then
return true
end
return false
end
function OPSGROUP:GetWaypoints()
return self.waypoints
end
function OPSGROUP:MarkWaypoints(Duration)
for i,_waypoint in pairs(self.waypoints or{})do
local waypoint=_waypoint
local text=string.format("Waypoint ID=%d of %s",waypoint.uid,self.groupname)
text=text..string.format("\nSpeed=%.1f kts, Alt=%d ft (%s)",UTILS.MpsToKnots(waypoint.speed),UTILS.MetersToFeet(waypoint.alt or 0),"BARO")
if waypoint.marker then
if waypoint.marker.text~=text then
waypoint.marker.text=text
end
else
waypoint.marker=MARKER:New(waypoint.coordinate,text):ToCoalition(self:GetCoalition())
end
end
if Duration then
self:RemoveWaypointMarkers(Duration)
end
return self
end
function OPSGROUP:RemoveWaypointMarkers(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.RemoveWaypointMarkers,self)
else
for i,_waypoint in pairs(self.waypoints or{})do
local waypoint=_waypoint
if waypoint.marker then
waypoint.marker:Remove()
end
end
end
return self
end
function OPSGROUP:GetWaypointByID(uid)
for _,_waypoint in pairs(self.waypoints or{})do
local waypoint=_waypoint
if waypoint.uid==uid then
return waypoint
end
end
return nil
end
function OPSGROUP:GetWaypointByIndex(index)
for i,_waypoint in pairs(self.waypoints)do
local waypoint=_waypoint
if i==index then
return waypoint
end
end
return nil
end
function OPSGROUP:GetWaypointUIDFromIndex(index)
for i,_waypoint in pairs(self.waypoints)do
local waypoint=_waypoint
if i==index then
return waypoint.uid
end
end
return nil
end
function OPSGROUP:GetWaypointIndex(uid)
if uid then
for i,_waypoint in pairs(self.waypoints or{})do
local waypoint=_waypoint
if waypoint.uid==uid then
return i
end
end
end
return nil
end
function OPSGROUP:GetWaypointIndexNext(cyclic,i)
if cyclic==nil then
cyclic=self.adinfinitum
end
local N=#self.waypoints
i=i or self.currentwp
local n=math.min(i+1,N)
if cyclic and i==N then
n=1
end
return n
end
function OPSGROUP:GetWaypointIndexCurrent()
return self.currentwp or 1
end
function OPSGROUP:GetWaypointIndexAfterID(uid)
local index=self:GetWaypointIndex(uid)
if index then
return index+1
else
return#self.waypoints+1
end
end
function OPSGROUP:GetWaypoint(indx)
return self.waypoints[indx]
end
function OPSGROUP:GetWaypointFinal()
return self.waypoints[#self.waypoints]
end
function OPSGROUP:GetWaypointNext(cyclic)
local n=self:GetWaypointIndexNext(cyclic)
return self.waypoints[n]
end
function OPSGROUP:GetWaypointCurrent()
return self.waypoints[self.currentwp]
end
function OPSGROUP:GetWaypointCurrentUID()
local wp=self:GetWaypointCurrent()
if wp then
return wp.uid
end
return nil
end
function OPSGROUP:GetNextWaypointCoordinate(cyclic)
local waypoint=self:GetWaypointNext(cyclic)
return waypoint.coordinate
end
function OPSGROUP:GetWaypointCoordinate(index)
local waypoint=self:GetWaypoint(index)
if waypoint then
return waypoint.coordinate
end
return nil
end
function OPSGROUP:GetWaypointSpeed(indx)
local waypoint=self:GetWaypoint(indx)
if waypoint then
return UTILS.MpsToKnots(waypoint.speed)
end
return nil
end
function OPSGROUP:GetWaypointUID(waypoint)
return waypoint.uid
end
function OPSGROUP:GetWaypointID(indx)
local waypoint=self:GetWaypoint(indx)
if waypoint then
return waypoint.uid
end
return nil
end
function OPSGROUP:GetSpeedToWaypoint(indx)
local speed=self:GetWaypointSpeed(indx)
if speed<=0.01 then
speed=self:GetSpeedCruise()
end
return speed
end
function OPSGROUP:GetDistanceToWaypoint(indx)
local dist=0
if#self.waypoints>0 then
indx=indx or self:GetWaypointIndexNext()
local wp=self:GetWaypoint(indx)
if wp then
local coord=self:GetCoordinate()
dist=coord:Get2DDistance(wp.coordinate)
end
end
return dist
end
function OPSGROUP:GetTimeToWaypoint(indx)
local s=self:GetDistanceToWaypoint(indx)
local v=self:GetVelocity()
local t=s/v
if t==math.inf then
return 365*24*60*60
elseif t==math.nan then
return 0
else
return t
end
end
function OPSGROUP:GetExpectedSpeed()
if self:IsHolding()or self:Is("Rearming")or self:IsWaiting()or self:IsRetreated()then
return 0
else
return self.speedWp or 0
end
end
function OPSGROUP:RemoveWaypointByID(uid)
local index=self:GetWaypointIndex(uid)
if index then
self:RemoveWaypoint(index)
end
return self
end
function OPSGROUP:RemoveWaypoint(wpindex)
if self.waypoints then
local wp=self:GetWaypoint(wpindex)
local istemp=wp.temp or wp.detour or wp.astar or wp.missionUID
local N=#self.waypoints
if N==1 then
self:T(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d! It is the only waypoint and a group needs at least ONE waypoint",wpindex))
return self
end
if wpindex>N then
self:T(self.lid..string.format("ERROR: Cannot remove waypoint with index=%d as there are only N=%d waypoints!",wpindex,N))
return self
end
if wp and wp.marker then
wp.marker:Remove()
end
table.remove(self.waypoints,wpindex)
local n=#self.waypoints
self:T(self.lid..string.format("Removing waypoint UID=%d [temp=%s]: index=%d [currentwp=%d]. N %d-->%d",wp.uid,tostring(istemp),wpindex,self.currentwp,N,n))
if wpindex>self.currentwp then
if self.currentwp>=n and not(self.adinfinitum or istemp)then
self:_PassedFinalWaypoint(true,"Removed FUTURE waypoint we are currently moving to and that was the LAST waypoint")
end
self:_CheckGroupDone(1)
else
if self.currentwp==1 then
if self.adinfinitum then
self.currentwp=#self.waypoints
else
self.currentwp=1
end
else
self.currentwp=self.currentwp-1
end
if(self.adinfinitum or istemp)then
self:_PassedFinalWaypoint(false,"Removed PASSED temporary waypoint")
end
end
end
return self
end
function OPSGROUP:OnEventBirth(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
if self.isFlightgroup then
if EventData.Place then
self.homebase=self.homebase or EventData.Place
self.currbase=EventData.Place
else
self.currbase=nil
end
if self.homebase and not self.destbase then
self.destbase=self.homebase
end
self:T(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned",unitname,self.currbase and self.currbase:GetName()or"unknown"))
else
self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned",unitname))
end
local element=self:GetElementByName(unitname)
if element and element.status~=OPSGROUP.ElementStatus.SPAWNED then
self:T(self.lid..string.format("EVENT: Element %s born ==> spawned",unitname))
self:T2(self.lid..string.format("DCS unit=%s isExist=%s",tostring(EventData.IniDCSUnit:getName()),tostring(EventData.IniDCSUnit:isExist())))
self:ElementSpawned(element)
end
end
end
function OPSGROUP:OnEventHit(EventData)
if EventData and EventData.TgtGroup and EventData.TgtUnit and EventData.TgtGroupName and EventData.TgtGroupName==self.groupname then
self:T2(self.lid..string.format("EVENT: Unit %s hit!",EventData.TgtUnitName))
local unit=EventData.TgtUnit
local group=EventData.TgtGroup
local unitname=EventData.TgtUnitName
local element=self:GetElementByName(unitname)
self.Nhit=self.Nhit or 0
self.Nhit=self.Nhit+1
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
self:ElementHit(element,EventData.IniUnit)
end
end
end
function OPSGROUP:OnEventDead(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
self:T2(self.lid..string.format("EVENT: Unit %s dead!",EventData.IniUnitName))
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed",element.name))
self:ElementDestroyed(element)
end
end
end
function OPSGROUP:OnEventRemoveUnit(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
self:T2(self.lid..string.format("EVENT: Unit %s removed!",EventData.IniUnitName))
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
self:T(self.lid..string.format("EVENT: Element %s removed ==> dead",element.name))
self:ElementDead(element)
end
end
end
function OPSGROUP:OnEventPlayerLeaveUnit(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
self:T2(self.lid..string.format("EVENT: Player left Unit %s!",EventData.IniUnitName))
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
local element=self:GetElementByName(unitname)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
self:T(self.lid..string.format("EVENT: Player left Element %s ==> dead",element.name))
self:ElementDead(element)
end
end
end
function OPSGROUP:OnEventKill(EventData)
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local targetname=tostring(EventData.TgtUnitName)
self:T2(self.lid..string.format("EVENT: Unit %s killed object %s!",tostring(EventData.IniUnitName),targetname))
local target=UNIT:FindByName(targetname)
if not target then
target=STATIC:FindByName(targetname,false)
end
if target then
self:T(self.lid..string.format("EVENT: Unit %s killed unit/static %s!",tostring(EventData.IniUnitName),targetname))
self.Nkills=self.Nkills+1
local mission=self:GetMissionCurrent()
if mission then
mission.Nkills=mission.Nkills+1
end
end
end
end
function OPSGROUP:SetTask(DCSTask)
if self:IsAlive()then
if self.taskenroute and#self.taskenroute>0 then
if tostring(DCSTask.id)=="ComboTask"then
for _,task in pairs(self.taskenroute)do
table.insert(DCSTask.params.tasks,1,task)
end
else
local tasks=UTILS.DeepCopy(self.taskenroute)
table.insert(tasks,DCSTask)
DCSTask=self.group.TaskCombo(self,tasks)
end
end
self.controller:setTask(DCSTask)
local text=string.format("SETTING Task %s",tostring(DCSTask.id))
if tostring(DCSTask.id)=="ComboTask"then
for i,task in pairs(DCSTask.params.tasks)do
text=text..string.format("\n[%d] %s",i,tostring(task.id))
end
end
self:T(self.lid..text)
end
return self
end
function OPSGROUP:PushTask(DCSTask)
if self:IsAlive()then
if self.taskenroute and#self.taskenroute>0 then
if tostring(DCSTask.id)=="ComboTask"then
for _,task in pairs(self.taskenroute)do
table.insert(DCSTask.params.tasks,1,task)
end
else
local tasks=UTILS.DeepCopy(self.taskenroute)
table.insert(tasks,DCSTask)
DCSTask=self.group.TaskCombo(self,tasks)
end
end
self.controller:pushTask(DCSTask)
local text=string.format("PUSHING Task %s",tostring(DCSTask.id))
if tostring(DCSTask.id)=="ComboTask"then
for i,task in pairs(DCSTask.params.tasks)do
text=text..string.format("\n[%d] %s",i,tostring(task.id))
end
end
self:T(self.lid..text)
end
return self
end
function OPSGROUP:HasTaskController()
local hastask=nil
if self.controller then
hastask=self.controller:hasTask()
end
self:T3(self.lid..string.format("Controller hasTask=%s",tostring(hastask)))
return hastask
end
function OPSGROUP:ClearTasks()
local hastask=self:HasTaskController()
if self:IsAlive()and self.controller and self:HasTaskController()then
self:T(self.lid..string.format("CLEARING Tasks"))
self.controller:resetTask()
end
return self
end
function OPSGROUP:AddTask(task,clock,description,prio,duration)
local newtask=self:NewTaskScheduled(task,clock,description,prio,duration)
table.insert(self.taskqueue,newtask)
self:T(self.lid..string.format("Adding SCHEDULED task %s starting at %s",newtask.description,UTILS.SecondsToClock(newtask.time,true)))
self:T3({newtask=newtask})
return newtask
end
function OPSGROUP:NewTaskScheduled(task,clock,description,prio,duration)
self.taskcounter=self.taskcounter+1
local time=timer.getAbsTime()+5
if clock then
if type(clock)=="string"then
time=UTILS.ClockToSeconds(clock)
elseif type(clock)=="number"then
time=timer.getAbsTime()+clock
end
end
local newtask={}
newtask.status=OPSGROUP.TaskStatus.SCHEDULED
newtask.dcstask=task
newtask.description=description or task.id
newtask.prio=prio or 50
newtask.time=time
newtask.id=self.taskcounter
newtask.duration=duration
newtask.waypoint=-1
newtask.type=OPSGROUP.TaskType.SCHEDULED
newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d",self.groupname,newtask.id))
newtask.stopflag:Set(0)
return newtask
end
function OPSGROUP:AddTaskWaypoint(task,Waypoint,description,prio,duration)
Waypoint=Waypoint or self:GetWaypointNext()
if Waypoint then
self.taskcounter=self.taskcounter+1
local newtask={}
newtask.description=description or string.format("Task #%d",self.taskcounter)
newtask.status=OPSGROUP.TaskStatus.SCHEDULED
newtask.dcstask=task
newtask.prio=prio or 50
newtask.id=self.taskcounter
newtask.duration=duration
newtask.time=0
newtask.waypoint=Waypoint.uid
newtask.type=OPSGROUP.TaskType.WAYPOINT
newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d",self.groupname,newtask.id))
newtask.stopflag:Set(0)
table.insert(self.taskqueue,newtask)
self:T(self.lid..string.format("Adding WAYPOINT task %s at WP ID=%d",newtask.description,newtask.waypoint))
self:T3({newtask=newtask})
return newtask
end
return nil
end
function OPSGROUP:AddTaskEnroute(task)
if not self.taskenroute then
self.taskenroute={}
end
local gotit=false
for _,Task in pairs(self.taskenroute)do
if Task.id==task.id then
gotit=true
break
end
end
if not gotit then
self:T(self.lid..string.format("Adding enroute task"))
table.insert(self.taskenroute,task)
end
end
function OPSGROUP:GetTasksWaypoint(id)
local tasks={}
self:_SortTaskQueue()
for _,_task in pairs(self.taskqueue)do
local task=_task
if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then
table.insert(tasks,task)
end
end
return tasks
end
function OPSGROUP:CountTasksWaypoint(id)
local n=0
for _,_task in pairs(self.taskqueue)do
local task=_task
if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then
n=n+1
end
end
return n
end
function OPSGROUP:_SortTaskQueue()
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.time<taskB.time)
end
table.sort(self.taskqueue,_sort)
end
function OPSGROUP:CountRemainingTasks()
local Ntot=0
local Nwp=0
local Nsched=0
for _,_task in pairs(self.taskqueue)do
local task=_task
if task.status==OPSGROUP.TaskStatus.SCHEDULED then
Ntot=Ntot+1
if task.type==OPSGROUP.TaskType.WAYPOINT then
Nwp=Nwp+1
elseif task.type==OPSGROUP.TaskType.SCHEDULED then
Nsched=Nsched+1
end
end
end
return Ntot,Nsched,Nwp
end
function OPSGROUP:RemoveTask(Task)
for i=#self.taskqueue,1,-1 do
local task=self.taskqueue[i]
if task.id==Task.id then
table.remove(self.taskqueue,i)
if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED then
self:_CheckGroupDone(1)
end
return true
end
end
return false
end
function OPSGROUP:_GetNextTask()
if self.taskpaused then
end
if#self.taskqueue==0 then
return nil
end
self:_SortTaskQueue()
local time=timer.getAbsTime()
for _,_task in pairs(self.taskqueue)do
local task=_task
if task.type==OPSGROUP.TaskType.SCHEDULED and task.status==OPSGROUP.TaskStatus.SCHEDULED and time>=task.time then
return task
end
end
return nil
end
function OPSGROUP:GetTaskCurrent()
local task=self:GetTaskByID(self.taskcurrent,OPSGROUP.TaskStatus.EXECUTING)
return task
end
function OPSGROUP:GetTaskByID(id,status)
for _,_task in pairs(self.taskqueue)do
local task=_task
if task.id==id then
if status==nil or status==task.status then
return task
end
end
end
return nil
end
function OPSGROUP:onbeforeTaskExecute(From,Event,To,Task)
local Mission=self:GetMissionByTaskID(Task.id)
if Mission and(Mission.Tpush or#Mission.conditionPush>0)then
if Mission:IsReadyToPush()then
if self:IsWaiting()then
self.Twaiting=nil
self.dTwait=nil
if self:IsFlightgroup()then
self.flaghold:Set(1)
end
end
else
if self:IsWaiting()then
else
local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude)or nil
self:Wait(nil,alt)
end
local dt=Mission.Tpush and Mission.Tpush-timer.getAbsTime()or 20
self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds",Mission.name,dt))
self:__TaskExecute(-dt,Task)
return false
end
end
if Mission and Mission.opstransport then
local delivered=Mission.opstransport:IsCargoDelivered(self.groupname)
if not delivered then
local dt=30
self:T(self.lid..string.format("Mission %s task execute suspended for %d seconds because we were not delivered",Mission.name,dt))
self:__TaskExecute(-dt,Task)
if(self:IsArmygroup()or self:IsNavygroup())and self:IsCruising()then
self:FullStop()
end
return false
end
end
return true
end
function OPSGROUP:onafterTaskExecute(From,Event,To,Task)
local text=string.format("Task %s ID=%d execute",tostring(Task.description),Task.id)
self:T(self.lid..text)
self:T2({Task})
if self.taskcurrent>0 then
self:TaskCancel()
end
self.taskcurrent=Task.id
Task.timestamp=timer.getAbsTime()
Task.status=OPSGROUP.TaskStatus.EXECUTING
if self:GetTaskCurrent()==nil then
table.insert(self.taskqueue,Task)
end
local Mission=self:GetMissionByTaskID(self.taskcurrent)
self:_UpdateTask(Task,Mission)
if Mission then
self:MissionExecute(Mission)
end
end
function OPSGROUP:_UpdateTask(Task,Mission)
Mission=Mission or self:GetMissionByTaskID(self.taskcurrent)
if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then
local followSet=SET_GROUP:New():AddGroup(self.group)
local param=Task.dcstask.params
local followUnit=UNIT:FindByName(param.unitname)
Task.formation=AI_FORMATION:New(followUnit,followSet,AUFTRAG.SpecialTask.FORMATION,"Follow X at given parameters.")
Task.formation:FormationCenterWing(-param.offsetX,50,math.abs(param.altitude),50,param.offsetZ,50)
Task.formation:SetFollowTimeInterval(param.dtFollow)
Task.formation:SetFlightModeFormation(self.group)
Task.formation:Start()
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
local zone=Task.dcstask.params.zone
local surfacetypes=nil
if self:IsArmygroup()then
surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD}
elseif self:IsNavygroup()then
surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER}
end
local Coordinate=zone:GetRandomCoordinate(nil,nil,surfacetypes)
local Speed=Task.dcstask.params.speed and UTILS.MpsToKnots(Task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise)
local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude)or nil
local currUID=self:GetWaypointCurrent().uid
local wp=nil
if self.isFlightgroup then
wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
elseif self.isArmygroup then
wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Task.dcstask.params.formation)
elseif self.isNavygroup then
wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
end
wp.missionUID=Mission and Mission.auftragsnummer or nil
elseif Task.dcstask.id==AUFTRAG.SpecialTask.RECON then
local target=Task.dcstask.params.target
self.reconindecies={}
for i=1,#target.targets do
table.insert(self.reconindecies,i)
end
local n=1
if Task.dcstask.params.randomly then
n=UTILS.GetRandomTableElement(self.reconindecies)
else
table.remove(self.reconindecies,n)
end
local object=target.targets[n]
local zone=object.Object
local Coordinate=zone:GetRandomCoordinate()
local Speed=Task.dcstask.params.speed and UTILS.MpsToKnots(Task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise)
local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude)or nil
local currUID=self:GetWaypointCurrent().uid
local wp=nil
if self.isFlightgroup then
wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
elseif self.isArmygroup then
wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Task.dcstask.params.formation)
elseif self.isNavygroup then
wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
end
wp.missionUID=Mission and Mission.auftragsnummer or nil
elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY or Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then
elseif Task.dcstask.id==AUFTRAG.SpecialTask.REARMING then
local rearmed=self:_CheckAmmoFull()
if rearmed then
self:T2(self.lid.."Ammo already full ==> reaming task done!")
self:TaskDone(Task)
else
self:T2(self.lid.."Ammo not full ==> Rearm()")
self:Rearm()
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then
elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then
if self:IsArmygroup()or self:IsNavygroup()then
self:FullStop()
else
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then
if self:IsArmygroup()or self:IsNavygroup()then
self:__FullStop(0.1)
else
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.AIRDEFENSE or Task.dcstask.id==AUFTRAG.SpecialTask.EWR then
if self:IsArmygroup()or self:IsNavygroup()then
self:FullStop()
else
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then
local target=Task.dcstask.params.target
local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax)or nil
if Task.dcstask.params.speed then
speed=UTILS.MpsToKnots(Task.dcstask.params.speed)
end
if target then
self:EngageTarget(target,speed,Task.dcstask.params.formation)
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLRACETRACK then
if self.isFlightgroup then
self:T("We are Special Auftrag Patrol Race Track, starting now ...")
local aircraft=self:GetGroup()
aircraft:PatrolRaceTrack(Task.dcstask.params.TrackPoint1,Task.dcstask.params.TrackPoint2,Task.dcstask.params.TrackAltitude,Task.dcstask.params.TrackSpeed,Task.dcstask.params.TrackFormation,false,1)
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.HOVER then
if self.isFlightgroup then
self:T("We are Special Auftrag HOVER, hovering now ...")
local alt=Task.dcstask.params.hoverAltitude
local time=Task.dcstask.params.hoverTime
local mSpeed=Task.dcstask.params.missionSpeed or self.speedCruise or 150
local Speed=UTILS.KmphToKnots(mSpeed)
local CruiseAlt=UTILS.FeetToMeters(Task.dcstask.params.missionAltitude or 1000)
local helo=self:GetGroup()
helo:SetSpeed(0.01,true)
helo:SetAltitude(alt,true,"BARO")
self:HoverStart()
local function FlyOn(Helo,Speed,CruiseAlt,Task)
if Helo then
Helo:SetSpeed(Speed,true)
Helo:SetAltitude(CruiseAlt,true,"BARO")
self:T("We are Special Auftrag HOVER, end of hovering now ...")
self:TaskDone(Task)
self:HoverEnd()
end
end
local timer=TIMER:New(FlyOn,helo,Speed,CruiseAlt,Task)
timer:Start(time)
end
elseif Task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then
self:T(self.lid.."Executing task for relocation mission")
local legion=Task.dcstask.params.legion
local Coordinate=legion.spawnzone:GetRandomCoordinate()
local currUID=self:GetWaypointCurrent().uid
local wp=nil
if self.isArmygroup then
self:T2(self.lid.."Routing group to spawn zone of new legion")
wp=ARMYGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID,Mission.optionFormation)
elseif self.isFlightgroup then
self:T2(self.lid.."Routing group to intermediate point near new legion")
Coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.8)
wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID,UTILS.MetersToFeet(self.altitudeCruise))
elseif self.isNavygroup then
self:T2(self.lid.."Routing group to spawn zone of new legion")
wp=NAVYGROUP.AddWaypoint(self,Coordinate,UTILS.KmphToKnots(self.speedCruise),currUID)
else
end
wp.missionUID=Mission and Mission.auftragsnummer or nil
elseif Task.dcstask.id==AUFTRAG.SpecialTask.CAPTUREZONE then
if self:IsEngaging()then
self:T2(self.lid..string.format("CaptureZone: Engaging currently!"))
else
local Coalitions=UTILS.GetCoalitionEnemy(self:GetCoalition(),false)
local zoneCurr=Task.target
if zoneCurr then
self:T(self.lid..string.format("Current target zone=%s owner=%s",zoneCurr:GetName(),zoneCurr:GetOwnerName()))
if zoneCurr:GetOwner()==self:GetCoalition()then
self:T(self.lid..string.format("Zone %s captured ==> Task DONE!",zoneCurr:GetName()))
self:TaskDone(Task)
else
self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName()))
if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then
self:T(self.lid..string.format("Zone %s NOT captured and EXECUTING ==> Find target",zoneCurr:GetName()))
local targetgroup=zoneCurr:GetScannedGroupSet():GetClosestGroup(self.coordinate,Coalitions)
if targetgroup then
self:T(self.lid..string.format("Zone %s NOT captured: engaging target %s",zoneCurr:GetName(),targetgroup:GetName()))
self:EngageTarget(targetgroup)
else
if self:IsFlightgroup()then
self:T(self.lid..string.format("Zone %s not captured but no target group could be found ==> TaskDone as FLIGHTGROUPS cannot capture zones",zoneCurr:GetName()))
self:TaskDone(Task)
else
self:T(self.lid..string.format("Zone %s not captured but no target group could be found. Should be captured in the next zone evaluation.",zoneCurr:GetName()))
end
end
else
self:T(self.lid..string.format("Zone %s NOT captured and NOT EXECUTING",zoneCurr:GetName()))
end
end
else
self:T(self.lid..string.format("NO Current target zone=%s"))
end
end
else
if Task.type==OPSGROUP.TaskType.SCHEDULED or Task.ismission then
local DCSTask=nil
if Task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then
local vec2=self:GetVec2()
local param=Task.dcstask.params
local heading=param.heading or math.random(1,360)
local Altitude=param.altitude or 500
local Alpha=param.angle or math.random(45,85)
local distance=Altitude/math.tan(math.rad(Alpha))
local tvec2=UTILS.Vec2Translate(vec2,distance,heading)
self:T(self.lid..string.format("Barrage: Shots=%s, Altitude=%d m, Angle=%dÂ°, heading=%03dÂ°, distance=%d m",tostring(param.shots),Altitude,Alpha,heading,distance))
DCSTask=CONTROLLABLE.TaskFireAtPoint(nil,tvec2,param.radius,param.shots,param.weaponType,Altitude)
elseif Task.ismission and Task.dcstask.id=='FireAtPoint'then
DCSTask=UTILS.DeepCopy(Task.dcstask)
local ammo=self:GetAmmoTot()
local nAmmo=ammo.Total
local weaponType=DCSTask.params.weaponType or-1
if weaponType==ENUMS.WeaponFlag.CruiseMissile then
nAmmo=ammo.MissilesCR
elseif weaponType==ENUMS.WeaponFlag.AnyRocket then
nAmmo=ammo.Rockets
elseif weaponType==ENUMS.WeaponFlag.Cannons then
nAmmo=ammo.Guns
end
local nShots=DCSTask.params.expendQty or 1
self:T(self.lid..string.format("Fire at point with nshots=%d of %d",nShots,nAmmo))
if nShots==-1 then
nShots=nAmmo
self:T(self.lid..string.format("Fire at point taking max amount of ammo = %d",nShots))
elseif nShots<1 then
local p=nShots
nShots=UTILS.Round(p*nAmmo,0)
self:T(self.lid..string.format("Fire at point taking %.1f percent amount of ammo = %d",p,nShots))
else
nShots=math.min(nShots,nAmmo)
end
DCSTask.params.expendQty=nShots
else
DCSTask=Task.dcstask
end
self:_SandwitchDCSTask(DCSTask,Task)
elseif Task.type==OPSGROUP.TaskType.WAYPOINT then
else
self:T(self.lid.."ERROR: Unknown task type: ")
end
end
end
function OPSGROUP:_SandwitchDCSTask(DCSTask,Task,SetTask,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP._SandwitchDCSTask,self,DCSTask,Task,SetTask)
else
local DCStasks={}
if DCSTask.id=='ComboTask'then
for TaskID,Task in ipairs(DCSTask.params.tasks)do
table.insert(DCStasks,Task)
end
else
table.insert(DCStasks,DCSTask)
end
local TaskCombo=self.group:TaskCombo(DCStasks)
local TaskCondition=self.group:TaskCondition(nil,Task.stopflag:GetName(),1,nil,Task.duration)
local TaskControlled=self.group:TaskControlled(TaskCombo,TaskCondition)
local TaskDone=self.group:TaskFunction("OPSGROUP._TaskDone",self,Task)
local TaskFinal=self.group:TaskCombo({TaskControlled,TaskDone})
if SetTask then
self:SetTask(TaskFinal)
else
self:PushTask(TaskFinal)
end
end
end
function OPSGROUP:onafterTaskCancel(From,Event,To,Task)
local currenttask=self:GetTaskCurrent()
Task=Task or currenttask
if Task then
if currenttask and Task.id==currenttask.id then
local stopflag=Task.stopflag:Get()
local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)",Task.description,Task.id,Task.stopflag:GetName(),stopflag)
self:T(self.lid..text)
Task.stopflag:Set(1)
local done=false
if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then
Task.formation:Stop()
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.RECON then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.AMMOSUPPLY then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.FUELSUPPLY then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.REARMING then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.ALERT5 then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.ONGUARD or Task.dcstask.id==AUFTRAG.SpecialTask.ARMOREDGUARD then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK or Task.dcstask.id==AUFTRAG.SpecialTask.ARMORATTACK then
done=true
elseif Task.dcstask.id==AUFTRAG.SpecialTask.NOTHING then
done=true
elseif stopflag==1 or(not self:IsAlive())or self:IsDead()or self:IsStopped()then
done=true
end
if done then
self:TaskDone(Task)
end
else
self:T(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE",Task.description,Task.id))
self:TaskDone(Task)
end
else
local text=string.format("WARNING: No (current) task to cancel!")
self:T(self.lid..text)
end
end
function OPSGROUP:onbeforeTaskDone(From,Event,To,Task)
local allowed=true
if Task.status==OPSGROUP.TaskStatus.PAUSED then
allowed=false
end
return allowed
end
function OPSGROUP:onafterTaskDone(From,Event,To,Task)
local text=string.format("Task done: %s ID=%d",Task.description,Task.id)
self:T(self.lid..text)
if Task.id==self.taskcurrent then
self.taskcurrent=0
end
Task.status=OPSGROUP.TaskStatus.DONE
if Task.backupROE then
self:SwitchROE(Task.backupROE)
end
local Mission=self:GetMissionByTaskID(Task.id)
if Mission and Mission:IsNotOver()then
local status=Mission:GetGroupStatus(self)
if status~=AUFTRAG.GroupStatus.PAUSED then
if Mission.type==AUFTRAG.Type.CAPTUREZONE and Mission:CountMissionTargets()>0 then
self:T(self.lid.."Remove mission waypoints")
self:_RemoveMissionWaypoints(Mission,false)
if self:IsFlightgroup()then
else
self:T(self.lid.."Task done ==> Route to mission for next opszone")
self:MissionStart(Mission)
return
end
end
local EgressUID=Mission:GetGroupEgressWaypointUID(self)
if EgressUID then
self:T(self.lid..string.format("Task Done but Egress waypoint defined ==> Will call Mission Done once group passed waypoint UID=%d!",EgressUID))
else
self:T(self.lid.."Task Done ==> Mission Done!")
self:MissionDone(Mission)
end
else
if self:IsOnMission(Mission.auftragsnummer)then
self.currentmission=nil
end
self:T(self.lid.."Remove mission waypoints")
self:_RemoveMissionWaypoints(Mission,false)
end
else
if Task.description=="Engage_Target"then
self:T(self.lid.."Task DONE Engage_Target ==> Cruise")
self:Disengage()
end
if Task.description==AUFTRAG.SpecialTask.ONGUARD or Task.description==AUFTRAG.SpecialTask.ARMOREDGUARD or Task.description==AUFTRAG.SpecialTask.NOTHING then
self:T(self.lid.."Task DONE OnGuard ==> Cruise")
self:Cruise()
end
if Task.description=="Task_Land_At"then
self:T(self.lid.."Taske DONE Task_Land_At ==> Wait")
self:Cruise()
self:Wait(20,100)
else
self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec")
self:_CheckGroupDone(1)
end
end
end
function OPSGROUP:AddMission(Mission)
Mission:AddOpsGroup(self)
Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.SCHEDULED)
Mission:Scheduled()
Mission.Nelements=Mission.Nelements+#self.elements
Mission.Ngroups=Mission.Ngroups+1
table.insert(self.missionqueue,Mission)
self.adinfinitum=Mission.DCStask.params.adinfinitum and Mission.DCStask.params.adinfinitum or false
local text=string.format("Added %s mission %s starting at %s, stopping at %s",
tostring(Mission.type),tostring(Mission.name),UTILS.SecondsToClock(Mission.Tstart,true),Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop,true)or"INF")
self:T(self.lid..text)
return self
end
function OPSGROUP:RemoveMission(Mission)
for i=#self.missionqueue,1,-1 do
local mission=self.missionqueue[i]
if mission.auftragsnummer==Mission.auftragsnummer then
local Task=Mission:GetGroupWaypointTask(self)
if Task then
self:RemoveTask(Task)
end
for j=#self.pausedmissions,1,-1 do
local mid=self.pausedmissions[j]
if Mission.auftragsnummer==mid then
table.remove(self.pausedmissions,j)
end
end
table.remove(self.missionqueue,i)
return self
end
end
return self
end
function OPSGROUP:CancelAllMissions()
self:T(self.lid.."Cancelling ALL missions!")
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
local mystatus=mission:GetGroupStatus(self)
if not(mystatus==AUFTRAG.GroupStatus.DONE or mystatus==AUFTRAG.GroupStatus.CANCELLED)then
self:T(self.lid.."Cancelling mission "..tostring(mission:GetName()))
self:MissionCancel(mission)
end
end
end
function OPSGROUP:CountRemainingMissison()
local N=0
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission and mission:IsNotOver()then
local status=mission:GetGroupStatus(self)
if status~=AUFTRAG.GroupStatus.DONE and status~=AUFTRAG.GroupStatus.CANCELLED then
N=N+1
end
end
end
return N
end
function OPSGROUP:CountRemainingTransports()
local N=0
for _,_transport in pairs(self.cargoqueue)do
local transport=_transport
local mystatus=transport:GetCarrierTransportStatus(self)
local status=transport:GetState()
self:T(self.lid..string.format("Transport my status=%s [%s]",mystatus,status))
if transport and mystatus==OPSTRANSPORT.Status.SCHEDULED and status~=OPSTRANSPORT.Status.DELIVERED and status~=OPSTRANSPORT.Status.CANCELLED then
N=N+1
end
end
if N==0 and self.cargoTransport and
self.cargoTransport:GetState()~=OPSTRANSPORT.Status.DELIVERED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.DELIVERED and
self.cargoTransport:GetState()~=OPSTRANSPORT.Status.CANCELLED and self.cargoTransport:GetCarrierTransportStatus(self)~=OPSTRANSPORT.Status.CANCELLED then
N=1
end
return N
end
function OPSGROUP:_GetNextMission()
if self:IsPickingup()or self:IsLoading()or self:IsTransporting()or self:IsUnloading()or self:IsLoaded()then
return nil
end
local Nmissions=#self.missionqueue
if Nmissions==0 then
return nil
end
local function _sort(a,b)
local taskA=a
local taskB=b
return(taskA.prio<taskB.prio)or(taskA.prio==taskB.prio and taskA.Tstart<taskB.Tstart)
end
table.sort(self.missionqueue,_sort)
local vip=math.huge
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.importance and mission.importance<vip then
vip=mission.importance
end
end
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
local isEscort=true
if mission.type==AUFTRAG.Type.ESCORT then
local target=mission:GetTargetData()
if not target:IsAlive()then
isEscort=false
end
end
local isTransport=true
if mission.opstransport then
local cargos=mission.opstransport:GetCargoOpsGroups(false)or{}
for _,_opsgroup in pairs(cargos)do
local opscargo=_opsgroup
if opscargo.groupname==self.groupname then
break
end
end
end
local isScheduled=mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.SCHEDULED
local isReadyToGo=(mission:IsReadyToGo()or self.legion)
local isImportant=(mission.importance==nil or mission.importance<=vip)
local go=isScheduled and isReadyToGo and isImportant and isTransport and isEscort
self:T3(self.lid..string.format("Mission %s [%s]: Go=%s [Scheduled=%s, Ready=%s, Important=%s, Transport=%s, Escort=%s]",mission:GetName(),mission:GetType(),tostring(go),
tostring(isScheduled),tostring(isReadyToGo),tostring(isImportant),tostring(isTransport),tostring(isEscort)))
if go then
return mission
end
end
return nil
end
function OPSGROUP:GetMissionByID(id)
if not id then
return nil
end
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.auftragsnummer==id then
return mission
end
end
return nil
end
function OPSGROUP:IsMissionInQueue(Mission)
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission.auftragsnummer==Mission.auftragsnummer then
return true
end
end
return false
end
function OPSGROUP:IsMissionTypeInQueue(MissionType)
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
if mission:GetType()==MissionType then
return true
end
end
return false
end
function OPSGROUP:GetMissionByTaskID(taskid)
if taskid then
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
local task=mission:GetGroupWaypointTask(self)
if task and task.id and task.id==taskid then
return mission
end
end
end
return nil
end
function OPSGROUP:GetMissionCurrent()
return self:GetMissionByID(self.currentmission)
end
function OPSGROUP:IsOnMission(MissionUID)
if self.currentmission==nil then
return false
else
if MissionUID then
return MissionUID==self.currentmission
else
return true
end
end
return true
end
function OPSGROUP:onbeforeMissionStart(From,Event,To,Mission)
self:T(self.lid..string.format("Starting mission %s, FSM=%s, LateActivated=%s, UnControlled=%s",tostring(Mission.name),self:GetState(),tostring(self:IsLateActivated()),tostring(self:IsUncontrolled())))
local delay=0
if self:IsInUtero()then
if self:IsLateActivated()then
self:Activate(delay)
delay=delay+1
end
end
if self:IsFlightgroup()and self:IsUncontrolled()and Mission.type~=AUFTRAG.Type.ALERT5 then
local fc=FLIGHTGROUP.GetFlightControl(self)
if fc and fc:IsControlling(self)then
FLIGHTGROUP.SetReadyForTakeoff(self,true)
else
self:StartUncontrolled(delay)
end
end
return true
end
function OPSGROUP:onafterMissionStart(From,Event,To,Mission)
local text=string.format("Starting %s Mission %s, target %s",Mission.type,tostring(Mission.name),Mission:GetTargetName())
self:T(self.lid..text)
self.currentmission=Mission.auftragsnummer
Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.STARTED)
Mission:__Started(3)
if self.speedMax>3.6 or true then
self:RouteToMission(Mission,3)
else
self:T(self.lid.."Immobile GROUP!")
local Clock=Mission.Tpush and UTILS.SecondsToClock(Mission.Tpush)or 5
local Task=self:AddTask(Mission.DCStask,Clock,Mission.name,Mission.prio,Mission.duration)
Task.ismission=true
Mission:SetGroupWaypointTask(self,Task)
self:__TaskExecute(3,Task)
end
end
function OPSGROUP:onafterMissionExecute(From,Event,To,Mission)
local text=string.format("Executing %s Mission %s, target %s",Mission.type,tostring(Mission.name),Mission:GetTargetName())
self:T(self.lid..text)
Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.EXECUTING)
Mission:Executing()
if self:IsHolding()and not self:HasPassedFinalWaypoint()then
self:Cruise()
end
if Mission.engagedetectedOn then
self:SetEngageDetectedOn(UTILS.MetersToNM(Mission.engagedetectedRmax),Mission.engagedetectedTypes,Mission.engagedetectedEngageZones,Mission.engagedetectedNoEngageZones)
end
if self.isFlightgroup then
if Mission.prohibitABExecute==true then
self:SetProhibitAfterburner()
self:T(self.lid.."Set prohibit AB")
elseif Mission.prohibitABExecute==false then
self:SetAllowAfterburner()
self:T2(self.lid.."Set allow AB")
end
end
end
function OPSGROUP:onafterPauseMission(From,Event,To)
local Mission=self:GetMissionCurrent()
if Mission then
Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.PAUSED)
local Task=Mission:GetGroupWaypointTask(self)
self:T(self.lid..string.format("Pausing current mission %s. Task=%s",tostring(Mission.name),tostring(Task and Task.description or"WTF")))
self:TaskCancel(Task)
self:_RemoveMissionWaypoints(Mission)
table.insert(self.pausedmissions,1,Mission.auftragsnummer)
end
end
function OPSGROUP:onafterUnpauseMission(From,Event,To)
local mission=self:_GetPausedMission()
if mission then
self:T(self.lid..string.format("Unpausing mission %s [%s]",mission:GetName(),mission:GetType()))
self:MissionStart(mission)
for i,mid in pairs(self.pausedmissions)do
if mid==mission.auftragsnummer then
self:T(self.lid..string.format("Removing paused mission id=%d",mid))
table.remove(self.pausedmissions,i)
break
end
end
else
self:T(self.lid.."ERROR: No mission to unpause!")
end
end
function OPSGROUP:onafterMissionCancel(From,Event,To,Mission)
if self:IsOnMission(Mission.auftragsnummer)then
local Task=Mission:GetGroupWaypointTask(self)
if Task then
self:T(self.lid..string.format("Cancel current mission %s. Task=%s",tostring(Mission.name),tostring(Task and Task.description or"WTF")))
self:TaskCancel(Task)
else
self:MissionDone(Mission)
end
else
Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.CANCELLED)
self:RemoveMission(Mission)
self:_CheckGroupDone(1)
end
end
function OPSGROUP:_RemoveMissionWaypoints(Mission,Silently)
for i=#self.waypoints,1,-1 do
local wp=self.waypoints[i]
if wp.missionUID==Mission.auftragsnummer then
if Silently then
table.remove(self.waypoints,i)
else
self:RemoveWaypoint(i)
end
end
end
end
function OPSGROUP:onafterMissionDone(From,Event,To,Mission)
local text=string.format("Mission %s DONE!",Mission.name)
self:T(self.lid..text)
Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.DONE)
if self:IsOnMission(Mission.auftragsnummer)then
self.currentmission=nil
end
self:_RemoveMissionWaypoints(Mission)
if Mission.patroldata then
Mission.patroldata.noccupied=Mission.patroldata.noccupied-1
AIRWING.UpdatePatrolPointMarker(Mission.patroldata)
end
if Mission.engagedetectedOn then
self:SetEngageDetectedOff()
end
if Mission.optionROE then
self:SwitchROE()
end
if self:IsFlightgroup()and Mission.optionROT then
self:SwitchROT()
end
if Mission.optionAlarm then
self:SwitchAlarmstate()
end
if Mission.optionEPLRS then
self:SwitchEPLRS()
end
if Mission.optionEmission then
self:SwitchEmission()
end
if Mission.optionInvisible then
self:SwitchInvisible()
end
if Mission.optionImmortal then
self:SwitchImmortal()
end
if Mission.optionFormation and self:IsFlightgroup()then
self:SwitchFormation()
end
if Mission.radio then
self:SwitchRadio()
end
if Mission.tacan then
self:_SwitchTACAN()
local cohort=self.cohort
if cohort then
cohort:ReturnTacan(Mission.tacan.Channel)
end
local asset=Mission:GetAssetByName(self.groupname)
if asset then
asset.tacan=nil
end
end
if Mission.icls then
self:_SwitchICLS()
end
if self.legion and Mission.legionReturn~=nil then
self:SetReturnToLegion(Mission.legionReturn)
end
local delay=1
if Mission.type==AUFTRAG.Type.ARTY then
delay=60
elseif Mission.type==AUFTRAG.Type.RELOCATECOHORT then
local legion=Mission.DCStask.params.legion
self:T(self.lid..string.format("Asset relocated to new legion=%s",tostring(legion.alias)))
local asset=Mission:GetAssetByName(self.groupname)
if asset then
asset.wid=legion.uid
end
self.legion=legion
if self.isArmygroup then
self:T2(self.lid.."Adding asset via ReturnToLegion()")
self:ReturnToLegion()
elseif self.isFlightgroup then
self:T2(self.lid.."Adding asset via RTB to new legion airbase")
self:RTB(self.legion.airbase)
end
return
end
if self.isFlightgroup then
if Mission.prohibitAB==true then
self:T2("Setting prohibit AB")
self:SetProhibitAfterburner()
elseif Mission.prohibitAB==false then
self:T2("Setting allow AB")
self:SetAllowAfterburner()
end
end
if self.legion and self.legionReturn==false and self.waypoints and#self.waypoints==1 then
local Coordinate=self:GetCoordinate()
if self.isArmygroup then
ARMYGROUP.AddWaypoint(self,Coordinate,0,nil,nil,false)
elseif self.isNavygroup then
NAVYGROUP.AddWaypoint(self,Coordinate,0,nil,nil,false)
end
self:RemoveWaypoint(1)
self:_PassedFinalWaypoint(true,"Passed final waypoint as group is done with mission but should NOT return to its legion")
end
self:_CheckGroupDone(delay)
end
function OPSGROUP:RouteToMission(mission,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,OPSGROUP.RouteToMission,self,mission)
else
self:T(self.lid..string.format("Route To Mission"))
if self:IsDead()or self:IsStopped()then
self:T(self.lid..string.format("Route To Mission: I am DEAD or STOPPED! Ooops..."))
return
end
if self:IsCargo()then
self:T(self.lid..string.format("Route To Mission: I am CARGO! You cannot route me..."))
return
end
if mission.type==AUFTRAG.Type.OPSTRANSPORT then
self:T(self.lid..string.format("Route To Mission: I am OPSTRANSPORT! Add transport and return..."))
self:AddOpsTransport(mission.opstransport)
return
end
if mission.type==AUFTRAG.Type.ALERT5 then
self:T(self.lid..string.format("Route To Mission: I am ALERT5! Go right to MissionExecute()..."))
self:MissionExecute(mission)
return
end
local uid=self:GetWaypointCurrentUID()
local waypointcoord=nil
local currentcoord=self:GetCoordinate()
local roadcoord=currentcoord:GetClosestPointToRoad()
local roaddist=nil
if roadcoord then
roaddist=currentcoord:Get2DDistance(roadcoord)
end
local targetzone=nil
local randomradius=mission.missionWaypointRadius or 1000
local surfacetypes=nil
if self:IsArmygroup()then
surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD}
elseif self:IsNavygroup()then
surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER}
end
local targetobject=mission:GetObjective(currentcoord,UTILS.GetCoalitionEnemy(self:GetCoalition(),true))
if targetobject then
self:T(self.lid..string.format("Route to mission target object %s",targetobject:GetName()))
end
if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname)then
local tzc=mission.opstransport:GetTZCofCargo(self.groupname)
local pickupzone=tzc.PickupZone
if self:IsInZone(pickupzone)then
self:PauseMission()
self:FullStop()
return
else
waypointcoord=pickupzone:GetRandomCoordinate()
end
elseif mission.type==AUFTRAG.Type.PATROLZONE or
mission.type==AUFTRAG.Type.BARRAGE or
mission.type==AUFTRAG.Type.AMMOSUPPLY or
mission.type==AUFTRAG.Type.FUELSUPPLY or
mission.type==AUFTRAG.Type.REARMING or
mission.type==AUFTRAG.Type.AIRDEFENSE or
mission.type==AUFTRAG.Type.EWR then
targetzone=targetobject
waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes)
elseif mission.type==AUFTRAG.Type.ONGUARD or mission.type==AUFTRAG.Type.ARMOREDGUARD then
waypointcoord=mission:GetMissionWaypointCoord(self.group,nil,surfacetypes)
elseif mission.type==AUFTRAG.Type.NOTHING then
targetzone=targetobject
waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes)
elseif mission.type==AUFTRAG.Type.HOVER then
local zone=targetobject
waypointcoord=zone:GetCoordinate()
elseif mission.type==AUFTRAG.Type.RELOCATECOHORT then
local ToCoordinate=mission.DCStask.params.legion:GetCoordinate()
if self.isFlightgroup then
waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,0.2):SetAltitude(self.altitudeCruise)
elseif self.isArmygroup then
if roadcoord then
waypointcoord=roadcoord
else
waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,100)
end
else
waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate,0.05)
end
elseif mission.type==AUFTRAG.Type.CAPTUREZONE then
targetzone=targetobject:GetZone()
waypointcoord=targetzone:GetRandomCoordinate(nil,nil,surfacetypes)
else
waypointcoord=mission:GetMissionWaypointCoord(self.group,randomradius,surfacetypes)
end
for _,task in pairs(mission.enrouteTasks)do
self:AddTaskEnroute(task)
end
local SpeedToMission=mission.missionSpeed and UTILS.KmphToKnots(mission.missionSpeed)or self:GetSpeedCruise()
if mission.type==AUFTRAG.Type.TROOPTRANSPORT then
mission.DCStask=mission:GetDCSMissionTask(self.group)
local pradius=mission.transportPickupRadius
local pickupZone=ZONE_RADIUS:New("Pickup Zone",mission.transportPickup:GetVec2(),pradius)
for _,_group in pairs(mission.transportGroupSet.Set)do
local group=_group
if group and group:IsAlive()then
local pcoord=pickupZone:GetRandomCoordinate(20,pradius,{land.SurfaceType.LAND,land.SurfaceType.ROAD})
local DCSTask=group:TaskEmbarkToTransport(pcoord,pradius)
group:SetTask(DCSTask,5)
end
end
elseif mission.type==AUFTRAG.Type.ARTY then
local targetcoord=mission:GetTargetCoordinate()
local inRange=self:InWeaponRange(targetcoord,mission.engageWeaponType,waypointcoord)
if inRange then
else
local coordInRange=self:GetCoordinateInRange(targetcoord,mission.engageWeaponType,waypointcoord,surfacetypes)
if coordInRange then
local waypoint=nil
if self:IsFlightgroup()then
waypoint=FLIGHTGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false)
elseif self:IsArmygroup()then
waypoint=ARMYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,mission.optionFormation,false)
elseif self:IsNavygroup()then
waypoint=NAVYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false)
end
waypoint.missionUID=mission.auftragsnummer
waypointcoord=coordInRange
uid=waypoint.uid
end
end
end
local d=currentcoord:Get2DDistance(waypointcoord)
self:T(self.lid..string.format("Distance to ingress waypoint=%.1f m",d))
local waypoint=nil
if self:IsFlightgroup()then
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
if d<1000 or mission.type==AUFTRAG.Type.RELOCATECOHORT then
formation=ENUMS.Formation.Vehicle.OffRoad
end
waypoint=ARMYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,formation,false)
elseif self:IsNavygroup()then
waypoint=NAVYGROUP.AddWaypoint(self,waypointcoord,SpeedToMission,uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false)
end
waypoint.missionUID=mission.auftragsnummer
local waypointtask=self:AddTaskWaypoint(mission.DCStask,waypoint,mission.name,mission.prio,mission.duration)
waypointtask.ismission=true
waypointtask.target=targetobject
mission:SetGroupWaypointTask(self,waypointtask)
mission:SetGroupWaypointIndex(self,waypoint.uid)
local egresscoord=mission:GetMissionEgressCoord()
if egresscoord then
local Ewaypoint=nil
if self:IsFlightgroup()then
Ewaypoint=FLIGHTGROUP.AddWaypoint(self,egresscoord,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
Ewaypoint=NAVYGROUP.AddWaypoint(self,egresscoord,SpeedToMission,waypoint.uid,UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise),false)
end
Ewaypoint.missionUID=mission.auftragsnummer
mission:SetGroupEgressWaypointUID(self,Ewaypoint.uid)
end
if targetzone and self:IsInZone(targetzone)then
self:T(self.lid.."Already in mission zone ==> TaskExecute()")
self:TaskExecute(waypointtask)
return
elseif d<25 then
self:T(self.lid.."Already within 25 meters of mission waypoint ==> TaskExecute()")
self:TaskExecute(waypointtask)
return
end
if self.speedMax<=3.6 or mission.teleport then
self:Teleport(waypointcoord,nil,true)
self:__TaskExecute(-1,waypointtask)
else
if self:IsArmygroup()then
self:Cruise(SpeedToMission)
elseif self:IsNavygroup()then
self:Cruise(SpeedToMission)
elseif self:IsFlightgroup()then
self:UpdateRoute()
end
end
self:_SetMissionOptions(mission)
end
end
function OPSGROUP:_SetMissionOptions(mission)
if mission.optionROE then
self:SwitchROE(mission.optionROE)
end
if mission.optionROT then
self:SwitchROT(mission.optionROT)
end
if mission.optionAlarm then
self:SwitchAlarmstate(mission.optionAlarm)
end
if mission.optionEPLRS then
self:SwitchEPLRS(mission.optionEPLRS)
end
if mission.optionEmission then
self:SwitchEmission(mission.optionEmission)
end
if mission.optionInvisible then
self:SwitchInvisible(mission.optionInvisible)
end
if mission.optionImmortal then
self:SwitchImmortal(mission.optionImmortal)
end
if mission.optionFormation and self:IsFlightgroup()then
self:SwitchFormation(mission.optionFormation)
end
if mission.radio then
self:SwitchRadio(mission.radio.Freq,mission.radio.Modu)
end
if mission.tacan then
self:SwitchTACAN(mission.tacan.Channel,mission.tacan.Morse,mission.tacan.BeaconName,mission.tacan.Band)
end
if mission.icls then
self:SwitchICLS(mission.icls.Channel,mission.icls.Morse,mission.icls.UnitName)
end
if self.isFlightgroup then
if mission.prohibitAB==true then
self:SetProhibitAfterburner()
self:T2("Set prohibit AB")
elseif mission.prohibitAB==false then
self:SetAllowAfterburner()
self:T2("Set allow AB")
end
end
return self
end
function OPSGROUP:_QueueUpdate()
if self:IsExist()then
local mission=self:_GetNextMission()
if mission then
local currentmission=self:GetMissionCurrent()
if currentmission then
if mission.urgent and mission.prio<currentmission.prio then
self:T(self.lid.."FF got urgent mission with higher prio!")
self:MissionCancel(currentmission)
self:__MissionStart(1,mission)
end
else
self:MissionStart(mission)
end
end
end
local ready=true
if self:IsFlightgroup()then
ready=self:IsAirborne()
end
if ready and self.taskcurrent<=0 then
local task=self:_GetNextTask()
if task then
self:TaskExecute(task)
end
end
end
function OPSGROUP:onbeforeWait(From,Event,To,Duration)
local allowed=true
local Tsuspend=nil
local mission=self:GetMissionCurrent()
if mission then
self:PauseMission()
return true
end
if self.taskcurrent>0 then
self:T(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!"))
Tsuspend=-30
allowed=false
end
if self.cargoTransport then
self:T(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!"))
Tsuspend=-30
allowed=false
end
if Tsuspend and not allowed then
self:__Wait(Tsuspend,Duration)
end
return allowed
end
function OPSGROUP:onafterWait(From,Event,To,Duration)
self:FullStop()
self.Twaiting=timer.getAbsTime()
self.dTwait=Duration
end
function OPSGROUP:onafterPassingWaypoint(From,Event,To,Waypoint)
local task=self:GetTaskCurrent()
local mission=nil
if task then
mission=self:GetMissionByTaskID(task.id)
end
if task and task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then
self:RemoveWaypointByID(Waypoint.uid)
local zone=task.dcstask.params.zone
local surfacetypes=nil
if self:IsArmygroup()then
surfacetypes={land.SurfaceType.LAND,land.SurfaceType.ROAD}
elseif self:IsNavygroup()then
surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER}
end
local Coordinate=zone:GetRandomCoordinate(nil,nil,surfacetypes)
local Speed=task.dcstask.params.speed and UTILS.MpsToKnots(task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise)
local Altitude=UTILS.MetersToFeet(task.dcstask.params.altitude or self.altitudeCruise)
local currUID=self:GetWaypointCurrent().uid
local wp=nil
if self.isFlightgroup then
wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
elseif self.isArmygroup then
wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,task.dcstask.params.formation)
elseif self.isNavygroup then
wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
end
wp.missionUID=mission and mission.auftragsnummer or nil
elseif task and task.dcstask.id==AUFTRAG.SpecialTask.RECON then
local target=task.dcstask.params.target
if self.adinfinitum and#self.reconindecies==0 then
self.reconindecies={}
for i=1,#target.targets do
table.insert(self.reconindecies,i)
end
end
if#self.reconindecies>0 then
local n=1
if task.dcstask.params.randomly then
n=UTILS.GetRandomTableElement(self.reconindecies)
else
n=self.reconindecies[1]
table.remove(self.reconindecies,1)
end
local object=target.targets[n]
local zone=object.Object
local Coordinate=zone:GetRandomCoordinate()
local Speed=task.dcstask.params.speed and UTILS.MpsToKnots(task.dcstask.params.speed)or UTILS.KmphToKnots(self.speedCruise)
local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude)or nil
local currUID=self:GetWaypointCurrent().uid
local wp=nil
if self.isFlightgroup then
wp=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
elseif self.isArmygroup then
wp=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,task.dcstask.params.formation)
elseif self.isNavygroup then
wp=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,currUID,Altitude)
end
wp.missionUID=mission and mission.auftragsnummer or nil
else
local wpindex=self:GetWaypointIndex(Waypoint.uid)
if wpindex==nil or wpindex==#self.waypoints then
if not self.adinfinitum or#self.waypoints<=1 then
self:_PassedFinalWaypoint(true,"Passing waypoint and NOT adinfinitum and #self.waypoints<=1")
end
end
self:TaskDone(task)
end
elseif task and task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then
local legion=task.dcstask.params.legion
self:T(self.lid..string.format("Asset arrived at relocation task waypoint ==> Task Done!"))
self:TaskDone(task)
elseif task and task.dcstask.id==AUFTRAG.SpecialTask.REARMING then
self:T(self.lid..string.format("FF Rearming Mission ==> Rearm()"))
self:Rearm()
else
local ntasks=self:_SetWaypointTasks(Waypoint)
local wpindex=self:GetWaypointIndex(Waypoint.uid)
if wpindex==nil or wpindex==#self.waypoints then
if self.adinfinitum then
if Waypoint.missionUID then
else
if#self.waypoints<=1 then
self:_PassedFinalWaypoint(true,"PassingWaypoint: adinfinitum but only ONE WAYPOINT left")
else
self:__UpdateRoute(-0.01,1,1)
end
end
else
self:_PassedFinalWaypoint(true,"PassingWaypoint: wpindex=#self.waypoints (or wpindex=nil)")
end
elseif wpindex==1 then
if self.adinfinitum then
if#self.waypoints<=1 then
self:_PassedFinalWaypoint(true,"PassingWaypoint: adinfinitum but only ONE WAYPOINT left")
else
if not Waypoint.missionUID then
self:__UpdateRoute(-0.01,2)
end
end
end
end
local isEgress=false
if Waypoint.missionUID then
self:T2(self.lid..string.format("Passing mission waypoint UID=%s",tostring(Waypoint.uid)))
local mission=self:GetMissionByID(Waypoint.missionUID)
local EgressUID=mission and mission:GetGroupEgressWaypointUID(self)or nil
isEgress=EgressUID and Waypoint.uid==EgressUID
if isEgress and mission:GetGroupStatus(self)~=AUFTRAG.GroupStatus.DONE then
self:MissionDone(mission)
end
end
if ntasks==0 and self:HasPassedFinalWaypoint()and not isEgress then
self:_CheckGroupDone(0.01)
end
local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s",
tostring(wpindex),#self.waypoints,Waypoint.uid,tostring(self.passedfinalwp),tostring(Waypoint.detour),tostring(Waypoint.astar))
self:T(self.lid..text)
end
local wpnext=self:GetWaypointNext()
if wpnext then
self.speedWp=wpnext.speed
self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp))
end
end
function OPSGROUP:_SetWaypointTasks(Waypoint)
local tasks=self:GetTasksWaypoint(Waypoint.uid)
local text=string.format("WP uid=%d tasks:",Waypoint.uid)
local missiontask=nil
if#tasks>0 then
for i,_task in pairs(tasks)do
local task=_task
text=text..string.format("\n[%d] %s",i,task.description)
if task.ismission then
missiontask=task
end
end
else
text=text.." None"
end
self:T(self.lid..text)
if missiontask then
self:T(self.lid.."Executing mission task")
local mission=self:GetMissionByTaskID(missiontask.id)
if mission then
if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname)then
self:PauseMission()
return
end
end
self:TaskExecute(missiontask)
return 1
end
local taskswp={}
for _,task in pairs(tasks)do
local Task=task
table.insert(taskswp,self.group:TaskFunction("OPSGROUP._TaskExecute",self,Task))
local TaskCondition=self.group:TaskCondition(nil,Task.stopflag:GetName(),1,nil,Task.duration)
table.insert(taskswp,self.group:TaskControlled(Task.dcstask,TaskCondition))
table.insert(taskswp,self.group:TaskFunction("OPSGROUP._TaskDone",self,Task))
end
if#taskswp>0 then
self:SetTask(self.group:TaskCombo(taskswp))
end
return#taskswp
end
function OPSGROUP:onafterPassedFinalWaypoint(From,Event,To)
self:T(self.lid..string.format("Group passed final waypoint"))
end
function OPSGROUP:onafterGotoWaypoint(From,Event,To,UID,Speed)
local n=self:GetWaypointIndex(UID)
if n then
Speed=Speed or self:GetSpeedToWaypoint(n)
self:T(self.lid..string.format("Goto Waypoint UID=%d index=%d from %d at speed %.1f knots",UID,n,self.currentwp,Speed))
self:__UpdateRoute(0.1,n,nil,Speed)
end
end
function OPSGROUP:onafterDetectedUnit(From,Event,To,Unit)
local unitname=Unit and Unit:GetName()or"unknown"
self:T2(self.lid..string.format("Detected unit %s",unitname))
if self.detectedunits:FindUnit(unitname)then
self:DetectedUnitKnown(Unit)
else
self:DetectedUnitNew(Unit)
end
end
function OPSGROUP:onafterDetectedUnitNew(From,Event,To,Unit)
self:T(self.lid..string.format("Detected New unit %s",Unit:GetName()))
self.detectedunits:AddUnit(Unit)
end
function OPSGROUP:onafterDetectedGroup(From,Event,To,Group)
local groupname=Group and Group:GetName()or"unknown"
self:T(self.lid..string.format("Detected group %s",groupname))
if self.detectedgroups:FindGroup(groupname)then
self:DetectedGroupKnown(Group)
else
self:DetectedGroupNew(Group)
end
end
function OPSGROUP:onafterDetectedGroupNew(From,Event,To,Group)
self:T(self.lid..string.format("Detected New group %s",Group:GetName()))
self.detectedgroups:AddGroup(Group)
end
function OPSGROUP:onafterEnterZone(From,Event,To,Zone)
local zonename=Zone and Zone:GetName()or"unknown"
self:T2(self.lid..string.format("Entered Zone %s",zonename))
self.inzones:Add(Zone:GetName(),Zone)
end
function OPSGROUP:onafterLeaveZone(From,Event,To,Zone)
local zonename=Zone and Zone:GetName()or"unknown"
self:T2(self.lid..string.format("Left Zone %s",zonename))
self.inzones:Remove(zonename,true)
end
function OPSGROUP:onbeforeLaserOn(From,Event,To,Target)
if self.spot.On then
return false
end
if Target then
self:SetLaserTarget(Target)
else
self:T(self.lid.."ERROR: No target provided for LASER!")
return false
end
local element=self:GetElementAlive()
if element then
self.spot.element=element
local offsetY=2
if self.isFlightgroup or self.isNavygroup then
offsetY=element.height
end
self.spot.offset={x=0,y=offsetY,z=0}
if self.spot.CheckLOS then
local los=self:HasLoS(self.spot.Coordinate,self.spot.element,self.spot.offset)
if los then
self:LaserGotLOS()
else
self:T(self.lid.."LASER got no LOS currently. Trying to switch the laser on again in 10 sec")
self:__LaserOn(-10,Target)
return false
end
end
else
self:T(self.lid.."ERROR: No element alive for lasing")
return false
end
return true
end
function OPSGROUP:onafterLaserOn(From,Event,To,Target)
if not self.spot.timer:IsRunning()then
self.spot.timer:Start(nil,self.spot.dt)
end
local DCSunit=self.spot.element.unit:GetDCSObject()
self.spot.Laser=Spot.createLaser(DCSunit,self.spot.offset,self.spot.vec3,self.spot.Code or 1688)
if self.spot.IRon then
self.spot.IR=Spot.createInfraRed(DCSunit,self.spot.offset,self.spot.vec3)
end
self.spot.On=true
self.spot.Paused=false
self:T(self.lid.."Switching LASER on")
end
function OPSGROUP:onbeforeLaserOff(From,Event,To)
return self.spot.On or self.spot.Paused
end
function OPSGROUP:onafterLaserOff(From,Event,To)
self:T(self.lid.."Switching LASER off")
if self.spot.On then
self.spot.Laser:destroy()
self.spot.IR:destroy()
self.spot.Laser=nil
self.spot.IR=nil
end
self.spot.timer:Stop()
self.spot.TargetUnit=nil
self.spot.On=false
self.spot.Paused=false
end
function OPSGROUP:onafterLaserPause(From,Event,To)
self:T(self.lid.."Switching LASER off temporarily")
self.spot.Laser:destroy()
self.spot.IR:destroy()
self.spot.Laser=nil
self.spot.IR=nil
self.spot.On=false
self.spot.Paused=true
end
function OPSGROUP:onbeforeLaserResume(From,Event,To)
return self.spot.Paused
end
function OPSGROUP:onafterLaserResume(From,Event,To)
self:T(self.lid.."Resuming LASER")
self.spot.Paused=false
local target=nil
if self.spot.TargetType==0 then
target=self.spot.Coordinate
elseif self.spot.TargetType==1 or self.spot.TargetType==2 then
target=self.spot.TargetUnit
elseif self.spot.TargetType==3 then
target=self.spot.TargetGroup
end
if target then
self:T(self.lid.."Switching LASER on again")
self:LaserOn(target)
end
end
function OPSGROUP:onafterLaserCode(From,Event,To,Code)
self.spot.Code=Code or 1688
self:T2(self.lid..string.format("Setting LASER Code to %d",self.spot.Code))
if self.spot.On then
self:T(self.lid..string.format("New LASER Code is %d",self.spot.Code))
self.spot.Laser:setCode(self.spot.Code)
end
end
function OPSGROUP:onafterLaserLostLOS(From,Event,To)
self.spot.LOS=false
self.spot.lostLOS=true
if self.spot.On then
self:LaserPause()
end
end
function OPSGROUP:onafterLaserGotLOS(From,Event,To)
self.spot.LOS=true
if self.spot.lostLOS then
self.spot.lostLOS=false
if self.spot.Paused then
self:LaserResume()
end
end
end
function OPSGROUP:SetLaserTarget(Target)
if Target then
if Target:IsInstanceOf("SCENERY")then
self.spot.TargetType=0
self.spot.offsetTarget={x=0,y=3,z=0}
elseif Target:IsInstanceOf("POSITIONABLE")then
local target=Target
if target:IsAlive()then
if target:IsInstanceOf("GROUP")then
self.spot.TargetGroup=target
self.spot.TargetUnit=target:GetHighestThreat()
self.spot.TargetType=3
else
self.spot.TargetUnit=target
if target:IsInstanceOf("STATIC")then
self.spot.TargetType=1
elseif target:IsInstanceOf("UNIT")then
self.spot.TargetType=2
end
end
local size,x,y,z=self.spot.TargetUnit:GetObjectSize()
if y then
self.spot.offsetTarget={x=0,y=y*0.75,z=0}
else
self.spot.offsetTarget={x=0,2,z=0}
end
else
self:T("WARNING: LASER target is not alive!")
return
end
elseif Target:IsInstanceOf("COORDINATE")then
self.spot.TargetType=0
self.spot.offsetTarget={x=0,y=0,z=0}
else
self:T(self.lid.."ERROR: LASER target should be a POSITIONABLE (GROUP, UNIT or STATIC) or a COORDINATE object!")
return
end
self.spot.vec3=UTILS.VecAdd(Target:GetVec3(),self.spot.offsetTarget)
self.spot.Coordinate:UpdateFromVec3(self.spot.vec3)
end
end
function OPSGROUP:_UpdateLaser()
if self.spot.TargetUnit then
if self.spot.TargetUnit:IsAlive()then
local vec3=self.spot.TargetUnit:GetVec3()
vec3=UTILS.VecAdd(vec3,self.spot.offsetTarget)
local dist=UTILS.VecDist3D(vec3,self.spot.vec3)
self.spot.vec3=vec3
self.spot.Coordinate:UpdateFromVec3(vec3)
if dist>1 then
if self.spot.On then
self.spot.Laser:setPoint(vec3)
if self.spot.IRon then
self.spot.IR:setPoint(vec3)
end
end
end
else
if self.spot.TargetGroup and self.spot.TargetGroup:IsAlive()then
local unit=self.spot.TargetGroup:GetHighestThreat()
if unit then
self:T(self.lid..string.format("Switching to target unit %s in the group",unit:GetName()))
self.spot.TargetUnit=unit
return
else
self:T(self.lid.."Target is not alive any more ==> switching LASER off")
self:LaserOff()
return
end
else
self:T(self.lid.."Target is not alive any more ==> switching LASER off")
self:LaserOff()
return
end
end
end
if self.spot.CheckLOS then
local los=self:HasLoS(self.spot.Coordinate,self.spot.element,self.spot.offset)
if los then
if self.spot.lostLOS then
self:LaserGotLOS()
end
else
if not self.spot.lostLOS then
self:LaserLostLOS()
end
end
end
end
function OPSGROUP:onbeforeElementSpawned(From,Event,To,Element)
if Element and Element.status==OPSGROUP.ElementStatus.SPAWNED then
self:T2(self.lid..string.format("Element %s is already spawned ==> Transition denied!",Element.name))
return false
end
return true
end
function OPSGROUP:onafterElementInUtero(From,Event,To,Element)
self:T(self.lid..string.format("Element in utero %s",Element.name))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.INUTERO)
end
function OPSGROUP:onafterElementDamaged(From,Event,To,Element)
self:T(self.lid..string.format("Element damaged %s",Element.name))
if Element and(Element.status~=OPSGROUP.ElementStatus.DEAD and Element.status~=OPSGROUP.ElementStatus.INUTERO)then
local lifepoints=0
if Element.DCSunit and Element.DCSunit:isExist()then
lifepoints=Element.DCSunit:getLife()
self:T(self.lid..string.format("Element life %s: %.2f/%.2f",Element.name,lifepoints,Element.life0))
else
self:T(self.lid..string.format("Element.DCSunit %s does not exist!",Element.name))
end
if lifepoints<=1.0 then
self:T(self.lid..string.format("Element %s life %.2f <= 1.0 ==> Destroyed!",Element.name,lifepoints))
self:ElementDestroyed(Element)
end
end
end
function OPSGROUP:onafterElementHit(From,Event,To,Element,Enemy)
Element.Nhit=Element.Nhit+1
self:T(self.lid..string.format("Element hit %s by %s [n=%d, N=%d]",Element.name,Enemy and Enemy:GetName()or"unknown",Element.Nhit,self.Nhit))
self:__Hit(-3,Enemy)
end
function OPSGROUP:onafterHit(From,Event,To,Enemy)
self:T(self.lid..string.format("Group hit by %s",Enemy and Enemy:GetName()or"unknown"))
end
function OPSGROUP:onafterElementDestroyed(From,Event,To,Element)
self:T(self.lid..string.format("Element destroyed %s",Element.name))
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
mission:ElementDestroyed(self,Element)
end
self.Ndestroyed=self.Ndestroyed+1
self:ElementDead(Element)
end
function OPSGROUP:onafterElementDead(From,Event,To,Element)
self:I(self.lid..string.format("Element dead %s at t=%.3f",Element.name,timer.getTime()))
self:_UpdateStatus(Element,OPSGROUP.ElementStatus.DEAD)
if self.spot.On and self.spot.element.name==Element.name then
self:LaserOff()
if self:GetNelements()>0 then
local target=nil
if self.spot.TargetType==0 then
target=self.spot.Coordinate
elseif self.spot.TargetType==1 or self.spot.TargetType==2 then
if self.spot.TargetUnit and self.spot.TargetUnit:IsAlive()then
target=self.spot.TargetUnit
end
elseif self.spot.TargetType==3 then
if self.spot.TargetGroup and self.spot.TargetGroup:IsAlive()then
target=self.spot.TargetGroup
end
end
if target then
self:__LaserOn(-1,target)
end
end
end
for i=#Element.cargoBay,1,-1 do
local mycargo=Element.cargoBay[i]
if mycargo.group then
self:_DelCargobay(mycargo.group)
if mycargo.group and not(mycargo.group:IsDead()or mycargo.group:IsStopped())then
mycargo.group:_RemoveMyCarrier()
if mycargo.reserved then
mycargo.group:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO)
else
for _,cargoelement in pairs(mycargo.group.elements)do
self:T2(self.lid.."Cargo element dead "..cargoelement.name)
mycargo.group:ElementDead(cargoelement)
end
end
end
else
if self.cargoTZC then
for _,_cargo in pairs(self.cargoTZC.Cargos)do
local cargo=_cargo
if cargo.uid==mycargo.cargoUID then
cargo.storage.cargoLost=cargo.storage.cargoLost+mycargo.storageAmount
end
end
end
self:_DelCargobayElement(Element,mycargo)
end
end
end
function OPSGROUP:onafterRespawn(From,Event,To,Template)
self:T(self.lid.."Respawning group!")
local template=UTILS.DeepCopy(Template or self.template)
template.lateActivation=false
self:_Respawn(0,template)
end
function OPSGROUP:Teleport(Coordinate,Delay,NoPauseMission)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP.Teleport,self,Coordinate,0,NoPauseMission)
else
self:T(self.lid.."FF Teleporting...")
if self:IsOnMission()and not NoPauseMission then
self:T(self.lid.."Pausing current mission for telport")
self:PauseMission()
end
local Template=UTILS.DeepCopy(self.template)
Template.lateActivation=self:IsLateActivated()
Template.uncontrolled=false
if self:IsFlightgroup()then
Template.route.points[1]=Coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,300,true,nil,nil,"Spawnpoint")
elseif self:IsArmygroup()then
Template.route.points[1]=Coordinate:WaypointGround(0)
elseif self:IsNavygroup()then
Template.route.points[1]=Coordinate:WaypointNaval(0)
end
local units=Template.units
local d={}
for i=1,#units do
local unit=units[i]
d[i]={x=Coordinate.x+(units[i].x-units[1].x),y=Coordinate.z+units[i].y-units[1].y}
end
for i=#units,1,-1 do
local unit=units[i]
local element=self:GetElementByName(unit.name)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
unit.parking=nil
unit.parking_id=nil
local vec3=element.unit:GetVec3()
local heading=element.unit:GetHeading()
unit.x=d[i].x
unit.y=d[i].y
unit.alt=Coordinate.y
unit.heading=math.rad(heading)
unit.psi=-unit.heading
else
table.remove(units,i)
end
end
self:_Respawn(0,Template,true)
end
end
function OPSGROUP:_Respawn(Delay,Template,Reset)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSGROUP._Respawn,self,0,Template,Reset)
else
self:T2(self.lid.."FF _Respawn")
Template=Template or self:_GetTemplate(true)
self.Ndestroyed=0
self.Nhit=0
if self:IsAlive()then
local units=Template.units
for i=#units,1,-1 do
local unit=units[i]
local element=self:GetElementByName(unit.name)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
if not Reset then
unit.parking=element.parking and element.parking.TerminalID or unit.parking
unit.parking_id=nil
local vec3=element.unit:GetVec3()
local heading=element.unit:GetHeading()
unit.x=vec3.x
unit.y=vec3.z
unit.alt=vec3.y
unit.heading=math.rad(heading)
unit.psi=-unit.heading
end
else
table.remove(units,i)
self.Ndestroyed=self.Ndestroyed+1
end
end
self:Despawn(0,true)
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:_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
self:T2({Template=Template})
self.group=_DATABASE:Spawn(Template)
self.dcsgroup=self:GetDCSGroup()
self.controller=self.dcsgroup:getController()
self.isLateActivated=Template.lateActivation
self.isUncontrolled=Template.uncontrolled
self.isDead=false
self.isDestroyed=false
self.groupinitialized=false
self.wpcounter=1
self.currentwp=1
self:_InitWaypoints()
self:_InitGroup(Template,0.001)
end
end
function OPSGROUP:onafterInUtero(From,Event,To)
self:T(self.lid..string.format("Group inutero at t=%.3f",timer.getTime()))
end
function OPSGROUP:onafterDamaged(From,Event,To)
self:T(self.lid..string.format("Group damaged at t=%.3f",timer.getTime()))
end
function OPSGROUP:onafterDestroyed(From,Event,To)
self:T(self.lid..string.format("Group destroyed at t=%.3f",timer.getTime()))
self.isDestroyed=true
end
function OPSGROUP:onbeforeDead(From,Event,To)
if self.Ndestroyed==#self.elements then
self:Destroyed()
end
end
function OPSGROUP:onafterDead(From,Event,To)
self:T(self.lid..string.format("Group dead at t=%.3f",timer.getTime()))
self.isDead=true
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
self:T(self.lid.."Cancelling mission because group is dead! Mission name "..tostring(mission:GetName()))
self:MissionCancel(mission)
mission:GroupDead(self)
end
self:ClearWaypoints()
self.groupinitialized=false
self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO
self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER
local mycarrier=self:_GetMyCarrierGroup()
if mycarrier and not mycarrier:IsDead()then
mycarrier:_DelCargobay(self)
self:_RemoveMyCarrier()
end
for i,_transport in pairs(self.cargoqueue)do
local transport=_transport
transport:__DeadCarrierGroup(1,self)
end
self.cargoqueue={}
self.cargoTransport=nil
self.cargoTZC=nil
if self.Ndestroyed==#self.elements then
if self.cohort then
self.cohort:DelGroup(self.groupname)
end
else
end
if self.legion then
if not self:IsInUtero()then
local asset=self.legion:GetAssetByName(self.groupname)
local request=self.legion:GetRequestByID(asset.rid)
self.legion:AssetDead(asset,request)
end
self:__Stop(-5)
elseif not self.isAI then
self:__Stop(-1)
end
end
function OPSGROUP:onbeforeStop(From,Event,To)
if self:IsAlive()then
self:T(self.lid..string.format("WARNING: Group is still alive! Will not stop the FSM. Use :Despawn() instead"))
return false
end
return true
end
function OPSGROUP:onafterStop(From,Event,To)
self:UnHandleEvent(EVENTS.Birth)
self:UnHandleEvent(EVENTS.Dead)
self:UnHandleEvent(EVENTS.RemoveUnit)
if self.isFlightgroup then
self:UnHandleEvent(EVENTS.EngineStartup)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.EngineShutdown)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self.currbase=nil
elseif self.isArmygroup then
self:UnHandleEvent(EVENTS.Hit)
end
for _,_mission in pairs(self.missionqueue)do
local mission=_mission
self:MissionCancel(mission)
end
self.timerCheckZone:Stop()
self.timerQueueUpdate:Stop()
self.timerStatus:Stop()
self.CallScheduler:Clear()
if self.Scheduler then
self.Scheduler:Clear()
end
if self.flightcontrol then
for _,_element in pairs(self.elements)do
local element=_element
if element.parking then
self.flightcontrol:SetParkingFree(element.parking)
end
end
self.flightcontrol:_RemoveFlight(self)
end
if self:IsAlive()and not(self:IsDead()or self:IsStopped())then
local life,life0=self:GetLifePoints()
local state=self:GetState()
local text=string.format("WARNING: Group is still alive! Current state=%s. Life points=%d/%d. Use OPSGROUP:Destroy() or OPSGROUP:Despawn() for a clean stop",state,life,life0)
self:T(self.lid..text)
end
_DATABASE.FLIGHTGROUPS[self.groupname]=nil
self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE")
end
function OPSGROUP:onafterOutOfAmmo(From,Event,To)
self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime()))
end
function OPSGROUP:_CheckCargoTransport()
local Time=timer.getAbsTime()
if self.verbose>=1 then
local text=""
for _,_element in pairs(self.elements)do
local element=_element
for _,_cargo in pairs(element.cargoBay)do
local cargo=_cargo
if cargo.group then
text=text..string.format("\n- %s in carrier %s, reserved=%s",tostring(cargo.group:GetName()),tostring(element.name),tostring(cargo.reserved))
else
text=text..string.format("\n- storage %s=%d kg in carrier %s [UID=%s]",
tostring(cargo.storageType),tostring(cargo.storageAmount*cargo.storageWeight),tostring(element.name),tostring(cargo.cargoUID))
end
end
end
if text==""then
text=" empty"
end
self:T(self.lid.."Cargo bay:"..text)
end
if self.verbose>=3 then
local text=""
for i,_transport in pairs(self.cargoqueue)do
local transport=_transport
local pickupzone=transport:GetPickupZone()
local deployzone=transport:GetDeployZone()
local pickupname=pickupzone and pickupzone:GetName()or"unknown"
local deployname=deployzone and deployzone:GetName()or"unknown"
text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s",i,transport.uid,transport:GetState(),pickupname,deployname)
for j,_cargo in pairs(transport:GetCargos())do
local cargo=_cargo
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
local state=cargo.opsgroup:GetState()
local status=cargo.opsgroup.cargoStatus
local name=cargo.opsgroup.groupname
local carriergroup,carrierelement,reserved=cargo.opsgroup:_GetMyCarrier()
local carrierGroupname=carriergroup and carriergroup.groupname or"none"
local carrierElementname=carrierelement and carrierelement.name or"none"
text=text..string.format("\n  (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s",j,name,state,status,carrierGroupname,carrierElementname,tostring(cargo.delivered))
else
end
end
end
if text~=""then
self:T(self.lid.."Cargo queue:"..text)
end
end
if self.cargoTransport and self.cargoTransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.DELIVERED then
self:DelOpsTransport(self.cargoTransport)
self.cargoTransport=nil
self.cargoTZC=nil
end
local mission=self:GetMissionCurrent()
if(not self.cargoTransport)and(mission==nil or mission.type==AUFTRAG.Type.NOTHING)then
self.cargoTransport=self:_GetNextCargoTransport()
if self.cargoTransport and mission then
self:MissionCancel(mission)
end
if self.cargoTransport and not self:IsActive()then
self:Activate()
end
end
if self.cargoTransport then
if self:IsNotCarrier()then
self.Tpickingup=nil
self.Tloading=nil
self.Ttransporting=nil
self.Tunloading=nil
self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self)
if self.cargoTZC then
self:T(self.lid..string.format("Not carrier ==> pickup at %s [TZC UID=%d]",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid))
self:__Pickup(-1)
else
self:T2(self.lid.."Not carrier ==> No TZC found")
end
elseif self:IsPickingup()then
self.Tpickingup=self.Tpickingup or Time
local tpickingup=Time-self.Tpickingup
self:T(self.lid..string.format("Picking up at %s [TZC UID=%d] for %s sec...",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid,tpickingup))
elseif self:IsLoading()then
self.Tloading=self.Tloading or Time
local tloading=Time-self.Tloading
self:T(self.lid..string.format("Loading at %s [TZC UID=%d] for %.1f sec...",self.cargoTZC.PickupZone and self.cargoTZC.PickupZone:GetName()or"unknown",self.cargoTZC.uid,tloading))
local boarding=false
local gotcargo=false
for _,_cargo in pairs(self.cargoTZC.Cargos)do
local cargo=_cargo
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
if cargo.opsgroup and cargo.opsgroup:IsBoarding(self.groupname)then
boarding=true
end
if cargo.opsgroup and cargo.opsgroup:IsLoaded(self.groupname)then
gotcargo=true
end
else
local mycargo=self:_GetMyCargoBayFromUID(cargo.uid)
if mycargo and mycargo.storageAmount>0 then
gotcargo=true
end
end
end
if gotcargo and self.cargoTransport:_CheckRequiredCargos(self.cargoTZC,self)and not boarding then
self:T(self.lid.."Boarding/loading finished ==> Loaded")
self.Tloading=nil
self:LoadingDone()
else
self:Loading()
end
elseif self:IsTransporting()then
self.Ttransporting=self.Ttransporting or Time
local ttransporting=Time-self.Ttransporting
self:T(self.lid.."Transporting (nothing to do)")
elseif self:IsUnloading()then
self.Tunloading=self.Tunloading or Time
local tunloading=Time-self.Tunloading
self:T(self.lid.."Unloading ==> Checking if all cargo was delivered")
local delivered=true
for _,_cargo in pairs(self.cargoTZC.Cargos)do
local cargo=_cargo
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
local carrierGroup=cargo.opsgroup:_GetMyCarrierGroup()
if(carrierGroup and carrierGroup:GetName()==self:GetName())and not cargo.delivered then
delivered=false
break
end
else
local mycargo=self:_GetMyCargoBayFromUID(cargo.uid)
if mycargo and not cargo.delivered then
delivered=false
break
end
end
end
if delivered then
self:T(self.lid.."Unloading finished ==> UnloadingDone")
self:UnloadingDone()
else
self:Unloading()
end
end
if self.verbose>=2 and self.cargoTransport then
local pickupzone=self.cargoTransport:GetPickupZone(self.cargoTZC)
local deployzone=self.cargoTransport:GetDeployZone(self.cargoTZC)
local pickupname=pickupzone and pickupzone:GetName()or"unknown"
local deployname=deployzone and deployzone:GetName()or"unknown"
local text=string.format("Carrier [%s]: %s --> %s",self.carrierStatus,pickupname,deployname)
for _,_cargo in pairs(self.cargoTransport:GetCargos(self.cargoTZC))do
local cargo=_cargo
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
local name=cargo.opsgroup:GetName()
local gstatus=cargo.opsgroup:GetState()
local cstatus=cargo.opsgroup.cargoStatus
local weight=cargo.opsgroup:GetWeightTotal()
local carriergroup,carrierelement,reserved=cargo.opsgroup:_GetMyCarrier()
local carrierGroupname=carriergroup and carriergroup.groupname or"none"
local carrierElementname=carrierelement and carrierelement.name or"none"
text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s",name,weight,gstatus,cstatus,carrierElementname,carrierGroupname,tostring(cargo.delivered))
else
end
end
self:I(self.lid..text)
end
end
return self
end
function OPSGROUP:_IsInCargobay(OpsGroup)
for _,_element in pairs(self.elements)do
local element=_element
for _,_cargo in pairs(element.cargoBay)do
local cargo=_cargo
if cargo.group.groupname==OpsGroup.groupname then
return true
end
end
end
return false
end
function OPSGROUP:_AddCargobay(CargoGroup,CarrierElement,Reserved)
local cargo=self:_GetCargobay(CargoGroup)
if cargo then
cargo.reserved=Reserved
else
cargo={}
cargo.group=CargoGroup
cargo.reserved=Reserved
table.insert(CarrierElement.cargoBay,cargo)
end
CargoGroup:_SetMyCarrier(self,CarrierElement,Reserved)
self.cargoBay[CargoGroup.groupname]=CarrierElement.name
if not Reserved then
local weight=CargoGroup:GetWeightTotal()
self:AddWeightCargo(CarrierElement.name,weight)
end
return self
end
function OPSGROUP:_AddCargobayStorage(CarrierElement,CargoUID,StorageType,StorageAmount,StorageWeight)
local MyCargo=self:_CreateMyCargo(CargoUID,nil,StorageType,StorageAmount,StorageWeight)
self:_AddMyCargoBay(MyCargo,CarrierElement)
end
function OPSGROUP:_CreateMyCargo(CargoUID,OpsGroup,StorageType,StorageAmount,StorageWeight)
local cargo={}
cargo.cargoUID=CargoUID
cargo.group=OpsGroup
cargo.storageType=StorageType
cargo.storageAmount=StorageAmount
cargo.storageWeight=StorageWeight
cargo.reserved=false
return cargo
end
function OPSGROUP:_AddMyCargoBay(MyCargo,CarrierElement)
table.insert(CarrierElement.cargoBay,MyCargo)
if not MyCargo.reserved then
local weight=0
if MyCargo.group then
weight=MyCargo.group:GetWeightTotal()
else
weight=MyCargo.storageAmount*MyCargo.storageWeight
end
self:AddWeightCargo(CarrierElement.name,weight)
end
end
function OPSGROUP:_GetMyCargoBayFromUID(uid)
for _,_element in pairs(self.elements)do
local element=_element
for i,_mycargo in pairs(element.cargoBay)do
local mycargo=_mycargo
if mycargo.cargoUID and mycargo.cargoUID==uid then
return mycargo,element,i
end
end
end
return nil,nil,nil
end
function OPSGROUP:GetCargoGroups(CarrierName)
local cargos={}
for _,_element in pairs(self.elements)do
local element=_element
if CarrierName==nil or element.name==CarrierName then
for _,_cargo in pairs(element.cargoBay)do
local cargo=_cargo
if not cargo.reserved then
table.insert(cargos,cargo.group)
end
end
end
end
return cargos
end
function OPSGROUP:_GetCargobay(CargoGroup)
local CarrierElement=nil
local cargobayIndex=nil
local reserved=nil
for i,_element in pairs(self.elements)do
local element=_element
for j,_cargo in pairs(element.cargoBay)do
local cargo=_cargo
if cargo.group and cargo.group.groupname==CargoGroup.groupname then
return cargo,j,element
end
end
end
return nil,nil,nil
end
function OPSGROUP:_GetCargobayElement(Element,CargoUID)
self:T3({Element=Element,CargoUID=CargoUID})
for i,_mycargo in pairs(Element.cargoBay)do
local mycargo=_mycargo
if mycargo.cargoUID and mycargo.cargoUID==CargoUID then
return mycargo
end
end
return nil
end
function OPSGROUP:_DelCargobayElement(Element,MyCargo)
for i,_mycargo in pairs(Element.cargoBay)do
local mycargo=_mycargo
if mycargo.cargoUID and MyCargo.cargoUID and mycargo.cargoUID==MyCargo.cargoUID then
if MyCargo.group then
self:RedWeightCargo(Element.name,MyCargo.group:GetWeightTotal())
else
self:RedWeightCargo(Element.name,MyCargo.storageAmount*MyCargo.storageWeight)
end
table.remove(Element.cargoBay,i)
return true
end
end
return false
end
function OPSGROUP:_DelCargobay(CargoGroup)
if self.cargoBay[CargoGroup.groupname]then
self.cargoBay[CargoGroup.groupname]=nil
end
local cargoBayItem,cargoBayIndex,CarrierElement=self:_GetCargobay(CargoGroup)
if cargoBayItem and cargoBayIndex then
self:T(self.lid..string.format("Removing cargo group %s from cargo bay (index=%d) of carrier %s",CargoGroup:GetName(),cargoBayIndex,CarrierElement.name))
table.remove(CarrierElement.cargoBay,cargoBayIndex)
if not cargoBayItem.reserved then
local weight=CargoGroup:GetWeightTotal()
self:RedWeightCargo(CarrierElement.name,weight)
end
return true
end
self:T(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!")
return false
end
function OPSGROUP:_GetNextCargoTransport()
local coord=self:GetCoordinate()
local function _sort(a,b)
local transportA=a
local transportB=b
return(transportA.prio<transportB.prio)
end
table.sort(self.cargoqueue,_sort)
local vip=math.huge
for _,_cargotransport in pairs(self.cargoqueue)do
local cargotransport=_cargotransport
if cargotransport.importance and cargotransport.importance<vip then
vip=cargotransport.importance
end
end
for _,_cargotransport in pairs(self.cargoqueue)do
local cargotransport=_cargotransport
local carrierstatusScheduled=cargotransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED
if cargotransport:IsReadyToGo()and carrierstatusScheduled and(cargotransport.importance==nil or cargotransport.importance<=vip)and not self:_CheckDelivered(cargotransport)then
cargotransport:Executing()
cargotransport:SetCarrierTransportStatus(self,OPSTRANSPORT.Status.EXECUTING)
return cargotransport
end
end
return nil
end
function OPSGROUP:_CheckDelivered(CargoTransport)
local done=true
for _,_cargo in pairs(CargoTransport:GetCargos())do
local cargo=_cargo
if self:CanCargo(cargo)then
if cargo.delivered then
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup==nil then
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsDead()or cargo.opsgroup:IsStopped())then
else
done=false
end
end
end
self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s",CargoTransport.uid,CargoTransport:GetState(),tostring(done)))
return done
end
function OPSGROUP:_CheckGoPickup(CargoTransport)
local done=true
if CargoTransport then
for _,_cargo in pairs(CargoTransport:GetCargos())do
local cargo=_cargo
if self:CanCargo(cargo)then
if cargo.delivered then
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup==nil or cargo.opsgroup:IsDead()or cargo.opsgroup:IsStopped())then
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsLoaded(CargoTransport:_GetCarrierNames()))then
else
done=false
end
end
end
self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s",CargoTransport.uid,CargoTransport:GetState(),tostring(done)))
end
return done
end
function OPSGROUP:AddOpsTransport(OpsTransport)
OpsTransport:Scheduled()
OpsTransport:_AddCarrier(self)
table.insert(self.cargoqueue,OpsTransport)
self:T(self.lid.."Adding transport to carrier, #self.cargoqueue="..#self.cargoqueue)
return self
end
function OPSGROUP:DelOpsTransport(CargoTransport)
for i=#self.cargoqueue,1,-1 do
local transport=self.cargoqueue[i]
if transport.uid==CargoTransport.uid then
self:T(self.lid..string.format("Removing transport UID=%d",transport.uid))
table.remove(self.cargoqueue,i)
CargoTransport:_DelCarrier(self)
return self
end
end
return self
end
function OPSGROUP:GetOpsTransportByUID(uid)
for i=#self.cargoqueue,1,-1 do
local transport=self.cargoqueue[i]
if transport.uid==uid then
return transport
end
end
return nil
end
function OPSGROUP:GetWeightTotal(UnitName,IncludeReserved)
local weight=0
for _,_element in pairs(self.elements)do
local element=_element
if(UnitName==nil or UnitName==element.name)and element.status~=OPSGROUP.ElementStatus.DEAD then
weight=weight+element.weightEmpty
for _,_cargo in pairs(element.cargoBay)do
local cargo=_cargo
local wcargo=0
if(not cargo.reserved)or(cargo.reserved==true and(IncludeReserved==true or IncludeReserved==nil))then
wcargo=cargo.group:GetWeightTotal(element.name)
end
weight=weight+wcargo
end
end
end
return weight
end
function OPSGROUP:GetFreeCargobay(UnitName,IncludeReserved)
local weightCargoMax=self:GetWeightCargoMax(UnitName)
local weightCargo=self:GetWeightCargo(UnitName,IncludeReserved)
local Free=weightCargoMax-weightCargo
self:T(self.lid..string.format("Free cargo bay=%d kg (unit=%s)",Free,(UnitName or"whole group")))
return Free
end
function OPSGROUP:GetFreeCargobayRelative(UnitName,IncludeReserved)
local free=self:GetFreeCargobay(UnitName,IncludeReserved)
local total=self:GetWeightCargoMax(UnitName)
local percent=free/total*100
return percent
end
function OPSGROUP:GetUsedCargobayRelative(UnitName,IncludeReserved)
local free=self:GetFreeCargobayRelative(UnitName,IncludeReserved)
return 100-free
end
function OPSGROUP:GetFreeCargobayMax(Currently)
local maxweight=0
for _,_element in pairs(self.elements)do
local element=_element
if element.status~=OPSGROUP.ElementStatus.DEAD then
local weight=element.weightMaxCargo
if Currently then
weight=weight-element.weightCargo
end
if weight>maxweight then
maxweight=weight
end
end
end
return maxweight
end
function OPSGROUP:GetWeightCargo(UnitName,IncludeReserved)
local weight=0
for _,_element in pairs(self.elements)do
local element=_element
if(UnitName==nil or UnitName==element.name)and element.status~=OPSGROUP.ElementStatus.DEAD then
weight=weight+element.weightCargo or 0
end
end
local gewicht=0
for _,_element in pairs(self.elements)do
local element=_element
if(UnitName==nil or UnitName==element.name)and(element and element.status~=OPSGROUP.ElementStatus.DEAD)then
for _,_cargo in pairs(element.cargoBay)do
local cargo=_cargo
if(not cargo.reserved)or(cargo.reserved==true and(IncludeReserved==true or IncludeReserved==nil))then
if cargo.group then
gewicht=gewicht+cargo.group:GetWeightTotal()
else
gewicht=gewicht+cargo.storageAmount*cargo.storageWeight
end
end
end
end
end
self:T3(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d",tostring(UnitName),tostring(IncludeReserved),weight,gewicht))
if IncludeReserved==false and gewicht~=weight then
self:T(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f",weight,gewicht))
end
return gewicht
end
function OPSGROUP:GetWeightCargoMax(UnitName)
local weight=0
for _,_element in pairs(self.elements)do
local element=_element
if(UnitName==nil or UnitName==element.name)and element.status~=OPSGROUP.ElementStatus.DEAD then
weight=weight+element.weightMaxCargo
end
end
return weight
end
function OPSGROUP:GetCargoOpsGroups()
local opsgroups={}
for _,_element in pairs(self.elements)do
local element=_element
for _,_cargo in pairs(element.cargoBay)do
local cargo=_cargo
table.insert(opsgroups,cargo.group)
end
end
return opsgroups
end
function OPSGROUP:AddWeightCargo(UnitName,Weight)
local element=self:GetElementByName(UnitName)
if element then
element.weightCargo=element.weightCargo+Weight
self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg",UnitName,Weight,element.weightCargo))
if self.isFlightgroup and element.unit and element.unit:IsAlive()then
trigger.action.setUnitInternalCargo(element.name,element.weightCargo)
end
end
return self
end
function OPSGROUP:RedWeightCargo(UnitName,Weight)
self:AddWeightCargo(UnitName,-Weight)
return self
end
function OPSGROUP:_GetWeightStorage(Storage,Total,Reserved,Amount)
local weight=Storage.cargoAmount
if not Total then
weight=weight-Storage.cargoLost-Storage.cargoLoaded-Storage.cargoDelivered
end
if Reserved then
weight=weight-Storage.cargoReserved
end
if not Amount then
weight=weight*Storage.cargoWeight
end
return weight
end
function OPSGROUP:CanCargo(Cargo)
if Cargo then
local weight=math.huge
if Cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
local weight=Cargo.opsgroup:GetWeightTotal()
for _,_element in pairs(self.elements)do
local element=_element
if element and element.status~=OPSGROUP.ElementStatus.DEAD and element.weightMaxCargo>=weight then
return true
end
end
else
weight=Cargo.storage.cargoWeight
end
local bay=0
for _,_element in pairs(self.elements)do
local element=_element
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
bay=bay+element.weightMaxCargo
end
end
if bay>=weight then
return true
end
end
return false
end
function OPSGROUP:FindCarrierForCargo(Weight)
for _,_element in pairs(self.elements)do
local element=_element
local free=self:GetFreeCargobay(element.name)
if free>=Weight then
return element
else
self:T3(self.lid..string.format("%s: Weight %d>%d free cargo bay",element.name,Weight,free))
end
end
return nil
end
function OPSGROUP:_SetMyCarrier(CarrierGroup,CarrierElement,Reserved)
self:T(self.lid..string.format("Setting My Carrier: %s (%s), reserved=%s",CarrierGroup:GetName(),tostring(CarrierElement.name),tostring(Reserved)))
self.mycarrier.group=CarrierGroup
self.mycarrier.element=CarrierElement
self.mycarrier.reserved=Reserved
self.cargoTransportUID=CarrierGroup.cargoTransport and CarrierGroup.cargoTransport.uid or nil
end
function OPSGROUP:_GetMyCarrierGroup()
if self.mycarrier and self.mycarrier.group then
return self.mycarrier.group
end
return nil
end
function OPSGROUP:_GetMyCarrierElement()
if self.mycarrier and self.mycarrier.element then
return self.mycarrier.element
end
return nil
end
function OPSGROUP:_IsMyCarrierReserved()
if self.mycarrier then
return self.mycarrier.reserved
end
return nil
end
function OPSGROUP:_GetMyCarrier()
return self.mycarrier.group,self.mycarrier.element,self.mycarrier.reserved
end
function OPSGROUP:_RemoveMyCarrier()
self:T(self.lid..string.format("Removing my carrier!"))
self.mycarrier.group=nil
self.mycarrier.element=nil
self.mycarrier.reserved=nil
self.mycarrier={}
self.cargoTransportUID=nil
return self
end
function OPSGROUP:onafterPickup(From,Event,To)
local oldstatus=self.carrierStatus
self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP)
local TZC=self.cargoTZC
local Zone=TZC.PickupZone
local inzone=self:IsInZone(Zone)
local airbasePickup=TZC.PickupAirbase
local ready4loading=false
if self:IsArmygroup()or self:IsNavygroup()then
ready4loading=inzone
else
ready4loading=self.currbase and airbasePickup and self.currbase:GetName()==airbasePickup:GetName()and self:IsParking()
if ready4loading==false and self.isHelo and self:IsLandedAt()and inzone then
ready4loading=true
end
end
if ready4loading then
if(self:IsArmygroup()or self:IsNavygroup())and not self:IsHolding()then
self:FullStop()
end
self:__Loading(-5)
else
local surfacetypes=nil
if self:IsArmygroup()or self:IsFlightgroup()then
surfacetypes={land.SurfaceType.LAND}
elseif self:IsNavygroup()then
surfacetypes={land.SurfaceType.WATER}
end
local Coordinate=Zone:GetRandomCoordinate(nil,nil,surfacetypes)
local uid=self:GetWaypointCurrentUID()
if self:IsFlightgroup()then
if self:IsParking()and self:IsUncontrolled()then
self:StartUncontrolled()
end
if airbasePickup then
local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC)
if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then
for i=#path.waypoints,1,-1 do
local wp=path.waypoints[i]
local coordinate=COORDINATE:NewFromWaypoint(wp)
local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true
uid=waypoint.uid
if i==1 then
waypoint.temp=false
waypoint.detour=1
end
end
else
local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.5)
local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),true);waypoint.detour=1
end
elseif self.isHelo then
local waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),false);waypoint.detour=1
else
self:T(self.lid.."ERROR: Transportcarrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone")
end
if self.isHelo and self:IsLandedAt()then
local Task=self:GetTaskCurrent()
if Task then
self:TaskCancel(Task)
else
self:T(self.lid.."ERROR: No current task but landed at?!")
end
end
if self:IsWaiting()then
self:__Cruise(-2)
end
elseif self:IsNavygroup()then
local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC)
if path then
for i=#path.waypoints,1,-1 do
local wp=path.waypoints[i]
local coordinate=COORDINATE:NewFromWaypoint(wp)
local waypoint=NAVYGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true
uid=waypoint.uid
end
end
local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,nil,uid,self.altitudeCruise,false);waypoint.detour=1
self:__Cruise(-2)
elseif self:IsArmygroup()then
local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC)
local Formation=self.cargoTransport:_GetFormationPickup(self.cargoTZC,self)
if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then
for i=#path.waypoints,1,-1 do
local wp=path.waypoints[i]
local coordinate=COORDINATE:NewFromWaypoint(wp)
local waypoint=ARMYGROUP.AddWaypoint(self,coordinate,nil,uid,wp.action,false);waypoint.temp=true
uid=waypoint.uid
end
end
local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,uid,Formation,false);waypoint.detour=1
self:__Cruise(-2,nil,Formation)
end
end
end
function OPSGROUP:_SortCargo(Cargos)
local function _sort(a,b)
local cargoA=a
local cargoB=b
local weightA=0
local weightB=0
if cargoA.opsgroup then
weightA=cargoA.opsgroup:GetWeightTotal()
else
weightA=self:_GetWeightStorage(cargoA.storage)
end
if cargoB.opsgroup then
weightB=cargoB.opsgroup:GetWeightTotal()
else
weightB=self:_GetWeightStorage(cargoB.storage)
end
return weightA>weightB
end
table.sort(Cargos,_sort)
return Cargos
end
function OPSGROUP:onafterLoading(From,Event,To)
self:_NewCarrierStatus(OPSGROUP.CarrierStatus.LOADING)
local cargos={}
for _,_cargo in pairs(self.cargoTZC.Cargos)do
local cargo=_cargo
local canCargo=self:CanCargo(cargo)
local isCarrier=false
local isNotCargo=true
local isHolding=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsHolding()or cargo.opsgroup:IsLoaded())or true
local inZone=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and(cargo.opsgroup:IsInZone(self.cargoTZC.EmbarkZone)or cargo.opsgroup:IsInUtero())or true
local isOnMission=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsOnMission()or false
if isOnMission then
local mission=cargo.opsgroup:GetMissionCurrent()
if mission and((mission.opstransport and mission.opstransport.uid==self.cargoTransport.uid)or mission.type==AUFTRAG.Type.NOTHING)then
isOnMission=not isHolding
end
end
local isAvail=true
if cargo.type==OPSTRANSPORT.CargoType.STORAGE then
local nAvail=cargo.storage.storageFrom:GetAmount(cargo.storage.cargoType)
if nAvail>0 then
isAvail=true
else
isAvail=false
end
else
isCarrier=cargo.opsgroup:IsPickingup()or cargo.opsgroup:IsLoading()or cargo.opsgroup:IsTransporting()or cargo.opsgroup:IsUnloading()
isNotCargo=cargo.opsgroup:IsNotCargo(true)
end
local isDead=cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDead()or false
self:T(self.lid..string.format("Loading: canCargo=%s, isCarrier=%s, isNotCargo=%s, isHolding=%s, isOnMission=%s",
tostring(canCargo),tostring(isCarrier),tostring(isNotCargo),tostring(isHolding),tostring(isOnMission)))
if canCargo and inZone and isNotCargo and isHolding and isAvail and(not(cargo.delivered or isDead or isCarrier or isOnMission))then
table.insert(cargos,cargo)
end
end
self:_SortCargo(cargos)
for _,_cargo in pairs(cargos)do
local cargo=_cargo
local weight=nil
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
weight=cargo.opsgroup:GetWeightTotal()
local carrier=self:FindCarrierForCargo(weight)
if carrier then
cargo.opsgroup:Board(self,carrier)
end
else
weight=self:_GetWeightStorage(cargo.storage,false)
local Amount=cargo.storage.storageFrom:GetAmount(cargo.storage.cargoType)
local Weight=Amount*cargo.storage.cargoWeight
weight=math.min(weight,Weight)
self:T(self.lid..string.format("Loading storage weight=%d kg (warehouse has %d kg)!",weight,Weight))
for _,_element in pairs(self.elements)do
local element=_element
local free=self:GetFreeCargobay(element.name)
local w=math.min(weight,free)
if w>=cargo.storage.cargoWeight then
local amount=math.floor(w/cargo.storage.cargoWeight)
cargo.storage.storageFrom:RemoveAmount(cargo.storage.cargoType,amount)
cargo.storage.cargoLoaded=cargo.storage.cargoLoaded+amount
self:_AddCargobayStorage(element,cargo.uid,cargo.storage.cargoType,amount,cargo.storage.cargoWeight)
weight=weight-amount*cargo.storage.cargoWeight
local text=string.format("Element %s: loaded amount=%d (weight=%d) ==> left=%d kg",element.name,amount,amount*cargo.storage.cargoWeight,weight)
self:T(self.lid..text)
if weight<=0 then
break
end
end
end
end
end
end
function OPSGROUP:_NewCargoStatus(Status)
if self.verbose>=2 then
self:I(self.lid..string.format("New cargo status: %s --> %s",tostring(self.cargoStatus),tostring(Status)))
end
self.cargoStatus=Status
end
function OPSGROUP:_NewCarrierStatus(Status)
if self.verbose>=2 then
self:I(self.lid..string.format("New carrier status: %s --> %s",tostring(self.carrierStatus),tostring(Status)))
end
self.carrierStatus=Status
end
function OPSGROUP:_TransferCargo(CargoGroup,CarrierGroup,CarrierElement)
self:T(self.lid..string.format("Transferring cargo %s to new carrier group %s",CargoGroup:GetName(),CarrierGroup:GetName()))
self:Unload(CargoGroup)
CarrierGroup:Load(CargoGroup,CarrierElement)
end
function OPSGROUP:onafterLoad(From,Event,To,CargoGroup,Carrier)
self:T(self.lid..string.format("Loading group %s",tostring(CargoGroup.groupname)))
local carrier=Carrier or CargoGroup:_GetMyCarrierElement()
if not carrier then
local weight=CargoGroup:GetWeightTotal()
carrier=self:FindCarrierForCargo(weight)
end
if carrier then
CargoGroup:_NewCargoStatus(OPSGROUP.CargoStatus.LOADED)
CargoGroup:ClearWaypoints()
self:_AddCargobay(CargoGroup,carrier,false)
if CargoGroup:IsAlive()then
CargoGroup:Despawn(0,true)
end
CargoGroup:Embarked(self,carrier)
self:Loaded(CargoGroup)
if self.cargoTransport then
CargoGroup:_DelMyLift(self.cargoTransport)
self.cargoTransport:Loaded(CargoGroup,self,carrier)
else
self:T(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!"))
end
else
self:T(self.lid.."ERROR: Cargo has no carrier on Load event!")
end
end
function OPSGROUP:onafterLoadingDone(From,Event,To)
self:T(self.lid.."Carrier Loading Done ==> Transport")
self:__Transport(1)
end
function OPSGROUP:onbeforeTransport(From,Event,To)
if self.cargoTransport==nil then
return false
elseif self.cargoTransport:IsDelivered()then
return false
end
return true
end
function OPSGROUP:onafterTransport(From,Event,To)
self:_NewCarrierStatus(OPSGROUP.CarrierStatus.TRANSPORTING)
local Zone=self.cargoTZC.DeployZone
local inzone=self:IsInZone(Zone)
local airbaseDeploy=self.cargoTZC.DeployAirbase
local ready2deploy=false
if self:IsArmygroup()or self:IsNavygroup()then
ready2deploy=inzone
else
ready2deploy=self.currbase and airbaseDeploy and self.currbase:GetName()==airbaseDeploy:GetName()and self:IsParking()
if ready2deploy==false and(self.isHelo or self.isVTOL)and self:IsLandedAt()and inzone then
ready2deploy=true
end
end
if inzone then
if(self:IsArmygroup()or self:IsNavygroup())and not self:IsHolding()then
self:FullStop()
end
self:__Unloading(-5)
else
local surfacetypes=nil
if self:IsArmygroup()or self:IsFlightgroup()then
surfacetypes={land.SurfaceType.LAND}
elseif self:IsNavygroup()then
surfacetypes={land.SurfaceType.WATER,land.SurfaceType.SHALLOW_WATER}
end
local Coordinate=Zone:GetRandomCoordinate(nil,nil,surfacetypes)
local uid=self:GetWaypointCurrentUID()
if self:IsFlightgroup()then
if self:IsParking()and self:IsUncontrolled()then
self:StartUncontrolled()
end
if airbaseDeploy then
local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC)
if path then
for i=1,#path.waypoints do
local wp=path.waypoints[i]
local coordinate=COORDINATE:NewFromWaypoint(wp)
local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true
uid=waypoint.uid
if i==#path.waypoints then
waypoint.temp=false
waypoint.detour=1
end
end
else
local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate,0.5)
local waypoint=FLIGHTGROUP.AddWaypoint(self,coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),true);waypoint.detour=1
end
elseif self.isHelo then
local waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,nil,uid,UTILS.MetersToFeet(self.altitudeCruise),false);waypoint.detour=1
else
self:T(self.lid.."ERROR: Aircraft (cargo carrier) cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone")
end
if self.isHelo and self:IsLandedAt()then
local Task=self:GetTaskCurrent()
if Task then
self:TaskCancel(Task)
else
self:T(self.lid.."ERROR: No current task but landed at?!")
end
end
elseif self:IsArmygroup()then
local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC)
local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC,self)
if path then
for i=1,#path.waypoints do
local wp=path.waypoints[i]
local coordinate=COORDINATE:NewFromWaypoint(wp)
local waypoint=ARMYGROUP.AddWaypoint(self,coordinate,nil,uid,wp.action,false);waypoint.temp=true
uid=waypoint.uid
end
end
local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,uid,Formation,false);waypoint.detour=1
self:Cruise(nil,Formation)
elseif self:IsNavygroup()then
local path=self.cargoTransport:_GetPathTransport(self.category,self.cargoTZC)
if path then
for i=1,#path.waypoints do
local wp=path.waypoints[i]
local coordinate=COORDINATE:NewFromWaypoint(wp)
local waypoint=NAVYGROUP.AddWaypoint(self,coordinate,nil,uid,nil,false);waypoint.temp=true
uid=waypoint.uid
end
end
local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,nil,uid,self.altitudeCruise,false);waypoint.detour=1
self:Cruise()
end
end
end
function OPSGROUP:onafterUnloading(From,Event,To)
self:_NewCarrierStatus(OPSGROUP.CarrierStatus.UNLOADING)
self:T(self.lid.."Unloading..")
local zone=self.cargoTZC.DisembarkZone or self.cargoTZC.DeployZone
for _,_cargo in pairs(self.cargoTZC.Cargos)do
local cargo=_cargo
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
if cargo.opsgroup:IsLoaded(self.groupname)and not cargo.opsgroup:IsDead()then
local carrier=nil
local carrierGroup=nil
local disembarkToCarriers=cargo.disembarkCarriers~=nil or self.cargoTZC.disembarkToCarriers
if cargo.disembarkZone then
zone=cargo.disembarkZone
end
self:T(self.lid..string.format("Unloading cargo %s to zone %s",cargo.opsgroup:GetName(),zone and zone:GetName()or"No Zone Found!"))
if zone and zone:IsInstanceOf("ZONE_AIRBASE")and zone:GetAirbase():IsShip()then
local shipname=zone:GetAirbase():GetName()
local ship=UNIT:FindByName(shipname)
local group=ship:GetGroup()
carrierGroup=_DATABASE:GetOpsGroup(group:GetName())
carrier=carrierGroup:GetElementByName(shipname)
end
if disembarkToCarriers then
self:T(self.lid..string.format("Trying to find disembark carriers in zone %s",zone:GetName()))
local disembarkCarriers=cargo.disembarkCarriers or self.cargoTZC.DisembarkCarriers
carrier,carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup,zone,disembarkCarriers,self.cargoTZC.DeployAirbase)
end
if(disembarkToCarriers and carrier and carrierGroup)or(not disembarkToCarriers)then
cargo.delivered=true
self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1
if carrier and carrierGroup then
self:_TransferCargo(cargo.opsgroup,carrierGroup,carrier)
elseif zone and zone:IsInstanceOf("ZONE_AIRBASE")and zone:GetAirbase():IsShip()then
self:T(self.lid.."ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!")
self:Unload(cargo.opsgroup)
else
if self.cargoTransport:GetDisembarkInUtero(self.cargoTZC)then
self:Unload(cargo.opsgroup)
else
local DisembarkZone=cargo.disembarkZone or self.cargoTransport:GetDisembarkZone(self.cargoTZC)
local Coordinate=nil
if DisembarkZone then
Coordinate=DisembarkZone:GetRandomCoordinate()
else
local element=cargo.opsgroup:_GetMyCarrierElement()
if element then
local zoneCarrier=self:GetElementZoneUnload(element.name)
Coordinate=zoneCarrier:GetRandomCoordinate()
else
self:E(self.lid..string.format("ERROR carrier element nil!"))
end
end
local Heading=math.random(0,359)
local activation=self.cargoTransport:GetDisembarkActivation(self.cargoTZC)
if cargo.disembarkActivation~=nil then
activation=cargo.disembarkActivation
end
self:Unload(cargo.opsgroup,Coordinate,activation,Heading)
end
self.cargoTransport:Unloaded(cargo.opsgroup,self)
end
else
self:T(self.lid.."Cargo needs carrier but no carrier is avaiable (yet)!")
end
else
end
else
if not cargo.delivered then
for _,_element in pairs(self.elements)do
local element=_element
local mycargo=self:_GetCargobayElement(element,cargo.uid)
if mycargo then
cargo.storage.storageTo:AddAmount(mycargo.storageType,mycargo.storageAmount)
cargo.storage.cargoDelivered=cargo.storage.cargoDelivered+mycargo.storageAmount
cargo.storage.cargoLoaded=cargo.storage.cargoLoaded-mycargo.storageAmount
self:_DelCargobayElement(element,mycargo)
self:T2(self.lid..string.format("Cargo loaded=%d, delivered=%d, lost=%d",cargo.storage.cargoLoaded,cargo.storage.cargoDelivered,cargo.storage.cargoLost))
end
end
local amountToDeliver=self:_GetWeightStorage(cargo.storage,false,false,true)
local amountTotal=self:_GetWeightStorage(cargo.storage,true,false,true)
local text=string.format("Amount delivered=%d, total=%d",amountToDeliver,amountTotal)
self:T(self.lid..text)
if amountToDeliver<=0 then
cargo.delivered=true
self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1
local text=string.format("Ndelivered=%d delivered=%s",self.cargoTransport.Ndelivered,tostring(cargo.delivered))
self:T(self.lid..text)
end
end
end
end
end
function OPSGROUP:onbeforeUnload(From,Event,To,OpsGroup,Coordinate,Heading)
local removed=self:_DelCargobay(OpsGroup)
return removed
end
function OPSGROUP:onafterUnload(From,Event,To,OpsGroup,Coordinate,Activated,Heading)
OpsGroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO)
if Coordinate then
local Template=UTILS.DeepCopy(OpsGroup.template)
if Activated==false then
Template.lateActivation=true
else
Template.lateActivation=false
end
for _,Unit in pairs(Template.units)do
local element=OpsGroup:GetElementByName(Unit.name)
if element then
local vec3=element.vec3
local rvec2={x=Unit.x-Template.x,y=Unit.y-Template.y}
local cvec2={x=Coordinate.x,y=Coordinate.z}
Unit.x=cvec2.x+rvec2.x
Unit.y=cvec2.y+rvec2.y
Unit.alt=land.getHeight({x=Unit.x,y=Unit.y})
Unit.heading=Heading and math.rad(Heading)or Unit.heading
Unit.psi=-Unit.heading
end
end
OpsGroup:_Respawn(0,Template)
if OpsGroup:IsNavygroup()then
OpsGroup:ClearWaypoints()
OpsGroup.currentwp=1
OpsGroup.passedfinalwp=true
NAVYGROUP.AddWaypoint(OpsGroup,Coordinate,nil,nil,nil,false)
elseif OpsGroup:IsArmygroup()then
OpsGroup:ClearWaypoints()
OpsGroup.currentwp=1
OpsGroup.passedfinalwp=true
ARMYGROUP.AddWaypoint(OpsGroup,Coordinate,nil,nil,nil,false)
end
else
OpsGroup.position=self:GetVec3()
end
OpsGroup:Disembarked(OpsGroup:_GetMyCarrierGroup(),OpsGroup:_GetMyCarrierElement())
self:Unloaded(OpsGroup)
OpsGroup:_RemoveMyCarrier()
end
function OPSGROUP:onafterUnloaded(From,Event,To,OpsGroupCargo)
self:T(self.lid..string.format("Unloaded OPSGROUP %s",OpsGroupCargo:GetName()))
if OpsGroupCargo.legion and OpsGroupCargo:IsInZone(OpsGroupCargo.legion.spawnzone)then
self:T(self.lid..string.format("Unloaded group %s returned to legion",OpsGroupCargo:GetName()))
OpsGroupCargo:Returned()
end
local paused=OpsGroupCargo:_CountPausedMissions()>0
if paused then
OpsGroupCargo:UnpauseMission()
end
end
function OPSGROUP:onafterUnloadingDone(From,Event,To)
self:T(self.lid.."Cargo unloading done..")
if self:IsFlightgroup()and self:IsLandedAt()then
local Task=self:GetTaskCurrent()
self:__TaskCancel(5,Task)
end
local delivered=self:_CheckGoPickup(self.cargoTransport)
if not delivered then
self.cargoTZC=self.cargoTransport:_GetTransportZoneCombo(self)
if self.cargoTZC then
self:T(self.lid.."Unloaded: Still cargo left ==> Pickup")
self:Pickup()
else
self:T(self.lid..string.format("WARNING: Not all cargo was delivered but could not get a transport zone combo ==> setting carrier state to NOT CARRIER"))
self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER)
end
else
self:T(self.lid.."Unloaded: ALL cargo unloaded ==> Delivered (current)")
self:Delivered(self.cargoTransport)
end
end
function OPSGROUP:onafterDelivered(From,Event,To,CargoTransport)
if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then
if self:IsPickingup()then
local wpindex=self:GetWaypointIndexNext(false)
if wpindex then
self:RemoveWaypoint(wpindex)
end
self.isLandingAtAirbase=nil
elseif self:IsLoading()then
elseif self:IsTransporting()then
elseif self:IsUnloading()then
end
self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER)
if self:IsFlightgroup()then
local function atbase(_airbase)
local airbase=_airbase
if airbase and self.currbase then
if airbase.AirbaseName==self.currbase.AirbaseName then
return true
end
end
return false
end
if self:IsUncontrolled()and not atbase(self.destbase)then
self:StartUncontrolled()
end
if self:IsLandedAt()then
local Task=self:GetTaskCurrent()
self:TaskCancel(Task)
end
else
self:__Cruise(-0.1)
end
self.cargoTransport:SetCarrierTransportStatus(self,OPSTRANSPORT.Status.DELIVERED)
self:T(self.lid..string.format("All cargo of transport UID=%d delivered ==> check group done in 0.2 sec",self.cargoTransport.uid))
self:_CheckGroupDone(0.2)
end
end
function OPSGROUP:onafterTransportCancel(From,Event,To,Transport)
if self.cargoTransport and self.cargoTransport.uid==Transport.uid then
self:T(self.lid..string.format("Cancel current transport %d",Transport.uid))
local calldelivered=false
if self:IsPickingup()then
calldelivered=true
elseif self:IsLoading()then
local cargos=Transport:GetCargoOpsGroups(false)
for _,_opsgroup in pairs(cargos)do
local opsgroup=_opsgroup
if opsgroup:IsBoarding(self.groupname)then
opsgroup:RemoveWaypoint(self.currentwp+1)
self:_DelCargobay(opsgroup)
opsgroup:_RemoveMyCarrier()
opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO)
elseif opsgroup:IsLoaded(self.groupname)then
local zoneCarrier=self:GetElementZoneUnload(opsgroup:_GetMyCarrierElement().name)
local Coordinate=zoneCarrier and zoneCarrier:GetRandomCoordinate()or self.cargoTransport:GetEmbarkZone(self.cargoTZC):GetRandomCoordinate()
local Heading=math.random(0,359)
self:Unload(opsgroup,Coordinate,self.cargoTransport:GetDisembarkActivation(self.cargoTZC),Heading)
self.cargoTransport:Unloaded(opsgroup,self)
end
end
calldelivered=true
elseif self:IsTransporting()then
elseif self:IsUnloading()then
else
end
if calldelivered then
self:__Delivered(-2,Transport)
end
else
Transport:SetCarrierTransportStatus(self,AUFTRAG.GroupStatus.CANCELLED)
self:DelOpsTransport(Transport)
self:_CheckGroupDone(1)
end
end
function OPSGROUP:onbeforeBoard(From,Event,To,CarrierGroup,Carrier)
if self:IsDead()then
self:T(self.lid.."Group DEAD ==> Deny Board transition!")
return false
elseif CarrierGroup:IsDead()then
self:T(self.lid.."Carrier Group DEAD ==> Deny Board transition!")
self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO)
return false
elseif Carrier.status==OPSGROUP.ElementStatus.DEAD then
self:T(self.lid.."Carrier Element DEAD ==> Deny Board transition!")
self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO)
return false
end
return true
end
function OPSGROUP:onafterBoard(From,Event,To,CarrierGroup,Carrier)
local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup()or CarrierGroup:IsNavygroup()
local CargoIsArmyOrNavy=self:IsArmygroup()or self:IsNavygroup()
if(CarrierIsArmyOrNavy and(CarrierGroup:GetVelocity(Carrier.name)<=1))or(CarrierGroup:IsFlightgroup()and(CarrierGroup:IsParking()or CarrierGroup:IsLandedAt()))then
local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive()and CarrierGroup:IsAlive()
if self:IsArmygroup()and CarrierGroup:IsNavygroup()then
board=false
end
if self:IsLoaded()then
self:T(self.lid..string.format("Group is loaded currently ==> Moving directly to new carrier - No Unload(), Disembart() events triggered!"))
self:_RemoveMyCarrier()
CarrierGroup:Load(self)
elseif board then
self:_NewCargoStatus(OPSGROUP.CargoStatus.BOARDING)
self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s",CarrierGroup:GetName(),CarrierGroup:GetState(),tostring(Carrier.name)))
local Coordinate=Carrier.unit:GetCoordinate()
self:ClearWaypoints(self.currentwp+1)
if self.isArmygroup then
local waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,nil,nil,ENUMS.Formation.Vehicle.Diamond);waypoint.detour=1
self:Cruise()
else
local waypoint=NAVYGROUP.AddWaypoint(self,Coordinate);waypoint.detour=1
self:Cruise()
end
CarrierGroup:_AddCargobay(self,Carrier,true)
else
self:T(self.lid..string.format("Board [loaded=%s] with direct load to carrier group=%s, element=%s",tostring(self:IsLoaded()),CarrierGroup:GetName(),tostring(Carrier.name)))
local mycarriergroup=self:_GetMyCarrierGroup()
if mycarriergroup then
self:T(self.lid..string.format("Current carrier group %s",mycarriergroup:GetName()))
end
if mycarriergroup and mycarriergroup:GetName()~=CarrierGroup:GetName()then
self:T(self.lid.."Unloading from mycarrier")
mycarriergroup:Unload(self)
end
CarrierGroup:Load(self)
end
else
self:T(self.lid.."Carrier not ready for boarding yet ==> repeating boarding call in 10 sec")
self:__Board(-10,CarrierGroup,Carrier)
CarrierGroup:_AddCargobay(self,Carrier,true)
end
end
function OPSGROUP:_CheckInZones()
if self.checkzones and self:IsAlive()then
local Ncheck=self.checkzones:Count()
local Ninside=self.inzones:Count()
self:T(self.lid..string.format("Check if group is in %d zones. Currently it is in %d zones.",self.checkzones:Count(),self.inzones:Count()))
local leftzones={}
for inzonename,inzone in pairs(self.inzones:GetSet())do
local isstillinzone=self.group:IsInZone(inzone)
if not isstillinzone then
table.insert(leftzones,inzone)
end
end
for _,leftzone in pairs(leftzones)do
self:LeaveZone(leftzone)
end
local enterzones={}
for checkzonename,_checkzone in pairs(self.checkzones:GetSet())do
local checkzone=_checkzone
local isincheckzone=self.group:IsInZone(checkzone)
if isincheckzone and not self.inzones:_Find(checkzonename)then
table.insert(enterzones,checkzone)
end
end
for _,enterzone in pairs(enterzones)do
self:EnterZone(enterzone)
end
end
end
function OPSGROUP:_CheckDetectedUnits()
if self.detectionOn and self.group and not self:IsDead()then
local detectedtargets=self.group:GetDetectedTargets()
local detected={}
local groups={}
for DetectionObjectID,Detection in pairs(detectedtargets or{})do
local DetectedObject=Detection.object
if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then
local unit=UNIT:Find(DetectedObject)
if unit and unit:IsAlive()then
local unitname=unit:GetName()
table.insert(detected,unit)
self:DetectedUnit(unit)
local group=unit:GetGroup()
if group then
groups[group:GetName()]=group
end
end
end
end
for groupname,group in pairs(groups)do
self:DetectedGroup(group)
end
local lost={}
for _,_unit in pairs(self.detectedunits:GetSet())do
local unit=_unit
local gotit=false
for _,_du in pairs(detected)do
local du=_du
if unit:GetName()==du:GetName()then
gotit=true
end
end
if not gotit then
table.insert(lost,unit:GetName())
self:DetectedUnitLost(unit)
end
end
self.detectedunits:RemoveUnitsByName(lost)
local lost={}
for _,_group in pairs(self.detectedgroups:GetSet())do
local group=_group
local gotit=false
for _,_du in pairs(groups)do
local du=_du
if group:GetName()==du:GetName()then
gotit=true
end
end
if not gotit then
table.insert(lost,group:GetName())
self:DetectedGroupLost(group)
end
end
self.detectedgroups:RemoveGroupsByName(lost)
end
end
function OPSGROUP:_CheckGroupDone(delay)
local fsmstate=self:GetState()
if self:IsAlive()and self.isAI then
if delay and delay>0 then
self:T(self.lid..string.format("Check OPSGROUP done? [state=%s] in %.3f seconds...",fsmstate,delay))
self:ScheduleOnce(delay,self._CheckGroupDone,self)
else
self:T(self.lid..string.format("Check OSGROUP done? [state=%s]",fsmstate))
if self:IsEngaging()then
self:T(self.lid.."Engaging! Group NOT done ==> UpdateRoute()")
self:UpdateRoute()
return
end
if self:IsReturning()then
self:T(self.lid.."Returning! Group NOT done...")
return
end
if self:IsRearming()then
self:T(self.lid.."Rearming! Group NOT done...")
return
end
if self:IsRetreating()then
self:T(self.lid.."Retreating! Group NOT done...")
return
end
if self:IsBoarding()then
self:T(self.lid.."Boarding! Group NOT done...")
return
end
if self:IsWaiting()then
self:T(self.lid.."Waiting! Group NOT done...")
return
end
local nTasks=self:CountRemainingTasks()
local nMissions=self:CountRemainingMissison()
local nTransports=self:CountRemainingTransports()
local nPaused=self:_CountPausedMissions()
if nPaused>0 and nPaused==nMissions then
local missionpaused=self:_GetPausedMission()
self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...",missionpaused.name,missionpaused.type))
self:UnpauseMission()
return
end
if nTasks>0 or nMissions>0 or nTransports>0 then
self:T(self.lid..string.format("Group still has tasks, missions or transports ==> NOT DONE"))
return
end
local waypoint=self:GetWaypoint(self.currentwp)
if waypoint then
local ntasks=self:CountTasksWaypoint(waypoint.uid)
if ntasks>0 then
self:T(self.lid..string.format("Still got %d tasks for the current waypoint UID=%d ==> RETURN (no action)",ntasks,waypoint.uid))
return
end
end
if self.adinfinitum then
if#self.waypoints>0 then
local i=self:GetWaypointIndexNext(true)
local speed=self:GetSpeedToWaypoint(i)
self:Cruise(speed)
self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots",i,speed))
else
self:T(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop"))
self:__FullStop(-1)
end
else
if self:HasPassedFinalWaypoint()then
if self.legion and self.legionReturn then
self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE, LEGION set ==> RTZ"))
if self.isArmygroup then
self:T2(self.lid.."RTZ to legion spawn zone")
self:RTZ(self.legion.spawnzone)
elseif self.isNavygroup then
self:T2(self.lid.."RTZ to legion port zone")
self:RTZ(self.legion.portzone)
end
else
self:__FullStop(-1)
self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop"))
end
else
if#self.waypoints>0 then
self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route"))
self:Cruise()
else
self:T(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop"))
self:__FullStop(-1)
end
end
end
end
end
end
function OPSGROUP:_CheckStuck()
if self:IsHolding()or self:Is("Rearming")or self:IsWaiting()or self:HasPassedFinalWaypoint()then
return
end
local Tnow=timer.getTime()
local ExpectedSpeed=self:GetExpectedSpeed()
local speed=self:GetVelocity()
if speed<0.1 then
if ExpectedSpeed>0 and not self.stuckTimestamp then
self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected",speed,ExpectedSpeed))
self.stuckTimestamp=Tnow
self.stuckVec3=self:GetVec3()
end
else
self.stuckTimestamp=nil
end
if self.stuckTimestamp then
local holdtime=Tnow-self.stuckTimestamp
if holdtime>=5*60 and holdtime<10*60 then
self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime))
if self:IsEngaging()then
self:__Disengage(1)
elseif self:IsReturning()then
self:T2(self.lid.."RTZ because of stuck")
self:__RTZ(1)
else
self:__Cruise(1)
end
elseif holdtime>=10*60 and holdtime<30*60 then
self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime))
local mission=self:GetMissionCurrent()
if mission then
self:T(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck",mission:GetName(),mission:GetType()))
self:MissionCancel(mission)
else
if self:IsReturning()then
self:T2(self.lid.."RTZ because of stuck")
self:__RTZ(1)
else
self:__Cruise(1)
end
end
elseif holdtime>=30*60 then
self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime))
if self.legion then
self:T(self.lid..string.format("Asset is returned to its legion after being stuck!"))
self:ReturnToLegion()
end
end
end
end
function OPSGROUP:_CheckDamage()
self:T(self.lid..string.format("Checking damage..."))
self.life=0
local damaged=false
for _,_element in pairs(self.elements)do
local element=_element
if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then
local life=element.unit:GetLife()
self.life=self.life+life
if life<element.life then
element.life=life
self:ElementDamaged(element)
damaged=true
end
end
end
if damaged then
self:Damaged()
end
return self
end
function OPSGROUP:_CheckAmmoFull()
local ammo=self:GetAmmoTot()
for key,value in pairs(self.ammo)do
if ammo[key]<value then
return false
end
end
return true
end
function OPSGROUP:_CheckAmmoStatus()
if self.ammo.Total>0 then
local ammo=self:GetAmmoTot()
if self:IsRearming()then
if ammo.Total>=self.ammo.Total then
self:Rearmed()
end
end
if self.outofAmmo and ammo.Total>0 then
self.outofAmmo=false
end
if ammo.Total==0 and not self.outofAmmo then
self.outofAmmo=true
self:OutOfAmmo()
end
if self.outofGuns and ammo.Guns>0 then
self.outofGuns=false
end
if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then
self.outofGuns=true
self:OutOfGuns()
end
if self.outofRockets and ammo.Rockets>0 then
self.outofRockets=false
end
if ammo.Rockets==0 and self.ammo.Rockets>0 and not self.outofRockets then
self.outofRockets=true
self:OutOfRockets()
end
if self.outofBombs and ammo.Bombs>0 then
self.outofBombs=false
end
if ammo.Bombs==0 and self.ammo.Bombs>0 and not self.outofBombs then
self.outofBombs=true
self:OutOfBombs()
end
if self.outofMissiles and ammo.Missiles>0 then
self.outofMissiles=false
end
if ammo.Missiles==0 and self.ammo.Missiles>0 and not self.outofMissiles then
self.outofMissiles=true
self:OutOfMissiles()
end
if self.outofMissilesAA and ammo.MissilesAA>0 then
self.outofMissilesAA=false
end
if ammo.MissilesAA==0 and self.ammo.MissilesAA>0 and not self.outofMissilesAA then
self.outofMissilesAA=true
self:OutOfMissilesAA()
end
if self.outofMissilesAG and ammo.MissilesAG>0 then
self.outofMissilesAG=false
end
if ammo.MissilesAG==0 and self.ammo.MissilesAG>0 and not self.outofMissilesAG then
self.outofMissilesAG=true
self:OutOfMissilesAG()
end
if self.outofMissilesAS and ammo.MissilesAS>0 then
self.outofMissilesAS=false
end
if ammo.MissilesAS==0 and self.ammo.MissilesAS>0 and not self.outofMissilesAS then
self.outofMissilesAS=true
self:OutOfMissilesAS()
end
if self.outofTorpedos and ammo.Torpedos>0 then
self.outofTorpedos=false
end
if ammo.Torpedos==0 and self.ammo.Torpedos>0 and not self.outofTorpedos then
self.outofTorpedos=true
self:OutOfTorpedos()
end
if self:IsEngaging()and ammo.Total==0 then
self:Disengage()
end
end
end
function OPSGROUP:_PrintTaskAndMissionStatus()
if self.verbose>=3 and#self.taskqueue>0 then
local text=string.format("Tasks #%d",#self.taskqueue)
for i,_task in pairs(self.taskqueue)do
local task=_task
local name=task.description
local taskid=task.dcstask.id or"unknown"
local status=task.status
local clock=UTILS.SecondsToClock(task.time,true)
local eta=task.time-timer.getAbsTime()
local started=task.timestamp and UTILS.SecondsToClock(task.timestamp,true)or"N/A"
local duration=-1
if task.duration then
duration=task.duration
if task.timestamp then
duration=task.duration-(timer.getAbsTime()-task.timestamp)
else
duration=task.duration
end
end
if task.type==OPSGROUP.TaskType.SCHEDULED then
text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d",i,taskid,name,status,clock,eta,started,duration)
elseif task.type==OPSGROUP.TaskType.WAYPOINT then
text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d",i,taskid,name,status,task.waypoint,started,duration,task.stopflag:Get())
end
end
self:I(self.lid..text)
end
if self.verbose>=2 then
local Mission=self:GetMissionByID(self.currentmission)
local text=string.format("Missions %d, Current: %s",self:CountRemainingMissison(),Mission and Mission.name or"none")
for i,_mission in pairs(self.missionqueue)do
local mission=_mission
local Cstart=UTILS.SecondsToClock(mission.Tstart,true)
local Cstop=mission.Tstop and UTILS.SecondsToClock(mission.Tstop,true)or"INF"
text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d",
i,tostring(mission.name),mission.type,mission:GetGroupStatus(self),tostring(mission.status),Cstart,Cstop,mission.prio,tostring(mission:GetGroupWaypointIndex(self)),mission:CountMissionTargets())
end
self:I(self.lid..text)
end
end
function OPSGROUP:_SimpleTaskFunction(Function,uid)
local DCSScript={}
DCSScript[#DCSScript+1]=string.format('local mygroup = _DATABASE:FindOpsGroup(\"%s\") ',self.groupname)
DCSScript[#DCSScript+1]=string.format('%s(mygroup, %d)',Function,uid)
local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript)))
return DCSTask
end
function OPSGROUP:_CreateWaypoint(waypoint)
waypoint.uid=self.wpcounter
waypoint.npassed=0
waypoint.coordinate=COORDINATE:New(waypoint.x,waypoint.alt,waypoint.y)
waypoint.name=string.format("Waypoint UID=%d",waypoint.uid)
waypoint.patrol=false
waypoint.detour=false
waypoint.astar=false
waypoint.temp=false
local taskswp={}
local TaskPassingWaypoint=self:_SimpleTaskFunction("OPSGROUP._PassingWaypoint",waypoint.uid)
table.insert(taskswp,TaskPassingWaypoint)
waypoint.task=self.group:TaskCombo(taskswp)
self.wpcounter=self.wpcounter+1
return waypoint
end
function OPSGROUP:_AddWaypoint(waypoint,wpnumber)
wpnumber=wpnumber or#self.waypoints+1
table.insert(self.waypoints,wpnumber,waypoint)
self:T(self.lid..string.format("Adding waypoint at index=%d with UID=%d",wpnumber,waypoint.uid))
if self.currentwp and wpnumber>self.currentwp then
self:_PassedFinalWaypoint(false,string.format("_AddWaypoint: wpnumber/index %d>%d self.currentwp",wpnumber,self.currentwp))
end
end
function OPSGROUP:_InitWaypoints(WpIndexMin,WpIndexMax)
self.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)
for i=WpIndexMin,WpIndexMax do
local wp=self.waypoints0[i]
local Coordinate=COORDINATE:NewFromWaypoint(wp)
wp.speed=wp.speed or 0
local speedknots=UTILS.MpsToKnots(wp.speed)
if i<=2 then
self.speedWp=wp.speed
self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s",self.speedWp))
end
local Speed=UTILS.MpsToKnots(wp.speed)
local Waypoint=nil
if self:IsFlightgroup()then
Waypoint=FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,nil,Altitude,false)
elseif self:IsArmygroup()then
Waypoint=ARMYGROUP.AddWaypoint(self,Coordinate,Speed,nil,wp.action,false)
elseif self:IsNavygroup()then
Waypoint=NAVYGROUP.AddWaypoint(self,Coordinate,Speed,nil,Depth,false)
end
local DCStasks=wp.task and wp.task.params.tasks or nil
if DCStasks and self.useMEtasks then
for _,DCStask in pairs(DCStasks)do
if DCStask.id and DCStask.id~="WrappedAction"then
self:AddTaskWaypoint(DCStask,Waypoint,"ME Task")
end
end
end
end
self:T(self.lid..string.format("Initializing %d waypoints",#self.waypoints))
if self:IsFlightgroup()then
self.homebase=self.homebase or self:GetHomebaseFromWaypoints()
local destbase=self:GetDestinationFromWaypoints()
self.destbase=self.destbase or destbase
self.currbase=self:GetHomebaseFromWaypoints()
if destbase and#self.waypoints>1 then
table.remove(self.waypoints,#self.waypoints)
end
if self.destbase==nil then
self.destbase=self.homebase
end
end
if#self.waypoints>0 then
if#self.waypoints==1 then
self:_PassedFinalWaypoint(true,"_InitWaypoints: #self.waypoints==1")
end
else
self:T(self.lid.."WARNING: No waypoints initialized. Number of waypoints is 0!")
end
return self
end
function OPSGROUP:Route(waypoints,delay)
if delay and delay>0 then
self:ScheduleOnce(delay,OPSGROUP.Route,self,waypoints)
else
if self:IsAlive()then
local DCSTask={
id='Mission',
params={
airborne=self:IsFlightgroup(),
route={points=waypoints},
},
}
self:SetTask(DCSTask)
else
self:T(self.lid.."ERROR: Group is not alive! Cannot route group.")
end
end
return self
end
function OPSGROUP:_UpdateWaypointTasks(n)
local waypoints=self.waypoints or{}
local nwaypoints=#waypoints
for i,_wp in pairs(waypoints)do
local wp=_wp
if i>=n or nwaypoints==1 then
self:T2(self.lid..string.format("Updating waypoint task for waypoint %d/%d ID=%d. Last waypoint passed %d",i,nwaypoints,wp.uid,self.currentwp))
local taskswp={}
local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint",self,wp.uid)
table.insert(taskswp,TaskPassingWaypoint)
wp.task=self.group:TaskCombo(taskswp)
end
end
end
function OPSGROUP._PassingWaypoint(opsgroup,uid)
local text=string.format("Group passing waypoint uid=%d",uid)
opsgroup:T(opsgroup.lid..text)
local waypoint=opsgroup:GetWaypointByID(uid)
if waypoint then
waypoint.npassed=waypoint.npassed+1
local currentwp=opsgroup.currentwp
opsgroup.currentwp=opsgroup:GetWaypointIndex(uid)
local wpistemp=waypoint.temp or waypoint.detour or waypoint.astar
if wpistemp then
opsgroup:RemoveWaypointByID(uid)
end
local wpnext=opsgroup:GetWaypointNext()
if wpnext then
opsgroup:T(opsgroup.lid..string.format("Next waypoint UID=%d index=%d",wpnext.uid,opsgroup:GetWaypointIndex(wpnext.uid)))
if opsgroup.isArmygroup then
opsgroup.option.Formation=wpnext.action
end
opsgroup.speed=wpnext.speed
if opsgroup.speed<0.01 then
opsgroup.speed=UTILS.KmphToMps(opsgroup.speedCruise)
end
else
opsgroup:_PassedFinalWaypoint(true,"_PassingWaypoint No next Waypoint found")
end
if opsgroup.currentwp==#opsgroup.waypoints and not(opsgroup.adinfinitum or wpistemp)then
opsgroup:_PassedFinalWaypoint(true,"_PassingWaypoint currentwp==#waypoints and NOT adinfinitum and NOT a temporary waypoint")
end
if waypoint.temp then
if(opsgroup:IsNavygroup()or opsgroup:IsArmygroup())and opsgroup.currentwp==#opsgroup.waypoints then
opsgroup:Cruise()
end
elseif waypoint.astar then
opsgroup:Cruise()
elseif waypoint.detour then
if opsgroup:IsRearming()then
opsgroup:Rearming()
elseif opsgroup:IsRetreating()then
opsgroup:Retreated()
elseif opsgroup:IsReturning()then
opsgroup:Returned()
elseif opsgroup:IsPickingup()then
if opsgroup:IsFlightgroup()then
if opsgroup.cargoTZC then
if opsgroup.cargoTZC.PickupAirbase then
opsgroup:LandAtAirbase(opsgroup.cargoTZC.PickupAirbase)
else
local coordinate=opsgroup.cargoTZC.PickupZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND})
opsgroup:LandAt(coordinate,60*60)
end
else
local coordinate=opsgroup:GetCoordinate()
opsgroup:LandAt(coordinate,60*60)
end
else
opsgroup:FullStop()
opsgroup:__Loading(-5)
end
elseif opsgroup:IsTransporting()then
if opsgroup:IsFlightgroup()then
if opsgroup.cargoTZC then
if opsgroup.cargoTZC.DeployAirbase then
opsgroup:LandAtAirbase(opsgroup.cargoTZC.DeployAirbase)
else
local coordinate=opsgroup.cargoTZC.DeployZone:GetRandomCoordinate(nil,nil,{land.SurfaceType.LAND})
opsgroup:LandAt(coordinate,60*60)
end
else
local coordinate=opsgroup:GetCoordinate()
opsgroup:LandAt(coordinate,60*60)
end
else
opsgroup:FullStop()
opsgroup:Unloading()
end
elseif opsgroup:IsBoarding()then
local carrierGroup=opsgroup:_GetMyCarrierGroup()
local carrier=opsgroup:_GetMyCarrierElement()
if carrierGroup and carrierGroup:IsAlive()then
if carrier and carrier.unit and carrier.unit:IsAlive()then
carrierGroup:Load(opsgroup)
else
opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier UNIT as it is NOT alive!")
end
else
opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier GROUP as it is NOT alive!")
end
elseif opsgroup:IsEngaging()then
opsgroup:T(opsgroup.lid.."Passing engaging waypoint")
else
opsgroup:DetourReached()
if waypoint.detour==0 then
opsgroup:FullStop()
elseif waypoint.detour==1 then
opsgroup:Cruise()
else
opsgroup:E("ERROR: waypoint.detour should be 0 or 1")
opsgroup:FullStop()
end
end
else
if opsgroup.ispathfinding then
opsgroup.ispathfinding=false
end
opsgroup:PassingWaypoint(waypoint)
end
end
end
function OPSGROUP._TaskExecute(group,opsgroup,task)
local text=string.format("_TaskExecute %s",task.description)
opsgroup:T3(opsgroup.lid..text)
if opsgroup then
opsgroup:TaskExecute(task)
end
end
function OPSGROUP._TaskDone(group,opsgroup,task)
local text=string.format("_TaskDone %s",task.description)
opsgroup:T(opsgroup.lid..text)
if opsgroup then
opsgroup:TaskDone(task)
end
end
function OPSGROUP:SetDefaultROE(roe)
self.optionDefault.ROE=roe or ENUMS.ROE.ReturnFire
return self
end
function OPSGROUP:SwitchROE(roe)
if self:IsAlive()or self:IsInUtero()then
self.option.ROE=roe or self.optionDefault.ROE
if self:IsInUtero()then
self:T2(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED",self.option.ROE))
else
self.group:OptionROE(self.option.ROE)
self:T(self.lid..string.format("Setting current ROE=%d (%s)",self.option.ROE,self:_GetROEName(self.option.ROE)))
end
else
self:T(self.lid.."WARNING: Cannot switch ROE! Group is not alive")
end
return self
end
function OPSGROUP:_GetROEName(roe)
local name="unknown"
if roe==0 then
name="Weapon Free"
elseif roe==1 then
name="Open Fire/Weapon Free"
elseif roe==2 then
name="Open Fire"
elseif roe==3 then
name="Return Fire"
elseif roe==4 then
name="Weapon Hold"
end
return name
end
function OPSGROUP:GetROE()
return self.option.ROE or self.optionDefault.ROE
end
function OPSGROUP:SetDefaultROT(rot)
self.optionDefault.ROT=rot or ENUMS.ROT.PassiveDefense
return self
end
function OPSGROUP:SwitchROT(rot)
if self:IsFlightgroup()then
if self:IsAlive()or self:IsInUtero()then
self.option.ROT=rot or self.optionDefault.ROT
if self:IsInUtero()then
self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED",self.option.ROT))
else
self.group:OptionROT(self.option.ROT)
self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)",self.option.ROT))
end
else
self:T(self.lid.."WARNING: Cannot switch ROT! Group is not alive")
end
end
return self
end
function OPSGROUP:GetROT()
return self.option.ROT or self.optionDefault.ROT
end
function OPSGROUP:SetDefaultAlarmstate(alarmstate)
self.optionDefault.Alarm=alarmstate or 0
return self
end
function OPSGROUP:SwitchAlarmstate(alarmstate)
if self:IsAlive()or self:IsInUtero()then
if self.isArmygroup or self.isNavygroup then
self.option.Alarm=alarmstate or self.optionDefault.Alarm
if self:IsInUtero()then
self:T2(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED",self.option.Alarm))
else
if self.option.Alarm==0 then
self.group:OptionAlarmStateAuto()
elseif self.option.Alarm==1 then
self.group:OptionAlarmStateGreen()
elseif self.option.Alarm==2 then
self.group:OptionAlarmStateRed()
else
self:T("ERROR: Unknown Alarm State! Setting to AUTO")
self.group:OptionAlarmStateAuto()
self.option.Alarm=0
end
self:T(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)",self.option.Alarm))
end
end
else
self:T(self.lid.."WARNING: Cannot switch Alarm State! Group is not alive.")
end
return self
end
function OPSGROUP:GetAlarmstate()
return self.option.Alarm or self.optionDefault.Alarm
end
function OPSGROUP:SetDefaultEPLRS(OnOffSwitch)
if OnOffSwitch==nil then
self.optionDefault.EPLRS=self.isEPLRS
else
self.optionDefault.EPLRS=OnOffSwitch
end
return self
end
function OPSGROUP:SwitchEPLRS(OnOffSwitch)
if self:IsAlive()or self:IsInUtero()then
if OnOffSwitch==nil then
self.option.EPLRS=self.optionDefault.EPLRS
else
self.option.EPLRS=OnOffSwitch
end
if self:IsInUtero()then
self:T2(self.lid..string.format("Setting current EPLRS=%s when GROUP is SPAWNED",tostring(self.option.EPLRS)))
else
self.group:CommandEPLRS(self.option.EPLRS)
self:T(self.lid..string.format("Setting current EPLRS=%s",tostring(self.option.EPLRS)))
end
else
self:E(self.lid.."WARNING: Cannot switch EPLRS! Group is not alive")
end
return self
end
function OPSGROUP:GetEPLRS()
return self.option.EPLRS or self.optionDefault.EPLRS
end
function OPSGROUP:SetDefaultEmission(OnOffSwitch)
if OnOffSwitch==nil then
self.optionDefault.Emission=true
else
self.optionDefault.Emission=OnOffSwitch
end
return self
end
function OPSGROUP:SwitchEmission(OnOffSwitch)
if self:IsAlive()or self:IsInUtero()then
if OnOffSwitch==nil then
self.option.Emission=self.optionDefault.Emission
else
self.option.Emission=OnOffSwitch
end
if self:IsInUtero()then
self:T2(self.lid..string.format("Setting current EMISSION=%s when GROUP is SPAWNED",tostring(self.option.Emission)))
else
self.group:EnableEmission(self.option.Emission)
self:T(self.lid..string.format("Setting current EMISSION=%s",tostring(self.option.Emission)))
end
else
self:E(self.lid.."WARNING: Cannot switch Emission! Group is not alive")
end
return self
end
function OPSGROUP:GetEmission()
return self.option.Emission or self.optionDefault.Emission
end
function OPSGROUP:SetDefaultInvisible(OnOffSwitch)
if OnOffSwitch==nil then
self.optionDefault.Invisible=true
else
self.optionDefault.Invisible=OnOffSwitch
end
return self
end
function OPSGROUP:SwitchInvisible(OnOffSwitch)
if self:IsAlive()or self:IsInUtero()then
if OnOffSwitch==nil then
self.option.Invisible=self.optionDefault.Invisible
else
self.option.Invisible=OnOffSwitch
end
if self:IsInUtero()then
self:T2(self.lid..string.format("Setting current INVISIBLE=%s when GROUP is SPAWNED",tostring(self.option.Invisible)))
else
self.group:SetCommandInvisible(self.option.Invisible)
self:T(self.lid..string.format("Setting current INVISIBLE=%s",tostring(self.option.Invisible)))
end
else
self:E(self.lid.."WARNING: Cannot switch Invisible! Group is not alive")
end
return self
end
function OPSGROUP:SetDefaultImmortal(OnOffSwitch)
if OnOffSwitch==nil then
self.optionDefault.Immortal=true
else
self.optionDefault.Immortal=OnOffSwitch
end
return self
end
function OPSGROUP:SwitchImmortal(OnOffSwitch)
if self:IsAlive()or self:IsInUtero()then
if OnOffSwitch==nil then
self.option.Immortal=self.optionDefault.Immortal
else
self.option.Immortal=OnOffSwitch
end
if self:IsInUtero()then
self:T2(self.lid..string.format("Setting current IMMORTAL=%s when GROUP is SPAWNED",tostring(self.option.Immortal)))
else
self.group:SetCommandImmortal(self.option.Immortal)
self:T(self.lid..string.format("Setting current IMMORTAL=%s",tostring(self.option.Immortal)))
end
else
self:E(self.lid.."WARNING: Cannot switch Immortal! Group is not alive")
end
return self
end
function OPSGROUP:SetDefaultTACAN(Channel,Morse,UnitName,Band,OffSwitch)
self.tacanDefault={}
self.tacanDefault.Channel=Channel or 74
self.tacanDefault.Morse=Morse or"XXX"
self.tacanDefault.BeaconName=UnitName
if self:IsFlightgroup()then
Band=Band or"Y"
else
Band=Band or"X"
end
self.tacanDefault.Band=Band
if OffSwitch then
self.tacanDefault.On=false
else
self.tacanDefault.On=true
end
return self
end
function OPSGROUP:_SwitchTACAN(Tacan)
if Tacan then
self:SwitchTACAN(Tacan.Channel,Tacan.Morse,Tacan.BeaconName,Tacan.Band)
else
if self.tacanDefault.On then
self:SwitchTACAN()
else
self:TurnOffTACAN()
end
end
end
function OPSGROUP:SwitchTACAN(Channel,Morse,UnitName,Band)
if self:IsInUtero()then
self:T(self.lid..string.format("Switching TACAN to DEFAULT when group is spawned"))
self:SetDefaultTACAN(Channel,Morse,UnitName,Band)
elseif self:IsAlive()then
Channel=Channel or self.tacanDefault.Channel
Morse=Morse or self.tacanDefault.Morse
Band=Band or self.tacanDefault.Band
UnitName=UnitName or self.tacanDefault.BeaconName
local unit=self:GetUnit(1)
if UnitName then
if type(UnitName)=="number"then
unit=self.group:GetUnit(UnitName)
else
unit=UNIT:FindByName(UnitName)
end
end
if not unit then
self:T(self.lid.."WARNING: Could not get TACAN unit. Trying first unit in the group")
unit=self:GetUnit(1)
end
if unit and unit:IsAlive()then
local UnitID=unit:GetID()
local Type=BEACON.Type.TACAN
local System=BEACON.System.TACAN
if self:IsFlightgroup()then
System=BEACON.System.TACAN_TANKER_Y
end
local Frequency=UTILS.TACANToFrequency(Channel,Band)
unit:CommandActivateBeacon(Type,System,Frequency,UnitID,Channel,Band,true,Morse,true)
self.tacan.Channel=Channel
self.tacan.Morse=Morse
self.tacan.Band=Band
self.tacan.BeaconName=unit:GetName()
self.tacan.BeaconUnit=unit
self.tacan.On=true
self:T(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s",self.tacan.Channel,self.tacan.Band,tostring(self.tacan.Morse),self.tacan.BeaconName))
else
self:T(self.lid.."ERROR: Cound not set TACAN! Unit is not alive")
end
else
self:T(self.lid.."ERROR: Cound not set TACAN! Group is not alive and not in utero any more")
end
return self
end
function OPSGROUP:TurnOffTACAN()
if self.tacan.BeaconUnit and self.tacan.BeaconUnit:IsAlive()then
self.tacan.BeaconUnit:CommandDeactivateBeacon()
end
self:T(self.lid..string.format("Switching TACAN OFF"))
self.tacan.On=false
end
function OPSGROUP:GetTACAN()
return self.tacan.Channel,self.tacan.Morse,self.tacan.Band,self.tacan.On,self.tacan.BeaconName
end
function OPSGROUP:GetBeaconTACAN()
return self.tacan
end
function OPSGROUP:SetDefaultICLS(Channel,Morse,UnitName,OffSwitch)
self.iclsDefault={}
self.iclsDefault.Channel=Channel or 1
self.iclsDefault.Morse=Morse or"XXX"
self.iclsDefault.BeaconName=UnitName
if OffSwitch then
self.iclsDefault.On=false
else
self.iclsDefault.On=true
end
return self
end
function OPSGROUP:_SwitchICLS(Icls)
if Icls then
self:SwitchICLS(Icls.Channel,Icls.Morse,Icls.BeaconName)
else
if self.iclsDefault.On then
self:SwitchICLS()
else
self:TurnOffICLS()
end
end
end
function OPSGROUP:SwitchICLS(Channel,Morse,UnitName)
if self:IsInUtero()then
self:SetDefaultICLS(Channel,Morse,UnitName)
self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED",self.iclsDefault.Channel,tostring(self.iclsDefault.Morse),tostring(self.iclsDefault.BeaconName)))
elseif self:IsAlive()then
Channel=Channel or self.iclsDefault.Channel
Morse=Morse or self.iclsDefault.Morse
local unit=self:GetUnit(1)
if UnitName then
if type(UnitName)=="number"then
unit=self:GetUnit(UnitName)
else
unit=UNIT:FindByName(UnitName)
end
end
if not unit then
self:T(self.lid.."WARNING: Could not get ICLS unit. Trying first unit in the group")
unit=self:GetUnit(1)
end
if unit and unit:IsAlive()then
local UnitID=unit:GetID()
unit:CommandActivateICLS(Channel,UnitID,Morse)
self.icls.Channel=Channel
self.icls.Morse=Morse
self.icls.Band=nil
self.icls.BeaconName=unit:GetName()
self.icls.BeaconUnit=unit
self.icls.On=true
self:T(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s",self.icls.Channel,tostring(self.icls.Morse),self.icls.BeaconName))
else
self:T(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.")
end
end
return self
end
function OPSGROUP:TurnOffICLS()
if self.icls.BeaconUnit and self.icls.BeaconUnit:IsAlive()then
self.icls.BeaconUnit:CommandDeactivateICLS()
end
self:T(self.lid..string.format("Switching ICLS OFF"))
self.icls.On=false
end
function OPSGROUP:SetDefaultRadio(Frequency,Modulation,OffSwitch)
self.radioDefault={}
self.radioDefault.Freq=Frequency or 251
self.radioDefault.Modu=Modulation or radio.modulation.AM
if OffSwitch then
self.radioDefault.On=false
else
self.radioDefault.On=true
end
return self
end
function OPSGROUP:GetRadio()
return self.radio.Freq,self.radio.Modu,self.radio.On
end
function OPSGROUP:SwitchRadio(Frequency,Modulation)
if self:IsInUtero()then
self:SetDefaultRadio(Frequency,Modulation)
self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED",self.radioDefault.Freq,UTILS.GetModulationName(self.radioDefault.Modu)))
elseif self:IsAlive()then
Frequency=Frequency or self.radioDefault.Freq
Modulation=Modulation or self.radioDefault.Modu
if self:IsFlightgroup()and not self.radio.On then
self.group:SetOption(AI.Option.Air.id.SILENCE,false)
end
self.group:CommandSetFrequency(Frequency,Modulation)
self.radio.Freq=Frequency
self.radio.Modu=Modulation
self.radio.On=true
self:T(self.lid..string.format("Switching radio to frequency %.3f MHz %s",self.radio.Freq,UTILS.GetModulationName(self.radio.Modu)))
else
self:T(self.lid.."ERROR: Cound not set Radio! Group is not alive or not in utero any more")
end
return self
end
function OPSGROUP:TurnOffRadio()
if self:IsAlive()then
if self:IsFlightgroup()then
self.group:SetOption(AI.Option.Air.id.SILENCE,true)
self.radio.On=false
self:T(self.lid..string.format("Switching radio OFF"))
else
self:T(self.lid.."ERROR: Radio can only be turned off for aircraft!")
end
end
return self
end
function OPSGROUP:SetDefaultFormation(Formation)
self.optionDefault.Formation=Formation
return self
end
function OPSGROUP:SwitchFormation(Formation)
if self:IsAlive()then
Formation=Formation or self.optionDefault.Formation
if self:IsFlightgroup()then
self.group:SetOption(AI.Option.Air.id.FORMATION,Formation)
elseif self.isArmygroup then
else
self:T(self.lid.."ERROR: Formation can only be set for aircraft or ground units!")
return self
end
self.option.Formation=Formation
self:T(self.lid..string.format("Switching formation to %s",tostring(self.option.Formation)))
end
return self
end
function OPSGROUP:SetDefaultCallsign(CallsignName,CallsignNumber)
self:T(self.lid..string.format("Setting Default callsing %s-%s",tostring(CallsignName),tostring(CallsignNumber)))
self.callsignDefault={}
self.callsignDefault.NumberSquad=CallsignName
self.callsignDefault.NumberGroup=CallsignNumber or 1
self.callsignDefault.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad)
return self
end
function OPSGROUP:SwitchCallsign(CallsignName,CallsignNumber)
if self:IsInUtero()then
self:SetDefaultCallsign(CallsignName,CallsignNumber)
elseif self:IsAlive()then
CallsignName=CallsignName or self.callsignDefault.NumberSquad
CallsignNumber=CallsignNumber or self.callsignDefault.NumberGroup
self.callsign.NumberSquad=CallsignName
self.callsign.NumberGroup=CallsignNumber
self:T(self.lid..string.format("Switching callsign to %d-%d",self.callsign.NumberSquad,self.callsign.NumberGroup))
self.group:CommandSetCallsign(self.callsign.NumberSquad,self.callsign.NumberGroup)
self.callsignName=UTILS.GetCallsignName(self.callsign.NumberSquad).."-"..self.callsign.NumberGroup
self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad)
for _,_element in pairs(self.elements)do
local element=_element
if element.status~=OPSGROUP.ElementStatus.DEAD then
element.callsign=element.unit:GetCallsign()
end
end
else
self:T(self.lid.."ERROR: Group is not alive and not in utero! Cannot switch callsign")
end
return self
end
function OPSGROUP:GetCallsignName(ShortCallsign,Keepnumber,CallsignTranslations)
local element=self:GetElementAlive()
if element then
self:T2(self.lid..string.format("Callsign %s",tostring(element.callsign)))
local name=element.callsign or"Ghostrider11"
name=name:gsub("-","")
if self.group:IsPlayer()or CallsignTranslations then
name=self.group:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
end
return name
end
return"Ghostrider11"
end
function OPSGROUP:_UpdatePosition()
if self:IsExist()then
self.positionLast=self.position or self:GetVec3()
self.headingLast=self.heading or self:GetHeading()
self.orientXLast=self.orientX or self:GetOrientationX()
self.velocityLast=self.velocity or self.group:GetVelocityMPS()
self.position=self:GetVec3()
self.heading=self:GetHeading()
self.orientX=self:GetOrientationX()
self.velocity=self:GetVelocity()
for _,_element in pairs(self.elements)do
local element=_element
element.vec3=self:GetVec3(element.name)
end
local Tnow=timer.getTime()
self.dTpositionUpdate=self.TpositionUpdate and Tnow-self.TpositionUpdate or 0
self.TpositionUpdate=Tnow
if not self.traveldist then
self.traveldist=0
end
self.travelds=UTILS.VecNorm(UTILS.VecSubstract(self.position,self.positionLast))
self.traveldist=self.traveldist+self.travelds
end
return self
end
function OPSGROUP:_AllSameStatus(status)
for _,_element in pairs(self.elements)do
local element=_element
if element.status==OPSGROUP.ElementStatus.DEAD then
elseif element.status~=status then
return false
end
end
return true
end
function OPSGROUP:_AllSimilarStatus(status)
if status==OPSGROUP.ElementStatus.DEAD then
for _,_element in pairs(self.elements)do
local element=_element
if element.status~=OPSGROUP.ElementStatus.DEAD then
return false
end
end
return true
end
for _,_element in pairs(self.elements)do
local element=_element
self:T2(self.lid..string.format("Status=%s, element %s status=%s",status,element.name,element.status))
if element.status~=OPSGROUP.ElementStatus.DEAD then
if status==OPSGROUP.ElementStatus.INUTERO then
if element.status~=status then
return false
end
elseif status==OPSGROUP.ElementStatus.SPAWNED then
if element.status~=status and
element.status==OPSGROUP.ElementStatus.INUTERO then
return false
end
elseif status==OPSGROUP.ElementStatus.PARKING then
if element.status~=status or
(element.status==OPSGROUP.ElementStatus.INUTERO or
element.status==OPSGROUP.ElementStatus.SPAWNED)then
return false
end
elseif status==OPSGROUP.ElementStatus.ENGINEON then
if element.status~=status and
(element.status==OPSGROUP.ElementStatus.INUTERO or
element.status==OPSGROUP.ElementStatus.SPAWNED or
element.status==OPSGROUP.ElementStatus.PARKING)then
return false
end
elseif status==OPSGROUP.ElementStatus.TAXIING then
if element.status~=status and
(element.status==OPSGROUP.ElementStatus.INUTERO or
element.status==OPSGROUP.ElementStatus.SPAWNED or
element.status==OPSGROUP.ElementStatus.PARKING or
element.status==OPSGROUP.ElementStatus.ENGINEON)then
return false
end
elseif status==OPSGROUP.ElementStatus.TAKEOFF then
if element.status~=status and
(element.status==OPSGROUP.ElementStatus.INUTERO or
element.status==OPSGROUP.ElementStatus.SPAWNED or
element.status==OPSGROUP.ElementStatus.PARKING or
element.status==OPSGROUP.ElementStatus.ENGINEON or
element.status==OPSGROUP.ElementStatus.TAXIING)then
return false
end
elseif status==OPSGROUP.ElementStatus.AIRBORNE then
if element.status~=status and
(element.status==OPSGROUP.ElementStatus.INUTERO or
element.status==OPSGROUP.ElementStatus.SPAWNED or
element.status==OPSGROUP.ElementStatus.PARKING or
element.status==OPSGROUP.ElementStatus.ENGINEON or
element.status==OPSGROUP.ElementStatus.TAXIING or
element.status==OPSGROUP.ElementStatus.TAKEOFF)then
return false
end
elseif status==OPSGROUP.ElementStatus.LANDED then
if element.status~=status and
(element.status==OPSGROUP.ElementStatus.AIRBORNE or
element.status==OPSGROUP.ElementStatus.LANDING)then
return false
end
elseif status==OPSGROUP.ElementStatus.ARRIVED then
if element.status~=status and
(element.status==OPSGROUP.ElementStatus.AIRBORNE or
element.status==OPSGROUP.ElementStatus.LANDING or
element.status==OPSGROUP.ElementStatus.LANDED)then
return false
end
end
else
end
end
self:T2(self.lid..string.format("All %d elements have similar status %s ==> returning TRUE",#self.elements,status))
return true
end
function OPSGROUP:_UpdateStatus(element,newstatus,airbase)
local oldstatus=element.status
element.status=newstatus
self:T3(self.lid..string.format("UpdateStatus element=%s: %s --> %s",element.name,oldstatus,newstatus))
for _,_element in pairs(self.elements)do
local Element=_element
self:T3(self.lid..string.format("Element %s: %s",Element.name,Element.status))
end
if newstatus==OPSGROUP.ElementStatus.INUTERO then
if self:_AllSimilarStatus(newstatus)then
self:InUtero()
end
elseif newstatus==OPSGROUP.ElementStatus.SPAWNED then
if self:_AllSimilarStatus(newstatus)then
self:Spawned()
end
elseif newstatus==OPSGROUP.ElementStatus.PARKING then
if self:_AllSimilarStatus(newstatus)then
self:Parking()
end
elseif newstatus==OPSGROUP.ElementStatus.ENGINEON then
elseif newstatus==OPSGROUP.ElementStatus.TAXIING then
if self:_AllSimilarStatus(newstatus)then
self:Taxiing()
end
elseif newstatus==OPSGROUP.ElementStatus.TAKEOFF then
if self:_AllSimilarStatus(newstatus)then
self:Takeoff(airbase)
end
elseif newstatus==OPSGROUP.ElementStatus.AIRBORNE then
if self:_AllSimilarStatus(newstatus)then
self:Airborne()
end
elseif newstatus==OPSGROUP.ElementStatus.LANDED then
if self:_AllSimilarStatus(newstatus)then
if self:IsLandingAt()then
self:LandedAt()
else
self:Landed(airbase)
end
end
elseif newstatus==OPSGROUP.ElementStatus.ARRIVED then
if self:_AllSimilarStatus(newstatus)then
if self:IsLanded()then
self:Arrived()
elseif self:IsAirborne()then
self:Landed()
self:Arrived()
end
end
elseif newstatus==OPSGROUP.ElementStatus.DEAD then
if self:_AllSimilarStatus(newstatus)then
self:Dead()
end
end
end
function OPSGROUP:_SetElementStatusAll(status)
for _,_element in pairs(self.elements)do
local element=_element
if element.status~=OPSGROUP.ElementStatus.DEAD then
element.status=status
end
end
end
function OPSGROUP:GetElementByName(unitname)
if unitname and type(unitname)=="string"then
for _,_element in pairs(self.elements)do
local element=_element
if element.name==unitname then
return element
end
end
end
return nil
end
function OPSGROUP:GetElementZoneBoundingBox(UnitName)
local element=self:GetElementByName(UnitName)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
element.zoneBoundingbox=element.zoneBoundingbox or ZONE_POLYGON_BASE:New(element.name.." Zone Bounding Box",{})
local l=element.length
local w=element.width
local X=self:GetOrientationX(element.name)
local heading=math.deg(math.atan2(X.z,X.x))
self:T(self.lid..string.format("Element %s bouding box: l=%d w=%d heading=%d",element.name,l,w,heading))
local b={}
b[1]={x=l/2,y=-w/2}
b[2]={x=l/2,y=w/2}
b[3]={x=-l/2,y=w/2}
b[4]={x=-l/2,y=-w/2}
for i,p in pairs(b)do
b[i]=UTILS.Vec2Rotate2D(p,heading)
end
local vec2=self:GetVec2(element.name)
local d=UTILS.Vec2Norm(vec2)
local h=UTILS.Vec2Hdg(vec2)
for i,p in pairs(b)do
b[i]=UTILS.Vec2Translate(p,d,h)
end
element.zoneBoundingbox:UpdateFromVec2(b)
return element.zoneBoundingbox
end
return nil
end
function OPSGROUP:GetElementZoneLoad(UnitName)
local element=self:GetElementByName(UnitName)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
element.zoneLoad=element.zoneLoad or ZONE_POLYGON_BASE:New(element.name.." Zone Load",{})
self:_GetElementZoneLoader(element,element.zoneLoad,self.carrierLoader)
return element.zoneLoad
end
return nil
end
function OPSGROUP:GetElementZoneUnload(UnitName)
local element=self:GetElementByName(UnitName)
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
element.zoneUnload=element.zoneUnload or ZONE_POLYGON_BASE:New(element.name.." Zone Unload",{})
self:_GetElementZoneLoader(element,element.zoneUnload,self.carrierUnloader)
return element.zoneUnload
end
return nil
end
function OPSGROUP:_GetElementZoneLoader(Element,Zone,Loader)
if Element.status~=OPSGROUP.ElementStatus.DEAD then
local l=Element.length
local w=Element.width
local X=self:GetOrientationX(Element.name)
local heading=math.deg(math.atan2(X.z,X.x))
local b={}
if Loader.type:lower()=="front"then
table.insert(b,{x=l/2,y=-Loader.width/2})
table.insert(b,{x=l/2+Loader.length,y=-Loader.width/2})
table.insert(b,{x=l/2+Loader.length,y=Loader.width/2})
table.insert(b,{x=l/2,y=Loader.width/2})
elseif Loader.type:lower()=="back"then
table.insert(b,{x=-l/2,y=-Loader.width/2})
table.insert(b,{x=-l/2-Loader.length,y=-Loader.width/2})
table.insert(b,{x=-l/2-Loader.length,y=Loader.width/2})
table.insert(b,{x=-l/2,y=Loader.width/2})
elseif Loader.type:lower()=="left"then
table.insert(b,{x=Loader.length/2,y=-w/2})
table.insert(b,{x=Loader.length/2,y=-w/2-Loader.width})
table.insert(b,{x=-Loader.length/2,y=-w/2-Loader.width})
table.insert(b,{x=-Loader.length/2,y=-w/2})
elseif Loader.type:lower()=="right"then
table.insert(b,{x=Loader.length/2,y=w/2})
table.insert(b,{x=Loader.length/2,y=w/2+Loader.width})
table.insert(b,{x=-Loader.length/2,y=w/2+Loader.width})
table.insert(b,{x=-Loader.length/2,y=w/2})
else
b[1]={x=l/2,y=-w/2}
b[2]={x=l/2,y=w/2}
b[3]={x=-l/2,y=w/2}
b[4]={x=-l/2,y=-w/2}
table.insert(b,{x=b[1].x+Loader.length,y=b[1].y-Loader.width})
table.insert(b,{x=b[2].x+Loader.length,y=b[2].y+Loader.width})
table.insert(b,{x=b[3].x-Loader.length,y=b[3].y+Loader.width})
table.insert(b,{x=b[4].x-Loader.length,y=b[4].y-Loader.width})
end
for i,p in pairs(b)do
b[i]=UTILS.Vec2Rotate2D(p,heading)
end
local vec2=self:GetVec2(Element.name)
local d=UTILS.Vec2Norm(vec2)
local h=UTILS.Vec2Hdg(vec2)
for i,p in pairs(b)do
b[i]=UTILS.Vec2Translate(p,d,h)
end
Zone:UpdateFromVec2(b)
return Zone
end
return nil
end
function OPSGROUP:GetElementAlive()
for _,_element in pairs(self.elements)do
local element=_element
if element.status~=OPSGROUP.ElementStatus.DEAD then
if element.unit and element.unit:IsAlive()then
return element
end
end
end
return nil
end
function OPSGROUP:GetNelements(status)
local n=0
for _,_element in pairs(self.elements)do
local element=_element
if element.status~=OPSGROUP.ElementStatus.DEAD then
if element.unit and element.unit:IsAlive()then
if status==nil or element.status==status then
n=n+1
end
end
end
end
return n
end
function OPSGROUP:GetAmmoElement(element)
return self:GetAmmoUnit(element.unit)
end
function OPSGROUP:GetAmmoTot()
local units=self.group:GetUnits()
local Ammo={}
Ammo.Total=0
Ammo.Guns=0
Ammo.Rockets=0
Ammo.Bombs=0
Ammo.Torpedos=0
Ammo.Missiles=0
Ammo.MissilesAA=0
Ammo.MissilesAG=0
Ammo.MissilesAS=0
Ammo.MissilesCR=0
Ammo.MissilesSA=0
for _,_unit in pairs(units or{})do
local unit=_unit
if unit and unit:IsExist()then
local ammo=self:GetAmmoUnit(unit)
Ammo.Total=Ammo.Total+ammo.Total
Ammo.Guns=Ammo.Guns+ammo.Guns
Ammo.Rockets=Ammo.Rockets+ammo.Rockets
Ammo.Bombs=Ammo.Bombs+ammo.Bombs
Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos
Ammo.Missiles=Ammo.Missiles+ammo.Missiles
Ammo.MissilesAA=Ammo.MissilesAA+ammo.MissilesAA
Ammo.MissilesAG=Ammo.MissilesAG+ammo.MissilesAG
Ammo.MissilesAS=Ammo.MissilesAS+ammo.MissilesAS
Ammo.MissilesCR=Ammo.MissilesCR+ammo.MissilesCR
Ammo.MissilesSA=Ammo.MissilesSA+ammo.MissilesSA
end
end
return Ammo
end
function OPSGROUP:GetAmmoUnit(unit,display)
if display==nil then
display=false
end
local nammo=0
local nshells=0
local nrockets=0
local nmissiles=0
local nmissilesAA=0
local nmissilesAG=0
local nmissilesAS=0
local nmissilesSA=0
local nmissilesBM=0
local nmissilesCR=0
local ntorps=0
local nbombs=0
unit=unit or self.group:GetUnit(1)
if unit and unit:IsExist()then
local text=string.format("OPSGROUP group %s - unit %s:\n",self.groupname,unit:GetName())
local ammotable=unit:GetAmmo()
if ammotable then
local weapons=#ammotable
for w=1,weapons do
local Nammo=ammotable[w]["count"]
local rmin=ammotable[w]["desc"]["rangeMin"]or 0
local rmax=ammotable[w]["desc"]["rangeMaxAltMin"]or 0
local Tammo=ammotable[w]["desc"]["typeName"]
local _weaponString=UTILS.Split(Tammo,"%.")
local _weaponName=_weaponString[#_weaponString]
local Category=ammotable[w].desc.category
local MissileCategory=nil
if Category==Weapon.Category.MISSILE then
MissileCategory=ammotable[w].desc.missileCategory
end
if Category==Weapon.Category.SHELL then
nshells=nshells+Nammo
text=text..string.format("- %d shells of type %s, range=%d - %d meters\n",Nammo,_weaponName,rmin,rmax)
elseif Category==Weapon.Category.ROCKET then
nrockets=nrockets+Nammo
text=text..string.format("- %d rockets of type %s, \n",Nammo,_weaponName,rmin,rmax)
elseif Category==Weapon.Category.BOMB then
nbombs=nbombs+Nammo
text=text..string.format("- %d bombs of type %s\n",Nammo,_weaponName)
elseif Category==Weapon.Category.MISSILE then
if MissileCategory==Weapon.MissileCategory.AAM then
nmissiles=nmissiles+Nammo
nmissilesAA=nmissilesAA+Nammo
elseif MissileCategory==Weapon.MissileCategory.SAM then
nmissiles=nmissiles+Nammo
nmissilesSA=nmissilesSA+Nammo
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
nmissiles=nmissiles+Nammo
nmissilesAS=nmissilesAS+Nammo
elseif MissileCategory==Weapon.MissileCategory.BM then
nmissiles=nmissiles+Nammo
nmissilesBM=nmissilesBM+Nammo
elseif MissileCategory==Weapon.MissileCategory.CRUISE then
nmissiles=nmissiles+Nammo
nmissilesCR=nmissilesCR+Nammo
elseif MissileCategory==Weapon.MissileCategory.OTHER then
nmissiles=nmissiles+Nammo
nmissilesAG=nmissilesAG+Nammo
end
text=text..string.format("- %d %s missiles of type %s, range=%d - %d meters\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName,rmin,rmax)
elseif Category==Weapon.Category.TORPEDO then
ntorps=ntorps+Nammo
text=text..string.format("- %d torpedos of type %s\n",Nammo,_weaponName)
else
text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory))
end
end
end
if display then
self:I(self.lid..text)
else
self:T3(self.lid..text)
end
end
nammo=nshells+nrockets+nmissiles+nbombs+ntorps
local ammo={}
ammo.Total=nammo
ammo.Guns=nshells
ammo.Rockets=nrockets
ammo.Bombs=nbombs
ammo.Torpedos=ntorps
ammo.Missiles=nmissiles
ammo.MissilesAA=nmissilesAA
ammo.MissilesAG=nmissilesAG
ammo.MissilesAS=nmissilesAS
ammo.MissilesCR=nmissilesCR
ammo.MissilesBM=nmissilesBM
ammo.MissilesSA=nmissilesSA
return ammo
end
function OPSGROUP:_MissileCategoryName(categorynumber)
local cat="unknown"
if categorynumber==Weapon.MissileCategory.AAM then
cat="air-to-air"
elseif categorynumber==Weapon.MissileCategory.SAM then
cat="surface-to-air"
elseif categorynumber==Weapon.MissileCategory.BM then
cat="ballistic"
elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then
cat="anti-ship"
elseif categorynumber==Weapon.MissileCategory.CRUISE then
cat="cruise"
elseif categorynumber==Weapon.MissileCategory.OTHER then
cat="other"
end
return cat
end
function OPSGROUP:_PassedFinalWaypoint(final,comment)
self:T(self.lid..string.format("Passed final waypoint=%s [from %s]: comment \"%s\"",tostring(final),tostring(self.passedfinalwp),tostring(comment)))
if final==true and not self.passedfinalwp then
self:PassedFinalWaypoint()
end
self.passedfinalwp=final
end
function OPSGROUP:_CoordinateFromObject(Object)
if Object then
if Object:IsInstanceOf("COORDINATE")then
return Object
else
if Object:IsInstanceOf("POSITIONABLE")or Object:IsInstanceOf("ZONE_BASE")then
self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate")
local coord=Object:GetCoordinate()
return coord
else
self:T(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!")
end
end
else
self:T(self.lid.."ERROR: Object passed is nil!")
end
return nil
end
function OPSGROUP:_IsElement(unitname)
for _,_element in pairs(self.elements)do
local element=_element
if element.name==unitname then
return true
end
end
return false
end
function OPSGROUP:CountElements(States)
if States then
if type(States)=="string"then
States={States}
end
else
States=OPSGROUP.ElementStatus
end
local IncludeDeads=true
local N=0
for _,_element in pairs(self.elements)do
local element=_element
if element and(IncludeDeads or element.status~=OPSGROUP.ElementStatus.DEAD)then
for _,state in pairs(States)do
if element.status==state then
N=N+1
break
end
end
end
end
return N
end
function OPSGROUP:_AddElementByName(unitname)
local unit=UNIT:FindByName(unitname)
if unit then
local element=self:GetElementByName(unitname)
if element then
else
element={}
element.status=OPSGROUP.ElementStatus.INUTERO
table.insert(self.elements,element)
end
element.name=unitname
element.unit=unit
element.DCSunit=Unit.getByName(unitname)
element.gid=element.DCSunit:getNumber()
element.uid=element.DCSunit:getID()
element.controller=element.DCSunit:getController()
element.Nhit=0
element.opsgroup=self
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)
element.playerName=element.DCSunit:getPlayerName()
else
element.ai=true
end
element.descriptors=unit:GetDesc()
element.category=unit:GetUnitCategory()
element.categoryname=unit:GetCategoryName()
element.typename=unit:GetTypeName()
element.ammo0=self:GetAmmoUnit(unit,false)
element.life=unit:GetLife()
element.life0=math.max(unit:GetLife0(),element.life)
element.size,element.length,element.height,element.width=unit:GetObjectSize()
element.weightEmpty=element.descriptors.massEmpty or 666
if self.isArmygroup then
element.weightMaxTotal=element.weightEmpty+10*95
elseif self.isNavygroup then
element.weightMaxTotal=element.weightEmpty+10*1000
else
element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+8*95
end
unit:SetCargoBayWeightLimit()
element.weightMaxCargo=unit.__.CargoBayWeightLimit
if element.cargoBay then
element.weightCargo=self:GetWeightCargo(element.name,false)
else
element.cargoBay={}
element.weightCargo=0
end
element.weight=element.weightEmpty+element.weightCargo
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
else
element.callsign="Peter-1-1"
element.modex="000"
element.payload={}
element.pylons={}
end
local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)",
element.name,element.status,element.skill,element.life,element.life0,element.categoryname,element.category,element.typename,
element.size,element.length,element.height,element.width,element.weight,element.weightMaxTotal,element.weightCargo,element.weightMaxCargo)
self:T(self.lid..text)
if unit:IsAlive()and element.status~=OPSGROUP.ElementStatus.SPAWNED then
self:__ElementSpawned(0.05,element)
end
return element
end
return nil
end
function OPSGROUP:_SetTemplate(Template)
self.template=Template or UTILS.DeepCopy(_DATABASE:GetGroupTemplate(self.groupname))
self:T3(self.lid.."Setting group template")
return self
end
function OPSGROUP:_GetTemplate(Copy)
if self.template then
if Copy then
local template=UTILS.DeepCopy(self.template)
return template
else
return self.template
end
else
self:T(self.lid..string.format("ERROR: No template was set yet!"))
end
return nil
end
function OPSGROUP:ClearWaypoints(IndexMin,IndexMax)
IndexMin=IndexMin or 1
IndexMax=IndexMax or#self.waypoints
for i=IndexMax,IndexMin,-1 do
table.remove(self.waypoints,i)
end
end
function OPSGROUP:_GetDetectedTarget()
local targetgroup=nil
local targetdist=math.huge
for _,_group in pairs(self.detectedgroups:GetSet())do
local group=_group
if group and group:IsAlive()then
local targetVec3=group:GetVec3()
local distance=UTILS.VecDist3D(self.position,targetVec3)
if distance<=self.engagedetectedRmax and distance<targetdist then
local righttype=false
for _,attribute in pairs(self.engagedetectedTypes)do
local gotit=group:HasAttribute(attribute,false)
self:T(self.lid..string.format("Group %s has attribute %s = %s",group:GetName(),attribute,tostring(gotit)))
if gotit then
righttype=true
break
end
end
if righttype then
local insideEngage=true
local insideNoEngage=false
if self.engagedetectedEngageZones then
insideEngage=false
for _,_zone in pairs(self.engagedetectedEngageZones.Set)do
local zone=_zone
local inzone=zone:IsVec3InZone(targetVec3)
if inzone then
insideEngage=true
break
end
end
end
if self.engagedetectedNoEngageZones then
for _,_zone in pairs(self.engagedetectedNoEngageZones.Set)do
local zone=_zone
local inzone=zone:IsVec3InZone(targetVec3)
if inzone then
insideNoEngage=true
break
end
end
end
if insideEngage and not insideNoEngage then
targetdist=distance
targetgroup=group
end
end
end
end
end
return targetgroup,targetdist
end
OPSTRANSPORT={
ClassName="OPSTRANSPORT",
verbose=0,
carriers={},
carrierTransportStatus={},
tzCombos={},
tzcCounter=0,
conditionStart={},
assets={},
legions={},
statusLegion={},
requestID={},
cargocounter=0,
}
OPSTRANSPORT.Status={
PLANNED="planned",
QUEUED="queued",
REQUESTED="requested",
SCHEDULED="scheduled",
EXECUTING="executing",
DELIVERED="delivered",
CANCELLED="cancelled",
SUCCESS="success",
FAILED="failed",
}
OPSTRANSPORT.CargoType={
OPSGROUP="OPSGROUP",
STORAGE="STORAGE",
}
_OPSTRANSPORTID=0
OPSTRANSPORT.version="0.8.0"
function OPSTRANSPORT:New(CargoGroups,PickupZone,DeployZone)
local self=BASE:Inherit(self,FSM:New())
_OPSTRANSPORTID=_OPSTRANSPORTID+1
self.lid=string.format("OPSTRANSPORT [UID=%d] | ",_OPSTRANSPORTID)
self.uid=_OPSTRANSPORTID
self:SetPriority()
self:SetTime()
self:SetRequiredCarriers()
self.formationArmy=ENUMS.Formation.Vehicle.OnRoad
self.formationHelo=ENUMS.Formation.RotaryWing.Wedge
self.formationPlane=ENUMS.Formation.FixedWing.Wedge
self.carriers={}
self.Ncargo=0
self.Ncarrier=0
self.Ndelivered=0
self.NcargoDead=0
self.NcarrierDead=0
self.tzcDefault=self:AddTransportZoneCombo(CargoGroups,PickupZone,DeployZone)
self:SetStartState(OPSTRANSPORT.Status.PLANNED)
self:AddTransition("*","Planned",OPSTRANSPORT.Status.PLANNED)
self:AddTransition(OPSTRANSPORT.Status.PLANNED,"Queued",OPSTRANSPORT.Status.QUEUED)
self:AddTransition(OPSTRANSPORT.Status.QUEUED,"Requested",OPSTRANSPORT.Status.REQUESTED)
self:AddTransition(OPSTRANSPORT.Status.REQUESTED,"Scheduled",OPSTRANSPORT.Status.SCHEDULED)
self:AddTransition(OPSTRANSPORT.Status.PLANNED,"Scheduled",OPSTRANSPORT.Status.SCHEDULED)
self:AddTransition(OPSTRANSPORT.Status.SCHEDULED,"Executing",OPSTRANSPORT.Status.EXECUTING)
self:AddTransition("*","Delivered",OPSTRANSPORT.Status.DELIVERED)
self:AddTransition("*","StatusUpdate","*")
self:AddTransition("*","Stop","*")
self:AddTransition("*","Cancel",OPSTRANSPORT.Status.CANCELLED)
self:AddTransition("*","Loaded","*")
self:AddTransition("*","Unloaded","*")
self:AddTransition("*","DeadCarrierUnit","*")
self:AddTransition("*","DeadCarrierGroup","*")
self:AddTransition("*","DeadCarrierAll","*")
self:__StatusUpdate(-1)
return self
end
function OPSTRANSPORT:AddTransportZoneCombo(CargoGroups,PickupZone,DeployZone)
self.tzcCounter=self.tzcCounter+1
local tzcombo={}
tzcombo.uid=self.tzcCounter
tzcombo.Ncarriers=0
tzcombo.Ncargo=0
tzcombo.Cargos={}
tzcombo.RequiredCargos={}
tzcombo.DisembarkCarriers={}
tzcombo.PickupPaths={}
tzcombo.TransportPaths={}
self:SetPickupZone(PickupZone,tzcombo)
self:SetDeployZone(DeployZone,tzcombo)
self:SetEmbarkZone(nil,tzcombo)
if CargoGroups then
self:AddCargoGroups(CargoGroups,tzcombo)
end
table.insert(self.tzCombos,tzcombo)
return tzcombo
end
function OPSTRANSPORT:AddCargoGroups(GroupSet,TransportZoneCombo,DisembarkActivation,DisembarkZone,DisembarkCarriers)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
if GroupSet:IsInstanceOf("GROUP")or GroupSet:IsInstanceOf("OPSGROUP")then
local cargo=self:_CreateCargoGroupData(GroupSet,TransportZoneCombo,DisembarkActivation,DisembarkZone,DisembarkCarriers)
if cargo then
self.Ncargo=self.Ncargo+1
table.insert(TransportZoneCombo.Cargos,cargo)
TransportZoneCombo.Ncargo=TransportZoneCombo.Ncargo+1
cargo.opsgroup:_AddMyLift(self)
end
else
for _,group in pairs(GroupSet.Set)do
self:AddCargoGroups(group,TransportZoneCombo,DisembarkActivation)
end
local groupset=GroupSet
function groupset.OnAfterAdded(groupset,From,Event,To,ObjectName,Object)
self:T(self.lid..string.format("Adding Cargo Group %s",tostring(ObjectName)))
self:AddCargoGroups(Object,TransportZoneCombo,DisembarkActivation,DisembarkZone,DisembarkCarriers)
end
end
if self.verbose>=1 then
local text=string.format("Added cargo groups:")
local Weight=0
for _,_cargo in pairs(self:GetCargos())do
local cargo=_cargo
local weight=cargo.opsgroup:GetWeightTotal()
Weight=Weight+weight
text=text..string.format("\n- %s [%s] weight=%.1f kg",cargo.opsgroup:GetName(),cargo.opsgroup:GetState(),weight)
end
text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg",self.Ncargo,Weight)
self:I(self.lid..text)
end
return self
end
function OPSTRANSPORT:AddCargoStorage(StorageFrom,StorageTo,CargoType,CargoAmount,CargoWeight,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
local cargo=self:_CreateCargoStorage(StorageFrom,StorageTo,CargoType,CargoAmount,CargoWeight,TransportZoneCombo)
if cargo then
self.Ncargo=self.Ncargo+1
table.insert(TransportZoneCombo.Cargos,cargo)
end
end
function OPSTRANSPORT:SetPickupZone(PickupZone,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.PickupZone=PickupZone
if PickupZone and PickupZone:IsInstanceOf("ZONE_AIRBASE")then
TransportZoneCombo.PickupAirbase=PickupZone._.ZoneAirbase
end
return self
end
function OPSTRANSPORT:GetPickupZone(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.PickupZone
end
function OPSTRANSPORT:SetDeployZone(DeployZone,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.DeployZone=DeployZone
if DeployZone and DeployZone:IsInstanceOf("ZONE_AIRBASE")then
TransportZoneCombo.DeployAirbase=DeployZone._.ZoneAirbase
end
return self
end
function OPSTRANSPORT:GetDeployZone(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.DeployZone
end
function OPSTRANSPORT:SetEmbarkZone(EmbarkZone,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.EmbarkZone=EmbarkZone or TransportZoneCombo.PickupZone
return self
end
function OPSTRANSPORT:GetEmbarkZone(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.EmbarkZone
end
function OPSTRANSPORT:SetDisembarkZone(DisembarkZone,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.DisembarkZone=DisembarkZone
return self
end
function OPSTRANSPORT:GetDisembarkZone(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.DisembarkZone
end
function OPSTRANSPORT:SetDisembarkActivation(Active,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
if Active==true or Active==nil then
TransportZoneCombo.disembarkActivation=true
else
TransportZoneCombo.disembarkActivation=false
end
return self
end
function OPSTRANSPORT:GetDisembarkActivation(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.disembarkActivation
end
function OPSTRANSPORT:SetDisembarkCarriers(Carriers,TransportZoneCombo)
self:T(self.lid.."Setting transfer carriers!")
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.disembarkToCarriers=true
self:_AddDisembarkCarriers(Carriers,TransportZoneCombo.DisembarkCarriers)
return self
end
function OPSTRANSPORT:_AddDisembarkCarriers(Carriers,Table)
if Carriers:IsInstanceOf("GROUP")or Carriers:IsInstanceOf("OPSGROUP")then
local carrier=self:_GetOpsGroupFromObject(Carriers)
if carrier then
table.insert(Table,carrier)
end
elseif Carriers:IsInstanceOf("SET_GROUP")or Carriers:IsInstanceOf("SET_OPSGROUP")then
for _,object in pairs(Carriers:GetSet())do
local carrier=self:_GetOpsGroupFromObject(object)
if carrier then
table.insert(Table,carrier)
end
end
else
self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!")
end
end
function OPSTRANSPORT:GetDisembarkCarriers(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.DisembarkCarriers
end
function OPSTRANSPORT:SetDisembarkInUtero(InUtero,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
if InUtero==true or InUtero==nil then
TransportZoneCombo.disembarkInUtero=true
else
TransportZoneCombo.disembarkInUtero=false
end
return self
end
function OPSTRANSPORT:GetDisembarkInUtero(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.disembarkInUtero
end
function OPSTRANSPORT:SetFormationPickup(Formation,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.PickupFormation=Formation
return self
end
function OPSTRANSPORT:_GetFormationDefault(OpsGroup)
if OpsGroup.isArmygroup then
return self.formationArmy
elseif OpsGroup.isFlightgroup then
if OpsGroup.isHelo then
return self.formationHelo
else
return self.formationPlane
end
else
return ENUMS.Formation.Vehicle.OffRoad
end
return nil
end
function OPSTRANSPORT:_GetFormationPickup(TransportZoneCombo,OpsGroup)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
local formation=TransportZoneCombo.PickupFormation or self:_GetFormationDefault(OpsGroup)
return formation
end
function OPSTRANSPORT:SetFormationTransport(Formation,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.TransportFormation=Formation
return self
end
function OPSTRANSPORT:_GetFormationTransport(TransportZoneCombo,OpsGroup)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
local formation=TransportZoneCombo.TransportFormation or self:_GetFormationDefault(OpsGroup)
return formation
end
function OPSTRANSPORT:SetRequiredCargos(Cargos,TransportZoneCombo)
self:T(self.lid.."Setting required cargos!")
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
TransportZoneCombo.RequiredCargos=TransportZoneCombo.RequiredCargos or{}
if Cargos:IsInstanceOf("GROUP")or Cargos:IsInstanceOf("OPSGROUP")then
local cargo=self:_GetOpsGroupFromObject(Cargos)
if cargo then
table.insert(TransportZoneCombo.RequiredCargos,cargo)
end
elseif Cargos:IsInstanceOf("SET_GROUP")or Cargos:IsInstanceOf("SET_OPSGROUP")then
for _,object in pairs(Cargos:GetSet())do
local cargo=self:_GetOpsGroupFromObject(object)
if cargo then
table.insert(TransportZoneCombo.RequiredCargos,cargo)
end
end
else
self:E(self.lid.."ERROR: Required Cargos must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!")
end
return self
end
function OPSTRANSPORT:GetRequiredCargos(TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
return TransportZoneCombo.RequiredCargos
end
function OPSTRANSPORT:SetRequiredCarriers(NcarriersMin,NcarriersMax)
self.nCarriersMin=NcarriersMin or 1
self.nCarriersMax=NcarriersMax or self.nCarriersMin
if self.nCarriersMax<self.nCarriersMin then
self.nCarriersMax=self.nCarriersMin
end
return self
end
function OPSTRANSPORT:GetRequiredCarriers()
return self.nCarriersMin,self.nCarriersMax
end
function OPSTRANSPORT:_AddCarrier(CarrierGroup)
if not self:IsCarrier(CarrierGroup)then
self.Ncarrier=self.Ncarrier+1
self:SetCarrierTransportStatus(CarrierGroup,OPSTRANSPORT.Status.SCHEDULED)
self:Scheduled()
table.insert(self.carriers,CarrierGroup)
end
return self
end
function OPSTRANSPORT:_DelCarrier(CarrierGroup,Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay,OPSTRANSPORT._DelCarrier,self,CarrierGroup)
else
if self:IsCarrier(CarrierGroup)then
for i=#self.carriers,1,-1 do
local carrier=self.carriers[i]
if carrier.groupname==CarrierGroup.groupname then
self:T(self.lid..string.format("Removing carrier %s",CarrierGroup.groupname))
table.remove(self.carriers,i)
end
end
end
end
return self
end
function OPSTRANSPORT:_GetCarrierNames()
local names={}
for _,_carrier in pairs(self.carriers)do
local carrier=_carrier
if carrier:IsAlive()~=nil then
table.insert(names,carrier.groupname)
end
end
return names
end
function OPSTRANSPORT:GetCargoOpsGroups(Delivered,Carrier,TransportZoneCombo)
local cargos=self:GetCargos(TransportZoneCombo,Carrier,Delivered)
local opsgroups={}
for _,_cargo in pairs(cargos)do
local cargo=_cargo
if cargo.type=="OPSGROUP"then
if cargo.opsgroup and not(cargo.opsgroup:IsDead()or cargo.opsgroup:IsStopped())then
table.insert(opsgroups,cargo.opsgroup)
end
end
end
return opsgroups
end
function OPSTRANSPORT:GetCargoStorages(Delivered,Carrier,TransportZoneCombo)
local cargos=self:GetCargos(TransportZoneCombo,Carrier,Delivered)
local opsgroups={}
for _,_cargo in pairs(cargos)do
local cargo=_cargo
if cargo.type=="STORAGE"then
table.insert(opsgroups,cargo.storage)
end
end
return opsgroups
end
function OPSTRANSPORT:GetCarriers()
return self.carriers
end
function OPSTRANSPORT:GetCargos(TransportZoneCombo,Carrier,Delivered)
local tczs=self.tzCombos
if TransportZoneCombo then
tczs={TransportZoneCombo}
end
local cargos={}
for _,_tcz in pairs(tczs)do
local tcz=_tcz
for _,_cargo in pairs(tcz.Cargos)do
local cargo=_cargo
if Delivered==nil or cargo.delivered==Delivered then
if Carrier==nil or Carrier:CanCargo(cargo)then
table.insert(cargos,cargo)
end
end
end
end
return cargos
end
function OPSTRANSPORT:GetCargoTotalWeight(Cargo,IncludeReserved)
local weight=0
if Cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
weight=Cargo.opsgroup:GetWeightTotal(nil,IncludeReserved)
else
if type(Cargo.storage.cargoType)=="number"then
if IncludeReserved then
return Cargo.storage.cargoAmount+Cargo.storage.cargoReserved
else
return Cargo.storage.cargoAmount
end
else
if IncludeReserved then
return Cargo.storage.cargoAmount*100
else
return(Cargo.storage.cargoAmount+Cargo.storage.cargoReserved)*100
end
end
end
return weight
end
function OPSTRANSPORT:SetTime(ClockStart,ClockStop)
local Tnow=timer.getAbsTime()
local Tstart=Tnow+5
if ClockStart and type(ClockStart)=="number"then
Tstart=Tnow+ClockStart
elseif ClockStart and type(ClockStart)=="string"then
Tstart=UTILS.ClockToSeconds(ClockStart)
end
local Tstop=nil
if ClockStop and type(ClockStop)=="number"then
Tstop=Tnow+ClockStop
elseif ClockStop and type(ClockStop)=="string"then
Tstop=UTILS.ClockToSeconds(ClockStop)
end
self.Tstart=Tstart
self.Tstop=Tstop
if Tstop then
self.duration=self.Tstop-self.Tstart
end
return self
end
function OPSTRANSPORT:SetPriority(Prio,Importance,Urgent)
self.prio=Prio or 50
self.urgent=Urgent
self.importance=Importance
return self
end
function OPSTRANSPORT:SetVerbosity(Verbosity)
self.verbose=Verbosity or 0
return self
end
function OPSTRANSPORT:AddConditionStart(ConditionFunction,...)
if ConditionFunction then
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionStart,condition)
end
return self
end
function OPSTRANSPORT:AddPathTransport(PathGroup,Reversed,Radius,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
if type(PathGroup)=="string"then
PathGroup=GROUP:FindByName(PathGroup)
end
local path={}
path.category=PathGroup:GetCategory()
path.radius=Radius or 0
path.waypoints=PathGroup:GetTaskRoute()
table.insert(TransportZoneCombo.TransportPaths,path)
return self
end
function OPSTRANSPORT:_GetPathTransport(Category,TransportZoneCombo)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
local pathsTransport=TransportZoneCombo.TransportPaths
if pathsTransport and#pathsTransport>0 then
local paths={}
for _,_path in pairs(pathsTransport)do
local path=_path
if path.category==Category then
table.insert(paths,path)
end
end
if#paths>0 then
local path=paths[math.random(#paths)]
return path
end
end
return nil
end
function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup,Status)
local oldstatus=self:GetCarrierTransportStatus(CarrierGroup)
self:T(self.lid..string.format("New carrier transport status for %s: %s --> %s",CarrierGroup:GetName(),oldstatus,Status))
self.carrierTransportStatus[CarrierGroup.groupname]=Status
return self
end
function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup)
local status=self.carrierTransportStatus[CarrierGroup.groupname]or"unknown"
return status
end
function OPSTRANSPORT:GetUID()
return self.uid
end
function OPSTRANSPORT:GetNcargoDelivered()
return self.Ndelivered
end
function OPSTRANSPORT:GetNcargoTotal()
return self.Ncargo
end
function OPSTRANSPORT:GetNcarrier()
return self.Ncarrier
end
function OPSTRANSPORT:AddAsset(Asset,TransportZoneCombo)
self:T(self.lid..string.format("Adding asset carrier \"%s\" to transport",tostring(Asset.spawngroupname)))
self.assets=self.assets or{}
table.insert(self.assets,Asset)
return self
end
function OPSTRANSPORT:DelAsset(Asset)
for i,_asset in pairs(self.assets or{})do
local asset=_asset
if asset.uid==Asset.uid then
self:T(self.lid..string.format("Removing asset \"%s\" from transport",tostring(Asset.spawngroupname)))
table.remove(self.assets,i)
return self
end
end
return self
end
function OPSTRANSPORT:AddAssetCargo(Asset,TransportZoneCombo)
self:T(self.lid..string.format("Adding asset cargo \"%s\" to transport and TZC=%s",tostring(Asset.spawngroupname),TransportZoneCombo and TransportZoneCombo.uid or"N/A"))
self.assetsCargo=self.assetsCargo or{}
table.insert(self.assetsCargo,Asset)
TransportZoneCombo.assetsCargo=TransportZoneCombo.assetsCargo or{}
TransportZoneCombo.assetsCargo[Asset.spawngroupname]=Asset
return self
end
function OPSTRANSPORT:GetTZCofCargo(GroupName)
for _,_tzc in pairs(self.tzCombos)do
local tzc=_tzc
for _,_cargo in pairs(tzc.Cargos)do
local cargo=_cargo
if cargo.opsgroup:GetName()==GroupName then
return tzc
end
end
end
return nil
end
function OPSTRANSPORT:AddLegion(Legion)
self:T(self.lid..string.format("Adding legion %s",Legion.alias))
table.insert(self.legions,Legion)
return self
end
function OPSTRANSPORT:RemoveLegion(Legion)
for i=#self.legions,1,-1 do
local legion=self.legions[i]
if legion.alias==Legion.alias then
self:T(self.lid..string.format("Removing legion %s",Legion.alias))
table.remove(self.legions,i)
return self
end
end
self:E(self.lid..string.format("ERROR: Legion %s not found and could not be removed!",Legion.alias))
return self
end
function OPSTRANSPORT:IsCarrier(CarrierGroup)
if CarrierGroup then
for _,_carrier in pairs(self.carriers)do
local carrier=_carrier
if carrier.groupname==CarrierGroup.groupname then
return true
end
end
end
return false
end
function OPSTRANSPORT:IsReadyToGo()
local text=self.lid.."Is ReadyToGo? "
local Tnow=timer.getAbsTime()
local gotzones=false
for _,_tz in pairs(self.tzCombos)do
local tz=_tz
if tz.PickupZone and tz.DeployZone then
gotzones=true
break
end
end
if not gotzones then
text=text.."No, pickup/deploy zone combo not yet defined!"
return false
end
if self.Tstart and Tnow<self.Tstart then
text=text.."No, start time not passed!"
return false
end
if self.Tstop and Tnow>self.Tstop then
text=text.."Nope, stop time already passed!"
self:T(text)
return false
end
local startme=self:EvalConditionsAll(self.conditionStart)
if not startme then
text=text..("No way, at least one start condition is not true!")
self:T(text)
return false
end
text=text.."Yes!"
self:T(text)
return true
end
function OPSTRANSPORT:SetLegionStatus(Legion,Status)
local status=self:GetLegionStatus(Legion)
self:T(self.lid..string.format("Setting LEGION %s to status %s-->%s",Legion.alias,tostring(status),tostring(Status)))
self.statusLegion[Legion.alias]=Status
return self
end
function OPSTRANSPORT:GetLegionStatus(Legion)
local status=self.statusLegion[Legion.alias]or"unknown"
return status
end
function OPSTRANSPORT:IsPlanned()
local is=self:is(OPSTRANSPORT.Status.PLANNED)
return is
end
function OPSTRANSPORT:IsQueued(Legion)
local is=self:is(OPSTRANSPORT.Status.QUEUED)
if Legion then
is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.QUEUED
end
return is
end
function OPSTRANSPORT:IsRequested(Legion)
local is=self:is(OPSTRANSPORT.Status.REQUESTED)
if Legion then
is=self:GetLegionStatus(Legion)==OPSTRANSPORT.Status.REQUESTED
end
return is
end
function OPSTRANSPORT:IsScheduled()
local is=self:is(OPSTRANSPORT.Status.SCHEDULED)
return is
end
function OPSTRANSPORT:IsExecuting()
local is=self:is(OPSTRANSPORT.Status.EXECUTING)
return is
end
function OPSTRANSPORT:IsDelivered(Nmin)
local is=self:is(OPSTRANSPORT.Status.DELIVERED)
if is==false and Nmin and self.Ndelivered>=math.min(self.Ncargo,Nmin)then
is=true
end
return is
end
function OPSTRANSPORT:onafterStatusUpdate(From,Event,To)
local fsmstate=self:GetState()
if self.verbose>=1 then
local text=string.format("%s: Ncargo=%d/%d, Ncarrier=%d/%d, Nlegions=%d",fsmstate:upper(),self.Ncargo,self.Ndelivered,#self.carriers,self.Ncarrier,#self.legions)
if self.verbose>=2 then
for i,_tz in pairs(self.tzCombos)do
local tz=_tz
local pickupzone=tz.PickupZone and tz.PickupZone:GetName()or"Unknown"
local deployzone=tz.DeployZone and tz.DeployZone:GetName()or"Unknown"
text=text..string.format("\n[%d] %s --> %s: Ncarriers=%d, Ncargo=%d (%d)",i,pickupzone,deployzone,tz.Ncarriers,#tz.Cargos,tz.Ncargo)
end
end
if self.verbose>=3 then
text=text..string.format("\nCargos:")
for _,_cargo in pairs(self:GetCargos())do
local cargo=_cargo
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
local carrier=cargo.opsgroup:_GetMyCarrierElement()
local name=carrier and carrier.name or"none"
local cstate=carrier and carrier.status or"N/A"
text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s [UID=%s]",
cargo.opsgroup:GetName(),cargo.opsgroup.cargoStatus:upper(),cargo.opsgroup:GetState(),cargo.opsgroup:GetWeightTotal(),name,cstate,tostring(cargo.delivered),tostring(cargo.opsgroup.cargoTransportUID))
else
local storage=cargo.storage
text=text..string.format("\n- storage type=%s: amount: total=%d loaded=%d, lost=%d, delivered=%d, delivered=%s [UID=%s]",
storage.cargoType,storage.cargoAmount,storage.cargoLoaded,storage.cargoLost,storage.cargoDelivered,tostring(cargo.delivered),tostring(cargo.uid))
end
end
text=text..string.format("\nCarriers:")
for _,_carrier in pairs(self.carriers)do
local carrier=_carrier
text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]",
carrier:GetName(),carrier.carrierStatus:upper(),carrier:GetState(),
carrier:GetWeightCargo(nil,false),carrier:GetWeightCargo(),carrier:GetWeightCargoMax(),
carrier:GetFreeCargobay(nil,false),carrier:GetFreeCargobay(),carrier:GetFreeCargobayMax())
end
end
self:I(self.lid..text)
end
self:_CheckDelivered()
if not self:IsDelivered()then
self:__StatusUpdate(-30)
end
end
function OPSTRANSPORT:IsCargoDelivered(GroupName)
for _,_cargo in pairs(self:GetCargos())do
local cargo=_cargo
if cargo.opsgroup:GetName()==GroupName then
return cargo.delivered
end
end
return nil
end
function OPSTRANSPORT:onafterPlanned(From,Event,To)
self:T(self.lid..string.format("New status: %s-->%s",From,To))
end
function OPSTRANSPORT:onafterScheduled(From,Event,To)
self:T(self.lid..string.format("New status: %s-->%s",From,To))
end
function OPSTRANSPORT:onafterExecuting(From,Event,To)
self:T(self.lid..string.format("New status: %s-->%s",From,To))
end
function OPSTRANSPORT:onbeforeDelivered(From,Event,To)
if From==OPSTRANSPORT.Status.DELIVERED then
return false
end
return true
end
function OPSTRANSPORT:onafterDelivered(From,Event,To)
self:T(self.lid..string.format("New status: %s-->%s",From,To))
for i=#self.carriers,1,-1 do
local carrier=self.carriers[i]
if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then
carrier:Delivered(self)
end
end
end
function OPSTRANSPORT:onafterLoaded(From,Event,To,OpsGroupCargo,OpsGroupCarrier,CarrierElement)
self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s",OpsGroupCargo:GetName(),tostring(CarrierElement.name)))
end
function OPSTRANSPORT:onafterUnloaded(From,Event,To,OpsGroupCargo,OpsGroupCarrier)
self:I(self.lid..string.format("Unloaded OPSGROUP %s",OpsGroupCargo:GetName()))
end
function OPSTRANSPORT:onafterDeadCarrierGroup(From,Event,To,OpsGroup)
self:I(self.lid..string.format("Carrier OPSGROUP %s dead!",OpsGroup:GetName()))
self.NcarrierDead=self.NcarrierDead+1
self:_DelCarrier(OpsGroup)
if#self.carriers==0 then
self:DeadCarrierAll()
end
end
function OPSTRANSPORT:onafterDeadCarrierAll(From,Event,To)
self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead!"))
if self.opszone then
self:I(self.lid..string.format("Cancelling transport on CHIEF level"))
self.chief:TransportCancel(self)
else
self:_CheckDelivered()
if not self:IsDelivered()then
self:Planned()
end
end
end
function OPSTRANSPORT:onafterCancel(From,Event,To)
local Ngroups=#self.carriers
self:I(self.lid..string.format("CANCELLING transport in status %s. Will wait for %d carrier groups to report DONE before evaluation",self:GetState(),Ngroups))
self.Tover=timer.getAbsTime()
if self.chief then
self:T(self.lid..string.format("CHIEF will cancel the transport. Will wait for mission DONE before evaluation!"))
self.chief:TransportCancel(self)
elseif self.commander then
self:T(self.lid..string.format("COMMANDER will cancel the transport. Will wait for transport DELIVERED before evaluation!"))
self.commander:TransportCancel(self)
elseif self.legions and#self.legions>0 then
for _,_legion in pairs(self.legions or{})do
local legion=_legion
self:T(self.lid..string.format("LEGION %s will cancel the transport. Will wait for transport DELIVERED before evaluation!",legion.alias))
legion:TransportCancel(self)
end
else
self:T(self.lid..string.format("No legion, commander or chief. Attached OPS groups will cancel the transport on their own. Will wait for transport DELIVERED before evaluation!"))
for _,_carrier in pairs(self:GetCarriers())do
local carrier=_carrier
carrier:TransportCancel(self)
end
local cargos=self:GetCargoOpsGroups(false)
for _,_cargo in pairs(cargos)do
local cargo=_cargo
cargo:_DelMyLift(self)
end
end
if self:IsPlanned()or self:IsQueued()or self:IsRequested()or Ngroups==0 then
self:T(self.lid..string.format("Cancelled transport was in %s stage with %d carrier groups assigned and alive. Call it DELIVERED!",self:GetState(),Ngroups))
self:Delivered()
end
end
function OPSTRANSPORT:_CheckDelivered()
if self.Ncargo>0 then
local done=true
local dead=true
for _,_cargo in pairs(self:GetCargos())do
local cargo=_cargo
if cargo.delivered then
dead=false
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup==nil then
dead=false
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDestroyed()then
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsDead()then
elseif cargo.type==OPSTRANSPORT.CargoType.OPSGROUP and cargo.opsgroup:IsStopped()then
dead=false
else
done=false
dead=false
end
end
if dead then
self:I(self.lid.."All cargo DEAD ==> Delivered!")
self:Delivered()
elseif done then
self:I(self.lid.."All cargo DONE ==> Delivered!")
self:Delivered()
end
end
end
function OPSTRANSPORT:_CheckRequiredCargos(TransportZoneCombo,CarrierGroup)
TransportZoneCombo=TransportZoneCombo or self.tzcDefault
local requiredCargos=TransportZoneCombo.Cargos
if TransportZoneCombo.RequiredCargos and#TransportZoneCombo.RequiredCargos>0 then
requiredCargos=TransportZoneCombo.RequiredCargos
else
requiredCargos={}
for _,_cargo in pairs(TransportZoneCombo.Cargos)do
local cargo=_cargo
table.insert(requiredCargos,cargo.opsgroup)
end
end
if requiredCargos==nil or#requiredCargos==0 then
return true
end
local carrierNames=self:_GetCarrierNames()
local weightmin=nil
for _,_cargo in pairs(requiredCargos)do
local cargo=_cargo
local isLoaded=cargo:IsLoaded(carrierNames)
if not isLoaded then
local weight=cargo:GetWeightTotal()
if weightmin==nil or weight<weightmin then
weightmin=weight
end
end
end
if weightmin then
local freeSpace=CarrierGroup:GetFreeCargobayMax(true)
self:T(self.lid..string.format("Check required cargos for carrier=%s free=%.1f, weight=%.1f",CarrierGroup:GetName(),freeSpace,weightmin))
if weightmin<freeSpace then
return false
else
return true
end
end
return true
end
function OPSTRANSPORT:EvalConditionsAll(Conditions)
for _,_condition in pairs(Conditions or{})do
local condition=_condition
local istrue=condition.func(unpack(condition.arg))
if not istrue then
return false
end
end
return true
end
function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup,Zone,DisembarkCarriers,DeployAirbase)
local carrier=nil
local carrierGroup=nil
for _,_carrier in pairs(DisembarkCarriers or{})do
local carrierGroup=_carrier
if carrierGroup and carrierGroup:IsAlive()and(carrierGroup:IsLoading()or DeployAirbase)then
carrier=carrierGroup:FindCarrierForCargo(CargoGroup)
if carrier then
if Zone==nil or Zone:IsVec2InZone(carrier.unit:GetVec2())then
return carrier,carrierGroup
else
self:T2(self.lid.."Got transfer carrier but carrier not in zone (yet)!")
end
else
self:T2(self.lid.."No transfer carrier available!")
end
end
end
self:T2(self.lid.."Could NOT find any carrier that is ALIVE and LOADING (or DELOYAIRBASE))!")
return nil,nil
end
function OPSTRANSPORT:_CreateCargoGroupData(group,TransportZoneCombo,DisembarkActivation,DisembarkZone,DisembarkCarriers)
local opsgroup=self:_GetOpsGroupFromObject(group)
for _,_cargo in pairs(TransportZoneCombo.Cargos or{})do
local cargo=_cargo
if cargo.opsgroup.groupname==opsgroup.groupname then
return nil
end
end
self.cargocounter=self.cargocounter+1
local cargo={}
cargo.uid=self.cargocounter
cargo.type="OPSGROUP"
cargo.opsgroup=opsgroup
cargo.delivered=false
cargo.status="Unknown"
cargo.tzcUID=TransportZoneCombo
cargo.disembarkZone=DisembarkZone
if DisembarkCarriers then
cargo.disembarkCarriers={}
self:_AddDisembarkCarriers(DisembarkCarriers,cargo.disembarkCarriers)
end
return cargo
end
function OPSTRANSPORT:_CreateCargoStorage(StorageFrom,StorageTo,CargoType,CargoAmount,CargoWeight,TransportZoneCombo)
local storage={}
storage.storageFrom=StorageFrom
storage.storageTo=StorageTo
storage.cargoType=CargoType
storage.cargoAmount=CargoAmount
storage.cargoDelivered=0
storage.cargoLost=0
storage.cargoReserved=0
storage.cargoLoaded=0
storage.cargoWeight=CargoWeight or 1
self.cargocounter=self.cargocounter+1
local cargo={}
cargo.uid=self.cargocounter
cargo.type="STORAGE"
cargo.opsgroup=nil
cargo.storage=storage
cargo.delivered=false
cargo.status="Unknown"
cargo.tzcUID=TransportZoneCombo
cargo.disembarkZone=nil
cargo.disembarkCarriers=nil
return cargo
end
function OPSTRANSPORT:_CountCargosInZone(Zone,Delivered,Carrier,TransportZoneCombo)
local cargos=self:GetCargos(TransportZoneCombo,Carrier,Delivered)
local function iscarrier(_cargo)
local cargo=_cargo
local mycarrier=cargo:_GetMyCarrierGroup()
if mycarrier and mycarrier:IsUnloading()then
local carriers=mycarrier.cargoTransport:GetDisembarkCarriers(mycarrier.cargoTZC)
for _,_carrier in pairs(carriers)do
local carrier=_carrier
if Carrier:GetName()==carrier:GetName()then
return true
end
end
if mycarrier.cargoTZC and mycarrier.cargoTZC.Cargos then
for _,_cargodata in pairs(mycarrier.cargoTZC.Cargos)do
local cargodata=_cargodata
if cargo:GetName()==cargodata.opsgroup:GetName()then
for _,_carrier in pairs(cargodata.disembarkCarriers)do
local carrier=_carrier
if Carrier:GetName()==carrier:GetName()then
return true
end
end
end
end
end
end
return false
end
local N=0
for _,_cargo in pairs(cargos)do
local cargo=_cargo
local isNotCargo=true
local isInZone=true
local isInUtero=true
if cargo.type==OPSTRANSPORT.CargoType.OPSGROUP then
local opsgroup=cargo.opsgroup
isNotCargo=opsgroup:IsNotCargo(true)
if not isNotCargo then
isNotCargo=iscarrier(opsgroup)
end
isInZone=opsgroup:IsInZone(Zone)
isInUtero=opsgroup:IsInUtero()
self:T(self.lid..string.format("Cargo=%s: notcargo=%s, iscarrier=%s inzone=%s, inutero=%s",opsgroup:GetName(),tostring(opsgroup:IsNotCargo(true)),tostring(iscarrier(opsgroup)),tostring(isInZone),tostring(isInUtero)))
end
if isNotCargo and(isInZone or isInUtero)then
N=N+1
end
end
self:T(self.lid..string.format("Found %d units in zone %s",N,Zone:GetName()))
return N
end
function OPSTRANSPORT:_GetTransportZoneCombo(Carrier)
local vec2=Carrier:GetVec2()
local function penalty(candidate)
local p=candidate.ncarriers*10-candidate.ncargo+candidate.distance/10
return p
end
local candidates={}
for i,_transportzone in pairs(self.tzCombos)do
local tz=_transportzone
if tz.PickupZone and tz.DeployZone and tz.EmbarkZone then
local ncargo=self:_CountCargosInZone(tz.EmbarkZone,false,Carrier,tz)
if ncargo>=1 then
local dist=tz.PickupZone:Get2DDistance(vec2)
local ncarriers=0
for _,_carrier in pairs(self.carriers)do
local carrier=_carrier
if carrier and carrier:IsAlive()and carrier.cargoTZC and carrier.cargoTZC.uid==tz.uid then
ncarriers=ncarriers+1
end
end
local candidate={tzc=tz,distance=dist/1000,ncargo=ncargo,ncarriers=ncarriers}
candidate.penalty=penalty(candidate)
table.insert(candidates,candidate)
end
end
end
if#candidates>0 then
local function optTZC(candA,candB)
return candA.penalty<candB.penalty
end
table.sort(candidates,optTZC)
if self.verbose>=3 then
local text="TZC optimized"
for i,candidate in pairs(candidates)do
text=text..string.format("\n[%d] TPZ=%d, Ncarriers=%d, Ncargo=%d, Distance=%.1f km, PENALTY=%d",i,candidate.tzc.uid,candidate.ncarriers,candidate.ncargo,candidate.distance,candidate.penalty)
end
self:I(self.lid..text)
end
return candidates[1].tzc
else
self:T(self.lid..string.format("Could NOT find a pickup zone (with cargo) for carrier group %s",Carrier:GetName()))
end
return nil
end
function OPSTRANSPORT:_GetOpsGroupFromObject(Object)
local opsgroup=nil
if Object:IsInstanceOf("OPSGROUP")then
opsgroup=Object
elseif Object:IsInstanceOf("GROUP")then
opsgroup=_DATABASE:GetOpsGroup(Object)
if not opsgroup then
if Object:IsAir()then
opsgroup=FLIGHTGROUP:New(Object)
elseif Object:IsShip()then
opsgroup=NAVYGROUP:New(Object)
else
opsgroup=ARMYGROUP:New(Object)
end
end
else
self:E(self.lid.."ERROR: Object must be a GROUP or OPSGROUP object!")
return nil
end
return opsgroup
end
OPSZONE={
ClassName="OPSZONE",
verbose=0,
Nred=0,
Nblu=0,
Nnut=0,
Ncoal={},
Tred=0,
Tblu=0,
Tnut=0,
chiefs={},
Missions={},
UpdateSeconds=120,
}
OPSZONE.ZoneType={
Circular="Circular",
Polygon="Polygon",
}
OPSZONE.version="0.6.2"
function OPSZONE:New(Zone,CoalitionOwner)
local self=BASE:Inherit(self,FSM:New())
if Zone then
if type(Zone)=="string"then
local Name=Zone
Zone=ZONE:FindByName(Name)
if not Zone then
local airbase=AIRBASE:FindByName(Name)
if airbase then
Zone=ZONE_AIRBASE:New(Name,2000)
end
end
if not Zone then
self:E(string.format("ERROR: No ZONE or ZONE_AIRBASE found for name: %s",Name))
return nil
end
end
else
self:E("ERROR: First parameter Zone is nil in OPSZONE:New(Zone) call!")
return nil
end
if Zone:IsInstanceOf("ZONE_AIRBASE")then
self.airbase=Zone._.ZoneAirbase
self.airbaseName=self.airbase:GetName()
self.zoneType=OPSZONE.ZoneType.Circular
self.zoneCircular=Zone
elseif Zone:IsInstanceOf("ZONE_RADIUS")then
self.zoneType=OPSZONE.ZoneType.Circular
self.zoneCircular=Zone
elseif Zone:IsInstanceOf("ZONE_POLYGON_BASE")then
self.zoneType=OPSZONE.ZoneType.Polygon
local zone=Zone
self.zoneCircular=zone:GetZoneRadius(nil,true)
else
self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!")
return nil
end
self.lid=string.format("OPSZONE %s | ",Zone:GetName())
self.zone=Zone
self.zoneName=Zone:GetName()
self.zoneRadius=self.zoneCircular:GetRadius()
self.Missions={}
self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone})
self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone})
_DATABASE:AddOpsZone(self)
self.ownerCurrent=CoalitionOwner or coalition.side.NEUTRAL
self.ownerPrevious=CoalitionOwner or coalition.side.NEUTRAL
self.isContested=false
self.Ncoal[coalition.side.BLUE]=0
self.Ncoal[coalition.side.RED]=0
self.Ncoal[coalition.side.NEUTRAL]=0
if self.airbase then
self.ownerCurrent=self.airbase:GetCoalition()
self.ownerPrevious=self.airbase:GetCoalition()
end
self:SetObjectCategories()
self:SetUnitCategories()
self:SetDrawZone()
self:SetMarkZone(true)
self:SetCaptureTime()
self:SetCaptureNunits()
self:SetCaptureThreatlevel()
self.timerStatus=TIMER:New(OPSZONE.Status,self)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Empty")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("*","Evaluated","*")
self:AddTransition("*","Captured","Guarded")
self:AddTransition("Empty","Guarded","Guarded")
self:AddTransition("*","Empty","Empty")
self:AddTransition("*","Attacked","Attacked")
self:AddTransition("*","Defeated","Guarded")
return self
end
function OPSZONE:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
function OPSZONE:SetObjectCategories(Categories)
if Categories and type(Categories)~="table"then
Categories={Categories}
end
self.ObjectCategories=Categories or{Object.Category.UNIT,Object.Category.STATIC}
return self
end
function OPSZONE:SetUnitCategories(Categories)
if Categories and type(Categories)~="table"then
Categories={Categories}
end
self.UnitCategories=Categories or{Unit.Category.GROUND_UNIT}
return self
end
function OPSZONE:SetCaptureThreatlevel(Threatlevel)
self.threatlevelCapture=Threatlevel or 0
return self
end
function OPSZONE:SetCaptureNunits(Nunits)
Nunits=Nunits or 1
self.nunitsCapture=Nunits
return self
end
function OPSZONE:SetCaptureTime(Tcapture)
self.TminCaptured=Tcapture or 0
return self
end
function OPSZONE:SetNeutralCanCapture(CanCapture)
self.neutralCanCapture=CanCapture
return self
end
function OPSZONE:SetDrawZone(Switch)
if Switch==false then
self.drawZone=false
else
self.drawZone=true
end
return self
end
function OPSZONE: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
local Coordinate=self:GetCoordinate()
self.markerText=self:_GetMarkerText()
self.marker=self.marker or MARKER:New(Coordinate,self.markerText)
if ReadOnly==false then
self.marker.readonly=false
else
self.marker.readonly=true
end
self.marker:ToAll()
else
if self.marker then
self.marker:Remove()
end
self.marker=nil
self.markZone=false
end
return self
end
function OPSZONE:GetOwner()
return self.ownerCurrent
end
function OPSZONE:GetOwnerName()
return UTILS.GetCoalitionName(self.ownerCurrent)
end
function OPSZONE:GetCoordinate()
local coordinate=self.zone:GetCoordinate()
return coordinate
end
function OPSZONE:GetScannedUnitSet()
return self.ScanUnitSet
end
function OPSZONE:GetScannedGroupSet()
return self.ScanGroupSet
end
function OPSZONE:GetRandomCoordinate(inner,outer,surfacetypes)
local zone=self:GetZone()
local coord=zone:GetRandomCoordinate(inner,outer,surfacetypes)
return coord
end
function OPSZONE:GetName()
return self.zoneName
end
function OPSZONE:GetZone()
return self.zone
end
function OPSZONE:GetPreviousOwner()
return self.ownerPrevious
end
function OPSZONE:GetAttackDuration()
if self:IsAttacked()and self.Tattacked then
local dT=timer.getAbsTime()-self.Tattacked
return dT
end
return nil
end
function OPSZONE:FindByName(ZoneName)
local Found=_DATABASE:FindOpsZone(ZoneName)
return Found
end
function OPSZONE:IsRed()
local is=self.ownerCurrent==coalition.side.RED
return is
end
function OPSZONE:IsBlue()
local is=self.ownerCurrent==coalition.side.BLUE
return is
end
function OPSZONE:IsNeutral()
local is=self.ownerCurrent==coalition.side.NEUTRAL
return is
end
function OPSZONE:IsCoalition(Coalition)
local is=self.ownerCurrent==Coalition
return is
end
function OPSZONE:IsStarted()
local is=not self:IsStopped()
return is
end
function OPSZONE:IsStopped()
local is=self:is("Stopped")
return is
end
function OPSZONE:IsGuarded()
local is=self:is("Guarded")
return is
end
function OPSZONE:IsEmpty()
local is=self:is("Empty")
return is
end
function OPSZONE:IsAttacked()
local is=self:is("Attacked")
return is
end
function OPSZONE:IsContested()
return self.isContested
end
function OPSZONE:IsStopped()
local is=self:is("Stopped")
return is
end
function OPSZONE:onafterStart(From,Event,To)
self:I(self.lid..string.format("Starting OPSZONE v%s",OPSZONE.version))
self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status,self)
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"))
self.timerStatus:Stop()
self.zone:UndrawZone()
if self.markZone then
self.marker:Remove()
end
self:UnHandleEvent(EVENTS.BaseCaptured)
self.CallScheduler:Clear()
if self.Scheduler then
self.Scheduler:Clear()
end
end
function OPSZONE:Status()
local fsmstate=self:GetState()
local contested=tostring(self:IsContested())
if self.verbose>=1 then
local text=string.format("State %s: Owner %d (previous %d), contested=%s, Nunits: red=%d, blue=%d, neutral=%d",fsmstate,self.ownerCurrent,self.ownerPrevious,contested,self.Nred,self.Nblu,self.Nnut)
self:I(self.lid..text)
end
self:Scan()
self:EvaluateZone()
self:_UpdateMarker()
if self.zone.DrawID and not self.drawZone then
self.zone:UndrawZone()
end
end
function OPSZONE:onbeforeCaptured(From,Event,To,NewOwnerCoalition)
if self.ownerCurrent==NewOwnerCoalition then
self:T(self.lid.."")
end
return true
end
function OPSZONE:onafterCaptured(From,Event,To,NewOwnerCoalition)
self:T(self.lid..string.format("Zone captured by coalition=%d",NewOwnerCoalition))
self.ownerPrevious=self.ownerCurrent
self.ownerCurrent=NewOwnerCoalition
if self.drawZone then
self.zone:UndrawZone()
local color=self:_GetZoneColor()
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
if chief.coalition==self.ownerCurrent then
chief:ZoneCaptured(self)
else
chief:ZoneLost(self)
end
end
end
function OPSZONE:onafterEmpty(From,Event,To)
self:T(self.lid..string.format("Zone is empty EVENT"))
end
function OPSZONE:onafterAttacked(From,Event,To,AttackerCoalition)
self:T(self.lid..string.format("Zone is being attacked by coalition=%s!",tostring(AttackerCoalition)))
end
function OPSZONE:onafterDefeated(From,Event,To,DefeatedCoalition)
self:T(self.lid..string.format("Defeated attack on zone by coalition=%d",DefeatedCoalition))
self.Tattacked=nil
end
function OPSZONE:onenterGuarded(From,Event,To)
if From~=To then
self:T(self.lid..string.format("Zone is guarded"))
self.Tattacked=nil
if self.drawZone then
self.zone:UndrawZone()
local color=self:_GetZoneColor()
local coalition=nil
if self.drawZoneForCoalition then
coalition=self.ownerCurrent
end
self.zone:DrawZone(coalition,color,1.0,color,0.5)
end
end
end
function OPSZONE:onenterAttacked(From,Event,To,AttackerCoalition)
if From~="Attacked"then
self:T(self.lid..string.format("Zone is Attacked"))
self.Tattacked=timer.getAbsTime()
if AttackerCoalition then
for _,_chief in pairs(self.chiefs)do
local chief=_chief
if chief.coalition~=AttackerCoalition then
chief:ZoneAttacked(self)
end
end
end
if self.drawZone then
self.zone:UndrawZone()
local color={1,204/255,204/255}
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
end
function OPSZONE:onenterEmpty(From,Event,To)
if From~=To then
self:T(self.lid..string.format("Zone is empty now"))
for _,_chief in pairs(self.chiefs)do
local chief=_chief
chief:ZoneEmpty(self)
end
if self.drawZone then
self.zone:UndrawZone()
local color=self:_GetZoneColor()
local coalition=nil
if self.drawZoneForCoalition then
coalition=self.ownerCurrent
end
self.zone:DrawZone(coalition,color,1.0,color,0.2)
end
end
end
function OPSZONE:Scan()
if self.verbose>=3 then
local text=string.format("Scanning zone %s R=%.1f m",self.zoneName,self.zoneRadius)
self:I(self.lid..text)
end
local SphereSearch={id=world.VolumeType.SPHERE,params={point=self.zone:GetVec3(),radius=self.zoneRadius}}
local Nred=0
local Nblu=0
local Nnut=0
local Tred=0
local Tblu=0
local Tnut=0
self.ScanGroupSet:Clear(false)
self.ScanUnitSet:Clear(false)
local function EvaluateZone(_ZoneObject)
local ZoneObject=_ZoneObject
if ZoneObject then
local ObjectCategory=Object.getCategory(ZoneObject)
if ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive()then
local DCSUnit=ZoneObject
local function Included()
if not self.UnitCategories then
return true
else
local CategoryDCSUnit=ZoneObject:getDesc().category
for _,UnitCategory in pairs(self.UnitCategories)do
if UnitCategory==CategoryDCSUnit then
return true
end
end
end
return false
end
if Included()then
local Coalition=DCSUnit:getCoalition()
local tl=0
local unit=UNIT:Find(DCSUnit)
if unit then
local inzone=true
if self.zoneType==OPSZONE.ZoneType.Polygon then
inzone=unit:IsInZone(self.zone)
end
if inzone then
tl=unit:GetThreatLevel()
self.ScanUnitSet:AddUnit(unit)
local group=unit:GetGroup()
if group then
self.ScanGroupSet:AddGroup(group,true)
end
if Coalition==coalition.side.RED then
Nred=Nred+1
Tred=Tred+tl
elseif Coalition==coalition.side.BLUE then
Nblu=Nblu+1
Tblu=Tblu+tl
elseif Coalition==coalition.side.NEUTRAL then
Nnut=Nnut+1
Tnut=Tnut+tl
end
if self.verbose>=4 then
self:I(self.lid..string.format("Found unit %s (coalition=%d)",DCSUnit:getName(),Coalition))
end
end
end
end
elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist()then
local DCSStatic=ZoneObject
local Coalition=DCSStatic:getCoalition()
local inzone=true
if self.zoneType==OPSZONE.ZoneType.Polygon then
local Vec3=DCSStatic:getPoint()
inzone=self.zone:IsVec3InZone(Vec3)
end
if inzone then
if Coalition==coalition.side.RED then
Nred=Nred+1
elseif Coalition==coalition.side.BLUE then
Nblu=Nblu+1
elseif Coalition==coalition.side.NEUTRAL then
Nnut=Nnut+1
end
if self.verbose>=4 then
self:I(self.lid..string.format("Found static %s (coalition=%d)",DCSStatic:getName(),Coalition))
end
end
elseif ObjectCategory==Object.Category.SCENERY then
local SceneryType=ZoneObject:getTypeName()
local SceneryName=ZoneObject:getName()
self:T2(self.lid..string.format("Found scenery type=%s, name=%s",SceneryType,SceneryName))
end
end
return true
end
world.searchObjects(self.ObjectCategories,SphereSearch,EvaluateZone)
if self.verbose>=3 then
local text=string.format("Scan result Nred=%d, Nblue=%d, Nneutral=%d",Nred,Nblu,Nnut)
if self.verbose>=4 then
for _,_unit in pairs(self.ScanUnitSet:GetSet())do
local unit=_unit
text=text..string.format("\nUnit %s coalition=%s",unit:GetName(),unit:GetCoalitionName())
end
for _,_group in pairs(self.ScanGroupSet:GetSet())do
local group=_group
text=text..string.format("\nGroup %s coalition=%s",group:GetName(),group:GetCoalitionName())
end
end
self:I(self.lid..text)
end
self.Nred=Nred
self.Nblu=Nblu
self.Nnut=Nnut
self.Ncoal[coalition.side.BLUE]=Nblu
self.Ncoal[coalition.side.RED]=Nred
self.Ncoal[coalition.side.NEUTRAL]=Nnut
self.Tblu=Tblu
self.Tred=Tred
self.Tnut=Tnut
return self
end
function OPSZONE:EvaluateZone()
local Nred=self.Nred
local Nblu=self.Nblu
local Nnut=self.Nnut
local Tnow=timer.getAbsTime()
local function captured(coal)
if not self.airbase then
if not self.Tcaptured then
self.Tcaptured=Tnow
end
if Tnow-self.Tcaptured>=self.TminCaptured then
self:Captured(coal)
self.Tcaptured=nil
end
end
end
if self:IsRed()then
if Nred==0 then
if Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then
captured(coalition.side.BLUE)
elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then
captured(coalition.side.NEUTRAL)
end
else
if Nblu>0 then
if not self:IsAttacked()and self.Tblu>=self.threatlevelCapture then
self:Attacked(coalition.side.BLUE)
end
elseif Nblu==0 then
if self:IsAttacked()and self:IsContested()then
self:Defeated(coalition.side.BLUE)
elseif self:IsEmpty()then
self:Guarded()
end
end
end
if Nblu==0 then
self.isContested=false
else
self.isContested=true
end
elseif self:IsBlue()then
if Nblu==0 then
if Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then
captured(coalition.side.RED)
elseif Nnut>=self.nunitsCapture and self.Tnut>=self.threatlevelCapture and self.neutralCanCapture then
captured(coalition.side.NEUTRAL)
end
else
if Nred>0 then
if not self:IsAttacked()and self.Tred>=self.threatlevelCapture then
self:Attacked(coalition.side.RED)
end
elseif Nred==0 then
if self:IsAttacked()and self:IsContested()then
self:Defeated(coalition.side.RED)
elseif self:IsEmpty()then
self:Guarded()
end
end
end
if Nred==0 then
self.isContested=false
else
self.isContested=true
end
elseif self:IsNeutral()then
if Nred>0 and Nblu>0 then
self:T(self.lid.."FF neutrals left neutral zone and red and blue are present! What to do?")
if not self:IsAttacked()then
self:Attacked()
end
self.isContested=true
elseif Nred>=self.nunitsCapture and self.Tred>=self.threatlevelCapture then
captured(coalition.side.RED)
elseif Nblu>=self.nunitsCapture and self.Tblu>=self.threatlevelCapture then
captured(coalition.side.BLUE)
end
else
self:E(self.lid.."ERROR: Unknown coaliton!")
end
if Nblu==0 and Nred==0 and Nnut==0 and(not self:IsEmpty())then
self:Empty()
end
if self.airbase then
local airbasecoalition=self.airbase:GetCoalition()
if airbasecoalition~=self.ownerCurrent then
self:T(self.lid..string.format("Captured airbase %s: Coaltion %d-->%d",self.airbaseName,self.ownerCurrent,airbasecoalition))
self:Captured(airbasecoalition)
end
end
self:Evaluated()
end
function OPSZONE:OnEventHit(EventData)
if self.HitsOn then
local UnitHit=EventData.TgtUnit
if UnitHit and UnitHit:IsInZone(self)and UnitHit:GetCoalition()==self.ownerCurrent then
self.HitTimeLast=timer.getTime()
if not self:IsAttacked()then
self:T3(self.lid.."Hit ==> Attack")
self:Attacked()
end
end
end
end
function OPSZONE:OnEventBaseCaptured(EventData)
if EventData and EventData.Place and self.airbase and self.airbaseName then
local airbase=EventData.Place
if EventData.PlaceName==self.airbaseName then
local CoalitionNew=airbase:GetCoalition()
self:I(self.lid..string.format("EVENT BASE CAPTURED: New coalition of airbase %s: %d [previous=%d]",self.airbaseName,CoalitionNew,self.ownerCurrent))
if CoalitionNew~=self.ownerCurrent then
self:Captured(CoalitionNew)
end
end
end
end
function OPSZONE:_GetZoneColor()
local color={0,0,0}
if self.ownerCurrent==coalition.side.NEUTRAL then
color=self.ZoneOwnerNeutral or{1,1,1}
elseif self.ownerCurrent==coalition.side.BLUE then
color=self.ZoneOwnerBlue or{0,0,1}
elseif self.ownerCurrent==coalition.side.RED then
color=self.ZoneOwnerRed or{1,0,0}
else
end
return color
end
function OPSZONE:SetZoneColor(Neutral,Blue,Red)
self.ZoneOwnerNeutral=Neutral or{1,1,1}
self.ZoneOwnerBlue=Blue or{0,0,1}
self.ZoneOwnerRed=Red or{1,0,0}
return self
end
function OPSZONE:_UpdateMarker()
if self.markZone then
local text=self:_GetMarkerText()
if text~=self.markerText then
self.markerText=text
self.marker:UpdateText(self.markerText)
end
end
end
function OPSZONE:_GetMarkerText()
local owner=UTILS.GetCoalitionName(self.ownerCurrent)
local prevowner=UTILS.GetCoalitionName(self.ownerPrevious)
local text=string.format("%s [N=%d, TL=%d T=%d]:\nOwner=%s [%s]\nState=%s [Contested=%s]\nBlue=%d [TL=%d]\nRed=%d [TL=%d]\nNeutral=%d [TL=%d]",
self.zoneName,self.nunitsCapture or 0,self.threatlevelCapture or 0,self.TminCaptured or 0,
owner,prevowner,self:GetState(),tostring(self:IsContested()),
self.Nblu,self.Tblu,self.Nred,self.Tred,self.Nnut,self.Tnut)
return text
end
function OPSZONE:_AddChief(Chief)
table.insert(self.chiefs,Chief)
end
function OPSZONE:_AddMission(Coalition,Type,Auftrag)
local entry={}
entry.Coalition=Coalition or coalition.side.NEUTRAL
entry.Type=Type or""
entry.Mission=Auftrag or nil
table.insert(self.Missions,entry)
return self
end
function OPSZONE:_GetMissions()
return self.Missions
end
function OPSZONE:_FindMissions(Coalition,Type)
local foundmissions={}
local found=false
for _,_entry in pairs(self.Missions)do
local entry=_entry
if entry.Coalition==Coalition and entry.Type==Type and entry.Mission and entry.Mission:IsNotOver()then
table.insert(foundmissions,entry.Mission)
found=true
end
end
return found,foundmissions
end
function OPSZONE:_CleanMissionTable()
local missions={}
for _,_entry in pairs(self.Missions)do
local entry=_entry
if entry.Mission and entry.Mission:IsNotOver()then
table.insert(missions,entry)
end
end
self.Missions=missions
return self
end
PLATOON={
ClassName="PLATOON",
verbose=0,
weaponData={},
}
PLATOON.version="0.1.0"
function PLATOON:New(TemplateGroupName,Ngroups,PlatoonName)
local self=BASE:Inherit(self,COHORT:New(TemplateGroupName,Ngroups,PlatoonName))
self:AddMissionCapability(AUFTRAG.Type.NOTHING,50)
self.isGround=true
self.ammo=self:_CheckAmmo()
return self
end
function PLATOON:SetBrigade(Brigade)
self.legion=Brigade
return self
end
function PLATOON:GetBrigade()
return self.legion
end
function PLATOON:onafterStatus(From,Event,To)
if self.verbose>=1 then
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A"
local skill=self.skill and tostring(self.skill)or"N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0;local NassetsP=0;local NassetsQ=0
if self.legion then
NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self)
end
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate,self.aircrafttype,callsign,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ)
self:T(self.lid..text)
if self.verbose>=3 and self.weaponData then
local text="Weapon Data:"
for bit,_weapondata in pairs(self.weaponData)do
local weapondata=_weapondata
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km",bit,weapondata.RangeMin/1000,weapondata.RangeMax/1000)
end
self:I(self.lid..text)
end
self:_CheckAssetStatus()
end
if not self:IsStopped()then
self:__Status(-60)
end
end
do
_PlayerTaskNr=0
PLAYERTASK={
ClassName="PLAYERTASK",
verbose=false,
lid=nil,
PlayerTaskNr=nil,
Type=nil,
TTSType=nil,
Target=nil,
Clients=nil,
Repeat=false,
repeats=0,
RepeatNo=1,
TargetMarker=nil,
SmokeColor=nil,
FlareColor=nil,
conditionSuccess={},
conditionFailure={},
TaskController=nil,
timestamp=0,
lastsmoketime=0,
Freetext=nil,
FreetextTTS=nil,
TaskSubType=nil,
NextTaskSuccess={},
NextTaskFailure={},
FinalState="none",
PreviousCount=0,
}
PLAYERTASK.version="0.1.26"
function PLAYERTASK:New(Type,Target,Repeat,Times,TTSType)
local self=BASE:Inherit(self,FSM:New())
self.Type=Type
self.Repeat=false
self.repeats=0
self.RepeatNo=1
self.Clients=FIFO:New()
self.TargetMarker=nil
self.SmokeColor=SMOKECOLOR.Red
self.conditionSuccess={}
self.conditionFailure={}
self.TaskController=nil
self.timestamp=timer.getAbsTime()
self.TTSType=TTSType or"close air support"
self.lastsmoketime=0
if type(Repeat)=="boolean"and Repeat==true and type(Times)=="number"and Times>1 then
self.Repeat=true
self.RepeatNo=Times or 1
end
_PlayerTaskNr=_PlayerTaskNr+1
self.PlayerTaskNr=_PlayerTaskNr
self.lid=string.format("PlayerTask #%d %s | ",self.PlayerTaskNr,tostring(self.Type))
if Target and Target.ClassName and Target.ClassName=="TARGET"then
self.Target=Target
elseif Target and Target.ClassName then
self.Target=TARGET:New(Target)
else
self:E(self.lid.."*** NO VALID TARGET!")
return self
end
self.PreviousCount=self.Target:CountTargets()
self:T(self.lid.."Created.")
self:SetStartState("Planned")
self:AddTransition("*","Planned","Planned")
self:AddTransition("*","Requested","Requested")
self:AddTransition("*","ClientAdded","*")
self:AddTransition("*","ClientRemoved","*")
self:AddTransition("*","Executing","Executing")
self:AddTransition("*","Progress","*")
self:AddTransition("*","Done","Done")
self:AddTransition("*","Cancel","Done")
self:AddTransition("*","Success","Done")
self:AddTransition("*","ClientAborted","*")
self:AddTransition("*","Failed","Failed")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
self:__Status(-5)
return self
end
function PLAYERTASK: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:_SetController(Controller)
self:T(self.lid.."_SetController")
self.TaskController=Controller
return self
end
function PLAYERTASK:SetCoalition(Coalition)
self:T(self.lid.."SetCoalition")
self.coalition=Coalition or coalition.side.BLUE
return self
end
function PLAYERTASK:GetCoalition()
self:T(self.lid.."GetCoalition")
return self.coalition
end
function PLAYERTASK:GetTarget()
self:T(self.lid.."GetTarget")
return self.Target
end
function PLAYERTASK:AddFreetext(Text)
self:T(self.lid.."AddFreetext")
self.Freetext=Text
return self
end
function PLAYERTASK:HasFreetext()
self:T(self.lid.."HasFreetext")
return self.Freetext~=nil and true or false
end
function PLAYERTASK:HasFreetextTTS()
self:T(self.lid.."HasFreetextTTS")
return self.FreetextTTS~=nil and true or false
end
function PLAYERTASK:SetSubType(Type)
self:T(self.lid.."AddSubType")
self.TaskSubType=Type
return self
end
function PLAYERTASK:GetSubType()
self:T(self.lid.."GetSubType")
return self.TaskSubType
end
function PLAYERTASK:GetFreetext()
self:T(self.lid.."GetFreetext")
return self.Freetext or self.FreetextTTS or"No Details"
end
function PLAYERTASK:AddFreetextTTS(TextTTS)
self:T(self.lid.."AddFreetextTTS")
self.FreetextTTS=TextTTS
return self
end
function PLAYERTASK:GetFreetextTTS()
self:T(self.lid.."GetFreetextTTS")
return self.FreetextTTS or self.Freetext or"No Details"
end
function PLAYERTASK:SetMenuName(Text)
self:T(self.lid.."SetMenuName")
self.Target.name=Text
return self
end
function PLAYERTASK: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)
local task=self
task:AddConditionSuccess(
function(target)
if target:IsInstanceOf("OPSZONE")then
return task:_CheckCaptureOpsZoneSuccess(target,CaptureSquadGroupNamePrefix,Coalition,true)
elseif target:IsInstanceOf("SET_OPSZONE")then
local successes=0
local isClientInZone=false
target:ForEachZone(function(opszone)
if task:_CheckCaptureOpsZoneSuccess(opszone,CaptureSquadGroupNamePrefix,Coalition)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 distance<minD and isLos then
return true
end
end
return false
end,task:GetTarget())
return self
end
function PLAYERTASK:AddTimeLimitFailureCondition(TimeLimit)
local task=self
TimeLimit=TimeLimit or 0
task.StartTime=-1
task:AddConditionFailure(
function()
if task.StartTime==-1 then
task.StartTime=timer.getTime()
end
return TimeLimit>0 and timer.getTime()-task.StartTime>TimeLimit
end)
return self
end
function PLAYERTASK:AddNextTaskAfterSuccess(Task)
self:T(self.lid.."AddNextTaskAfterSuccess")
table.insert(self.NextTaskSuccess,Task)
return self
end
function PLAYERTASK:AddNextTaskAfterFailure(Task)
self:T(self.lid.."AddNextTaskAfterFailure")
table.insert(self.NextTaskFailure,Task)
return self
end
function PLAYERTASK:IsDone()
self:T(self.lid.."IsDone?")
local IsDone=false
local state=self:GetState()
if state=="Done"or state=="Stopped"then
IsDone=true
end
return IsDone
end
function PLAYERTASK: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
return hasclients
end
function PLAYERTASK:GetClients()
self:T(self.lid.."GetClients")
local clientlist=self.Clients:GetIDStackSorted()or{}
local count=self.Clients:Count()
return clientlist,count
end
function PLAYERTASK:GetClientObjects()
self:T(self.lid.."GetClientObjects")
local clientlist=self.Clients:GetDataTable()or{}
local count=self.Clients:Count()
return clientlist,count
end
function PLAYERTASK:CountClients()
self:T(self.lid.."CountClients")
return self.Clients:Count()
end
function PLAYERTASK:HasPlayerName(Name)
self:T(self.lid.."HasPlayerName?")
return self.Clients:HasUniqueID(Name)
end
function PLAYERTASK:AddClient(Client)
self:T(self.lid.."AddClient")
local name=Client:GetPlayerName()
if not self.Clients:HasUniqueID(name)then
self.Clients:Push(Client,name)
self:__ClientAdded(-2,Client)
end
if self.TaskController and self.TaskController.Scoring then
self.TaskController.Scoring:_AddPlayerFromUnit(Client)
end
return self
end
function PLAYERTASK:RemoveClient(Client,Name)
self:T(self.lid.."RemoveClient")
local name=Name or Client:GetPlayerName()
if self.Clients:HasUniqueID(name)then
self.Clients:PullByID(name)
if self.verbose then
self.Clients:Flush()
end
self:__ClientRemoved(-2,Client)
if self.Clients:Count()==0 then
self:__Failed(-1)
end
end
return self
end
function PLAYERTASK:ClientAbort(Client)
self:T(self.lid.."ClientAbort")
if Client and Client:IsAlive()then
self:RemoveClient(Client)
self:__ClientAborted(-1,Client)
return self
else
if self.Clients:Count()==0 then
self:__Failed(-1)
end
end
return self
end
function PLAYERTASK:MarkTargetOnF10Map(Text,Coalition,ReadOnly)
self:T(self.lid.."MarkTargetOnF10Map")
if self.Target then
local coordinate=self.Target:GetCoordinate()
if coordinate then
if self.TargetMarker then
self.TargetMarker:Remove()
end
local text=Text or("Target of "..self.lid)
self.TargetMarker=MARKER:New(coordinate,text)
if ReadOnly then
self.TargetMarker:ReadOnly()
end
if Coalition then
self.TargetMarker:ToCoalition(Coalition)
else
self.TargetMarker:ToAll()
end
end
end
return self
end
function PLAYERTASK:SmokeTarget(Color)
self:T(self.lid.."SmokeTarget")
local color=Color or SMOKECOLOR.Red
if not self.lastsmoketime then self.lastsmoketime=0 end
local TDiff=timer.getAbsTime()-self.lastsmoketime
if self.Target and TDiff>299 then
local coordinate=self.Target:GetAverageCoordinate()
if coordinate then
coordinate:Smoke(color)
self.lastsmoketime=timer.getAbsTime()
end
end
return self
end
function PLAYERTASK:FlareTarget(Color)
self:T(self.lid.."SmokeTarget")
local color=Color or FLARECOLOR.Red
if self.Target then
local coordinate=self.Target:GetAverageCoordinate()
if coordinate then
coordinate:Flare(color,0)
end
end
return self
end
function PLAYERTASK:IlluminateTarget(Power,Height)
self:T(self.lid.."IlluminateTarget")
local Power=Power or 1000
local Height=Height or 150
if self.Target then
local coordinate=self.Target:GetAverageCoordinate()
if coordinate then
local bcoord=COORDINATE:NewFromVec2(coordinate:GetVec2(),Height)
bcoord:IlluminationBomb(Power)
end
end
return self
end
function PLAYERTASK:AddConditionSuccess(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionSuccess,condition)
return self
end
function PLAYERTASK:AddConditionFailure(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionFailure,condition)
return self
end
function PLAYERTASK:_EvalConditionsAny(Conditions)
for _,_condition in pairs(Conditions or{})do
local condition=_condition
local istrue=condition.func(unpack(condition.arg))
if istrue then
return true
end
end
return false
end
function PLAYERTASK:onafterStatus(From,Event,To)
self:T({From,Event,To})
self:T(self.lid.."onafterStatus")
local status=self:GetState()
if status=="Stopped"then return self end
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
targetdead=true
self:__Success(-2)
status="Success"
return self
end
end
local clientsalive=false
if status=="Executing"then
local ClientTable=self.Clients:GetDataTable()
for _,_client in pairs(ClientTable)do
local client=_client
if client:IsAlive()then
clientsalive=true
end
end
if status=="Executing"and(not clientsalive)and(not targetdead)then
self:__Failed(-2)
status="Failed"
end
end
if status~="Done"and status~="Stopped"then
local successCondition=self:_EvalConditionsAny(self.conditionSuccess)
local failureCondition=self:_EvalConditionsAny(self.conditionFailure)
if failureCondition and status~="Failed"then
self:__Failed(-2)
status="Failed"
elseif successCondition then
self:__Success(-2)
status="Success"
end
if status~="Failed"and status~="Success"then
local targetcount=self.Target:CountTargets()
if targetcount<self.PreviousCount then
self:__Progress(-2,targetcount)
self.PreviousCount=targetcount
end
end
if self.verbose then
self:I(self.lid.."Target dead: "..tostring(targetdead).." | Clients alive: "..tostring(clientsalive))
end
self:__Status(-20)
elseif status~="Stopped"then
self:__Stop(-1)
end
return self
end
function PLAYERTASK:onafterProgress(From,Event,To,TargetCount)
self:T({From,Event,To})
if self.TaskController then
if self.TaskController.Scoring then
local clients,count=self:GetClientObjects()
if count>0 then
for _,_client in pairs(clients)do
self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,10)
end
end
end
self.TaskController:__TaskProgress(-1,self,TargetCount)
end
return self
end
function PLAYERTASK:onafterPlanned(From,Event,To)
self:T({From,Event,To})
self.timestamp=timer.getAbsTime()
return self
end
function PLAYERTASK:onafterRequested(From,Event,To)
self:T({From,Event,To})
self.timestamp=timer.getAbsTime()
return self
end
function PLAYERTASK:onafterExecuting(From,Event,To)
self:T({From,Event,To})
self.timestamp=timer.getAbsTime()
return self
end
function PLAYERTASK:onafterStop(From,Event,To)
self:T({From,Event,To})
self.timestamp=timer.getAbsTime()
return self
end
function PLAYERTASK:onafterClientAdded(From,Event,To,Client)
self:T({From,Event,To})
if Client and self.verbose then
local text=string.format("Player %s joined task %03d!",Client:GetPlayerName()or"Generic",self.PlayerTaskNr)
self:T(self.lid..text)
end
self.timestamp=timer.getAbsTime()
return self
end
function PLAYERTASK:onafterDone(From,Event,To)
self:T({From,Event,To})
if self.TaskController then
self.TaskController:__TaskDone(-1,self)
end
self.timestamp=timer.getAbsTime()
self:__Stop(-1)
return self
end
function PLAYERTASK:onafterCancel(From,Event,To)
self:T({From,Event,To})
if self.TaskController then
self.TaskController:__TaskCancelled(-1,self)
end
self.timestamp=timer.getAbsTime()
self.FinalState="Cancelled"
self:__Done(-1)
return self
end
function PLAYERTASK:onafterSuccess(From,Event,To)
self:T({From,Event,To})
if self.TaskController then
self.TaskController:__TaskSuccess(-1,self)
end
if self.TargetMarker then
self.TargetMarker:Remove()
end
if self.TaskController and self.TaskController.Scoring then
local clients,count=self:GetClientObjects()
if count>0 then
for _,_client in pairs(clients)do
local auftrag=self:GetSubType()
self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,self.TaskController.Scores[self.Type])
end
end
end
self.timestamp=timer.getAbsTime()
self.FinalState="Success"
self:__Done(-1)
return self
end
function PLAYERTASK:onafterFailed(From,Event,To)
self:T({From,Event,To})
self.repeats=self.repeats+1
if self.Repeat and(self.repeats<=self.RepeatNo)then
if self.TaskController then
self.TaskController:__TaskRepeatOnFailed(-1,self)
end
self:__Planned(-1)
return self
else
if self.TargetMarker then
self.TargetMarker:Remove()
end
self.FinalState="Failed"
self:__Done(-1)
end
if self.TaskController.Scoring then
local clients,count=self:GetClientObjects()
if count>0 then
for _,_client in pairs(clients)do
local auftrag=self:GetSubType()
self.TaskController.Scoring:AddGoalScore(_client,self.Type,nil,-self.TaskController.Scores[self.Type])
end
end
end
self.timestamp=timer.getAbsTime()
return self
end
end
do
PLAYERTASKCONTROLLER={
ClassName="PLAYERTASKCONTROLLER",
verbose=false,
lid=nil,
TargetQueue=nil,
ClientSet=nil,
UseGroupNames=true,
PlayerMenu={},
usecluster=false,
MenuName=nil,
ClusterRadius=0.5,
NoScreenOutput=false,
TargetRadius=500,
UseWhiteList=false,
WhiteList={},
gettext=nil,
locale="en",
precisionbombing=false,
taskinfomenu=false,
activehasinfomenu=false,
MarkerReadOnly=false,
customcallsigns={},
ShortCallsign=true,
Keepnumber=false,
CallsignTranslations=nil,
PlayerFlashMenu={},
PlayerJoinMenu={},
PlayerInfoMenu={},
PlayerMenuTag={},
noflaresmokemenu=false,
illumenu=false,
TransmitOnlyWithPlayers=true,
buddylasing=false,
PlayerRecce=nil,
Coalition=nil,
MenuParent=nil,
ShowMagnetic=true,
InfoHasLLDDM=false,
InfoHasCoordinate=false,
UseTypeNames=false,
Scoring=nil,
MenuNoTask=nil,
InformationMenu=false,
TaskInfoDuration=30,
}
PLAYERTASKCONTROLLER.Type={
A2A="Air-To-Air",
A2G="Air-To-Ground",
A2S="Air-To-Sea",
A2GS="Air-To-Ground-Sea",
}
AUFTRAG.Type.PRECISIONBOMBING="Precision Bombing"
AUFTRAG.Type.CTLD="Combat Transport"
AUFTRAG.Type.CSAR="Combat Rescue"
AUFTRAG.Type.CONQUER="Conquer"
PLAYERTASKCONTROLLER.Scores={
[AUFTRAG.Type.PRECISIONBOMBING]=100,
[AUFTRAG.Type.CTLD]=100,
[AUFTRAG.Type.CSAR]=100,
[AUFTRAG.Type.INTERCEPT]=100,
[AUFTRAG.Type.ANTISHIP]=100,
[AUFTRAG.Type.CAS]=100,
[AUFTRAG.Type.BAI]=100,
[AUFTRAG.Type.SEAD]=100,
[AUFTRAG.Type.BOMBING]=100,
[AUFTRAG.Type.BOMBRUNWAY]=100,
[AUFTRAG.Type.CONQUER]=100,
[AUFTRAG.Type.RECON]=100,
[AUFTRAG.Type.ESCORT]=100,
[AUFTRAG.Type.CAP]=100,
[AUFTRAG.Type.CAPTUREZONE]=100,
}
PLAYERTASKCONTROLLER.SeadAttributes={
SAM=GROUP.Attribute.GROUND_SAM,
AAA=GROUP.Attribute.GROUND_AAA,
EWR=GROUP.Attribute.GROUND_EWR,
}
PLAYERTASKCONTROLLER.Messages={
EN={
TASKABORT="Task aborted!",
NOACTIVETASK="No active task!",
FREQUENCIES="frequencies ",
FREQUENCY="frequency %.3f",
BROADCAST="%s, %s, switch to %s for task assignment!",
CASTTS="close air support",
SEADTTS="suppress air defense",
BOMBTTS="bombing",
PRECBOMBTTS="precision bombing",
BAITTS="battle field air interdiction",
ANTISHIPTTS="anti-ship",
INTERCEPTTS="intercept",
BOMBRUNWAYTTS="bomb runway",
HAVEACTIVETASK="You already have one active task! Complete it first!",
PILOTJOINEDTASK="%s, %s. You have been assigned %s task %03d",
TASKNAME="%s Task ID %03d",
TASKNAMETTS="%s Task ID %03d",
THREATHIGH="high",
THREATMEDIUM="medium",
THREATLOW="low",
THREATTEXT="%s\nThreat: %s\nTargets left: %d\nCoord: %s",
ELEVATION="\nTarget Elevation: %s %s",
METER="meter",
FEET="feet",
THREATTEXTTTS="%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.",
MARKTASK="%s, %s, copy, task %03d location marked on map!",
SMOKETASK="%s, %s, copy, task %03d location smoked!",
FLARETASK="%s, %s, copy, task %03d location illuminated!",
ABORTTASK="All stations, %s, %s has aborted %s task %03d!",
UNKNOWN="Unknown",
MENUTASKING=" Tasking ",
MENUACTIVE="Active Task",
MENUINFO="Info",
MENUMARK="Mark on map",
MENUSMOKE="Smoke",
MENUFLARE="Flare",
MENUILLU="Illuminate",
MENUABORT="Abort",
MENUJOIN="Join Task",
MENUTASKINFO="Task Info",
MENUTASKNO="TaskNo",
MENUNOTASKS="Currently no tasks available.",
TASKCANCELLED="Task #%03d %s is cancelled!",
TASKCANCELLEDTTS="%s, task %03d %s is cancelled!",
TASKSUCCESS="Task #%03d %s completed successfully!",
TASKSUCCESSTTS="%s, task %03d %s completed successfully!",
TASKFAILED="Task #%03d %s was a failure!",
TASKFAILEDTTS="%s, task %03d %s was a failure!",
TASKFAILEDREPLAN="Task #%03d %s available for reassignment!",
TASKFAILEDREPLANTTS="%s, task %03d %s vailable for reassignment!",
TASKADDED="%s has a new %s task available!",
PILOTS="\nPilot(s): ",
PILOTSTTS=". Pilot(s): ",
YES="Yes",
NO="No",
NONE="None",
POINTEROVERTARGET="%s, %s, pointer in reach for task %03d, lasing!",
POINTERTARGETREPORT="\nPointer in reach: %s\nLasing: %s",
RECCETARGETREPORT="\nRecce %s in reach: %s\nLasing: %s",
POINTERTARGETLASINGTTS=". Pointer in reach and lasing.",
TARGET="Target",
FLASHON="%s - Flashing directions is now ON!",
FLASHOFF="%s - Flashing directions is now OFF!",
FLASHMENU="Flash Directions Switch",
BRIEFING="Briefing",
TARGETLOCATION="Target location",
COORDINATE="Coordinate",
INFANTRY="Infantry",
TECHNICAL="Technical",
ARTILLERY="Artillery",
TANKS="Tanks",
AIRDEFENSE="Airdefense",
SAM="SAM",
GROUP="Group",
UNARMEDSHIP="Merchant",
LIGHTARMEDSHIP="Light Boat",
CORVETTE="Corvette",
FRIGATE="Frigate",
CRUISER="Cruiser",
DESTROYER="Destroyer",
CARRIER="Aircraft Carrier",
RADIOS="Radios",
},
DE={
TASKABORT="Auftrag abgebrochen!",
NOACTIVETASK="Kein aktiver Auftrag!",
FREQUENCIES="Frequenzen ",
FREQUENCY="Frequenz %.3f",
BROADCAST="%s, %s, Radio %s für Aufgabenzuteilung!",
CASTTS="Nahbereichsunterstützung",
SEADTTS="Luftabwehr ausschalten",
BOMBTTS="Bombardieren",
PRECBOMBTTS="Präzisionsbombardieren",
BAITTS="Luftunterstützung",
ANTISHIPTTS="Anti-Schiff",
INTERCEPTTS="Abfangen",
BOMBRUNWAYTTS="Startbahn Bombardieren",
HAVEACTIVETASK="Du hast einen aktiven Auftrag! Beende ihn zuerst!",
PILOTJOINEDTASK="%s, %s hat Auftrag %s %03d angenommen",
TASKNAME="%s Auftrag ID %03d",
TASKNAMETTS="%s Auftrag ID %03d",
THREATHIGH="hoch",
THREATMEDIUM="mittel",
THREATLOW="niedrig",
THREATTEXT="%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s",
ELEVATION="\nZiel Höhe: %s %s",
METER="Meter",
FEET="Fuss",
THREATTEXTTTS="%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.",
MARKTASK="%s, %s, verstanden, Zielposition %03d auf der Karte markiert!",
SMOKETASK="%s, %s, verstanden, Zielposition %03d mit Rauch markiert!",
FLARETASK="%s, %s, verstanden, Zielposition %03d beleuchtet!",
ABORTTASK="%s, an alle, %s hat Auftrag %s %03d abgebrochen!",
UNKNOWN="Unbekannt",
MENUTASKING=" Aufträge ",
MENUACTIVE="Aktiver Auftrag",
MENUINFO="Information",
MENUMARK="Kartenmarkierung",
MENUSMOKE="Rauchgranate",
MENUFLARE="Leuchtgranate",
MENUILLU="Feldbeleuchtung",
MENUABORT="Abbrechen",
MENUJOIN="Auftrag annehmen",
MENUTASKINFO="Auftrag Briefing",
MENUTASKNO="AuftragsNr",
MENUNOTASKS="Momentan keine Aufträge verfügbar.",
TASKCANCELLED="Auftrag #%03d %s wurde beendet!",
TASKCANCELLEDTTS="%s, Auftrag %03d %s wurde beendet!",
TASKSUCCESS="Auftrag #%03d %s erfolgreich!",
TASKSUCCESSTTS="%s, Auftrag %03d %s erfolgreich!",
TASKFAILED="Auftrag #%03d %s gescheitert!",
TASKFAILEDTTS="%s, Auftrag %03d %s gescheitert!",
TASKFAILEDREPLAN="Auftrag #%03d %s gescheitert! Neuplanung!",
TASKFAILEDREPLANTTS="%s, Auftrag %03d %s gescheitert! Neuplanung!",
TASKADDED="%s hat einen neuen Auftrag %s erstellt!",
PILOTS="\nPilot(en): ",
PILOTSTTS=". Pilot(en): ",
YES="Ja",
NO="Nein",
NONE="Keine",
POINTEROVERTARGET="%s, %s, Marker im Zielbereich für %03d, Laser an!",
POINTERTARGETREPORT="\nMarker im Zielbereich: %s\nLaser an: %s",
RECCETARGETREPORT="\nSpäher % im Zielbereich: %s\nLasing: %s",
POINTERTARGETLASINGTTS=". Marker im Zielbereich, Laser is an.",
TARGET="Ziel",
FLASHON="%s - Richtungsangaben einblenden ist EIN!",
FLASHOFF="%s - Richtungsangaben einblenden ist AUS!",
FLASHMENU="Richtungsangaben Schalter",
BRIEFING="Briefing",
TARGETLOCATION="Zielposition",
COORDINATE="Koordinate",
INFANTRY="Infantrie",
TECHNICAL="Technische",
ARTILLERY="Artillerie",
TANKS="Panzer",
AIRDEFENSE="Flak",
SAM="Luftabwehr",
GROUP="Einheit",
UNARMEDSHIP="Handelsschiff",
LIGHTARMEDSHIP="Tender",
CORVETTE="Korvette",
FRIGATE="Fregatte",
CRUISER="Kreuzer",
DESTROYER="Zerstörer",
CARRIER="Flugzeugträger",
RADIOS="Frequenzen",
},
}
PLAYERTASKCONTROLLER.version="0.1.70"
function PLAYERTASKCONTROLLER:New(Name,Coalition,Type,ClientFilter)
local self=BASE:Inherit(self,FSM:New())
self.Name=Name or"CentCom"
self.Coalition=Coalition or coalition.side.BLUE
self.CoalitionName=UTILS.GetCoalitionName(Coalition)
self.Type=Type or PLAYERTASKCONTROLLER.Type.A2G
self.usecluster=false
if self.Type==PLAYERTASKCONTROLLER.Type.A2A then
self.usecluster=true
end
self.ClusterRadius=0.5
self.TargetRadius=500
self.ClientFilter=ClientFilter
self.TargetQueue=FIFO:New()
self.TaskQueue=FIFO:New()
self.TasksPerPlayer=FIFO:New()
self.PrecisionTasks=FIFO:New()
self.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.holdmenutime=30
self.MarkerReadOnly=false
self.repeatonfailed=true
self.repeattimes=5
self.UseGroupNames=true
self.customcallsigns={}
self.ShortCallsign=true
self.Keepnumber=false
self.CallsignTranslations=nil
self.noflaresmokemenu=false
self.illumenu=false
self.ShowMagnetic=true
self.UseTypeNames=false
self.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
self.IsClientSet=true
end
if ClientFilter and not self.IsClientSet then
self.ClientSet=SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart()
elseif not self.IsClientSet then
self.ClientSet=SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterStart()
end
self.ActiveClientSet=SET_CLIENT:New()
self.lid=string.format("PlayerTaskController %s %s | ",self.Name,tostring(self.Type))
self:_InitLocalization()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","TaskAdded","*")
self:AddTransition("*","TaskDone","*")
self:AddTransition("*","TaskCancelled","*")
self:AddTransition("*","TaskSuccess","*")
self:AddTransition("*","TaskFailed","*")
self:AddTransition("*","TaskProgress","*")
self:AddTransition("*","TaskTargetSmoked","*")
self:AddTransition("*","TaskTargetFlared","*")
self:AddTransition("*","TaskTargetIlluminated","*")
self:AddTransition("*","TaskRepeatOnFailed","*")
self:AddTransition("*","PlayerJoinedTask","*")
self:AddTransition("*","PlayerAbortedTask","*")
self:AddTransition("*","Stop","Stopped")
self:__Start(2)
local starttime=math.random(5,10)
self:__Status(starttime)
self:I(self.lid..self.version.." Started.")
return self
end
function PLAYERTASKCONTROLLER:EnableScoring(Scoring)
self.Scoring=Scoring or SCORING:New(self.Name)
return self
end
function PLAYERTASKCONTROLLER:DisableScoring()
self.Scoring=nil
return self
end
function PLAYERTASKCONTROLLER:_InitLocalization()
self:T(self.lid.."_InitLocalization")
self.gettext=TEXTANDSOUND:New("PLAYERTASKCONTROLLER","en")
self.locale="en"
for locale,table in pairs(self.Messages)do
local Locale=string.lower(tostring(locale))
self:T("**** Adding locale: "..Locale)
for ID,Text in pairs(table)do
self:T(string.format('Adding ID %s',tostring(ID)))
self.gettext:AddEntry(Locale,tostring(ID),Text)
end
end
return self
end
function PLAYERTASKCONTROLLER:SetEnableUseTypeNames()
self:T(self.lid.."SetEnableUseTypeNames")
self.UseTypeNames=true
return self
end
function PLAYERTASKCONTROLLER:SetDisableUseTypeNames()
self:T(self.lid.."SetDisableUseTypeNames")
self.UseTypeNames=false
return self
end
function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff)
self:T(self.lid.."SetAllowFlashDirection")
self.AllowFlash=OnOff
return self
end
function PLAYERTASKCONTROLLER:SetShowRadioInfoMenu(OnOff)
self:T(self.lid.."SetAllowRadioInfoMenu")
self.InformationMenu=OnOff
return self
end
function PLAYERTASKCONTROLLER:SetDisableSmokeFlareTask()
self:T(self.lid.."SetDisableSmokeFlareTask")
self.noflaresmokemenu=true
return self
end
function PLAYERTASKCONTROLLER:SetTransmitOnlyWithPlayers(Switch)
self.TransmitOnlyWithPlayers=Switch
if self.SRSQueue then
self.SRSQueue:SetTransmitOnlyWithPlayers(Switch)
end
return self
end
function PLAYERTASKCONTROLLER:SetEnableSmokeFlareTask()
self:T(self.lid.."SetEnableSmokeFlareTask")
self.noflaresmokemenu=false
return self
end
function PLAYERTASKCONTROLLER:SetEnableIlluminateTask()
self:T(self.lid.."SetEnableSmokeFlareTask")
self.illumenu=true
return self
end
function PLAYERTASKCONTROLLER:SetDisableIlluminateTask()
self:T(self.lid.."SetDisableIlluminateTask")
self.illumenu=false
return self
end
function PLAYERTASKCONTROLLER:SetInfoShowsCoordinate(OnOff,LLDDM)
self:T(self.lid.."SetInfoShowsCoordinate")
self.InfoHasCoordinate=OnOff
self.InfoHasLLDDM=LLDDM
return self
end
function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
if not ShortCallsign or ShortCallsign==false then
self.ShortCallsign=false
else
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)
self:T(self.lid.."_GetTextForSpeech")
text=string.gsub(text,"%d","%1 ")
text=string.gsub(text,"^%s*","")
text=string.gsub(text,"%s*$","")
text=string.gsub(text,"  "," ")
return text
end
function PLAYERTASKCONTROLLER:SetTaskRepetition(OnOff,Repeats)
self:T(self.lid.."SetTaskRepetition")
if OnOff then
self.repeatonfailed=true
self.repeattimes=Repeats or 5
else
self.repeatonfailed=false
self.repeattimes=Repeats or 5
end
return self
end
function PLAYERTASKCONTROLLER: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,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
LasingDrone=FlightGroup
self.precisionbombing=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,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)
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 OPSGROUP/SET_OPSGROUP object passed or object is not alive!")
end
else
self.autolase=nil
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
self.PlayerRecce=Recce
return self
end
function PLAYERTASKCONTROLLER:DisableBuddyLasing()
self:T(self.lid.."DisableBuddyLasing")
self.buddylasing=false
return self
end
function PLAYERTASKCONTROLLER:EnableMarkerOps(Tag)
self:T(self.lid.."EnableMarkerOps")
local tag=Tag or"TASK"
local MarkerOps=MARKEROPS_BASE:New(tag,{"Name","Text"},true)
local function Handler(Keywords,Coord,Text)
if self.verbose then
local m=MESSAGE:New(string.format("Target added from marker at: %s",Coord:ToStringA2G(nil,nil,self.ShowMagnetic)),15,"INFO"):ToAll()
local m=MESSAGE:New(string.format("Text: %s",Text),15,"INFO"):ToAll()
end
local menuname=string.match(Text,"Name=(.+),")
local freetext=string.match(Text,"Text=(.+)")
if menuname then
Coord.menuname=menuname
if freetext then
Coord.freetext=freetext
end
end
self:AddTarget(Coord)
end
function MarkerOps:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord)
Handler(Keywords,Coord,Text)
end
function MarkerOps:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord)
Handler(Keywords,Coord,Text)
end
self.MarkerOps=MarkerOps
return self
end
function PLAYERTASKCONTROLLER:_GetPlayerName(Client)
self:T(self.lid.."_GetPlayerName")
local playername=Client:GetPlayerName()
local ttsplayername=nil
if not self.customcallsigns[playername]then
local playergroup=Client:GetGroup()
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
return playername,ttsplayername
end
function PLAYERTASKCONTROLLER:DisablePrecisionBombing(FlightGroup,LaserCode)
self:T(self.lid.."DisablePrecisionBombing")
self.autolase=nil
self.precisionbombing=false
return self
end
function PLAYERTASKCONTROLLER:EnableTaskInfoMenu()
self:T(self.lid.."EnableTaskInfoMenu")
self.taskinfomenu=true
return self
end
function PLAYERTASKCONTROLLER:DisableTaskInfoMenu()
self:T(self.lid.."DisableTaskInfoMenu")
self.taskinfomenu=false
return self
end
function PLAYERTASKCONTROLLER:SetMenuOptions(InfoMenu,ItemLimit,HoldTime)
self:T(self.lid.."SetMenuOptions")
self.activehasinfomenu=InfoMenu or false
if self.activehasinfomenu then
self:EnableTaskInfoMenu()
end
self.menuitemlimit=ItemLimit or 5
self.holdmenutime=HoldTime or 30
return self
end
function PLAYERTASKCONTROLLER:SetMarkerReadOnly()
self:T(self.lid.."SetMarkerReadOnly")
self.MarkerReadOnly=true
return self
end
function PLAYERTASKCONTROLLER:SetMarkerDeleteable()
self:T(self.lid.."SetMarkerDeleteable")
self.MarkerReadOnly=false
return self
end
function PLAYERTASKCONTROLLER:_EventHandler(EventData)
self:T(self.lid.."_EventHandler: "..EventData.id)
if EventData.id==EVENTS.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=""
if self.TasksPerPlayer:HasUniqueID(EventData.IniPlayerName)then
local task=self.TasksPerPlayer:PullByID(EventData.IniPlayerName)
local Client=_DATABASE:FindClient(EventData.IniPlayerName)
if Client then
task:RemoveClient(Client)
text=self.gettext:GetEntry("TASKABORT",self.locale)
self.ActiveTaskMenuTemplate:ResetMenu(Client)
self.JoinTaskMenuTemplate:ResetMenu(Client)
else
task:RemoveClient(nil,EventData.IniPlayerName)
text=self.gettext:GetEntry("TASKABORT",self.locale)
end
else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
self:T(self.lid..text)
end
elseif EventData.id==EVENTS.PlayerEnterAircraft and EventData.IniCoalition==self.Coalition then
if EventData.IniPlayerName and EventData.IniGroup then
if self.IsClientSet and(not self.ClientSet:IsIncludeObject(CLIENT:FindByName(EventData.IniUnitName)))then
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
return self
end
self:T(self.lid.."Event for player: "..EventData.IniPlayerName)
if self.UseSRS then
local frequency=self.Frequency
local freqtext=""
if type(frequency)=="table"then
freqtext=self.gettext:GetEntry("FREQUENCIES",self.locale)
freqtext=freqtext..table.concat(frequency,", ")
else
local freqt=self.gettext:GetEntry("FREQUENCY",self.locale)
freqtext=string.format(freqt,frequency)
end
local modulation=self.Modulation
if type(modulation)=="table"then modulation=modulation[1]end
modulation=UTILS.GetModulationName(modulation)
local switchtext=self.gettext:GetEntry("BROADCAST",self.locale)
local playername=EventData.IniPlayerName
if EventData.IniGroup then
if self.customcallsigns[playername]then
self.customcallsigns[playername]=nil
end
playername=EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
end
playername=self:_GetTextForSpeech(playername)
local text=string.format(switchtext,playername,self.MenuName or self.Name,freqtext)
self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation)
end
if EventData.IniPlayerName then
local player=_DATABASE:FindClient(EventData.IniUnitName)
self:_SwitchMenuForClient(player,"Info")
end
end
end
return self
end
function PLAYERTASKCONTROLLER:SetLocale(Locale)
self:T(self.lid.."SetLocale")
self.locale=Locale or"en"
return self
end
function PLAYERTASKCONTROLLER:SuppressScreenOutput(OnOff)
self:T(self.lid.."SuppressScreenOutput")
self.NoScreenOutput=OnOff or false
return self
end
function PLAYERTASKCONTROLLER:SetTargetRadius(Radius)
self:T(self.lid.."SetTargetRadius")
self.TargetRadius=Radius or 500
return self
end
function PLAYERTASKCONTROLLER:SetClusterRadius(Radius)
self:T(self.lid.."SetClusterRadius")
self.ClusterRadius=Radius or 0.5
self.usecluster=true
return self
end
function PLAYERTASKCONTROLLER:CancelTask(Task)
self:T(self.lid.."CancelTask")
Task:__Cancel(-1)
return self
end
function PLAYERTASKCONTROLLER:SwitchUseGroupNames(OnOff)
self:T(self.lid.."SwitchUseGroupNames")
if OnOff then
self.UseGroupNames=true
else
self.UseGroupNames=false
end
return self
end
function PLAYERTASKCONTROLLER:SwitchMagenticAngles(OnOff)
self:T(self.lid.."SwitchMagenticAngles")
if OnOff then
self.ShowMagnetic=true
else
self.ShowMagnetic=false
end
return self
end
function PLAYERTASKCONTROLLER:_GetAvailableTaskTypes()
self:T(self.lid.."_GetAvailableTaskTypes")
local tasktypes={}
self.TaskQueue:ForEach(
function(Task)
local task=Task
local type=Task.Type
tasktypes[type]={}
end
)
return tasktypes
end
function PLAYERTASKCONTROLLER:_GetTasksPerType()
self:T(self.lid.."_GetTasksPerType")
local tasktypes=self:_GetAvailableTaskTypes()
local datatable=self.TaskQueue:GetDataTable()
local threattable={}
for _,_task in pairs(datatable)do
local task=_task
local threat=task.Target:GetThreatLevelMax()
if not task:IsDone()then
threattable[#threattable+1]={task=task,threat=threat}
end
end
table.sort(threattable,function(k1,k2)return k1.threat>k2.threat end)
for _id,_data in pairs(threattable)do
local threat=_data.threat
local task=_data.task
local type=task.Type
local name=task.Target:GetName()
if not task:IsDone()then
table.insert(tasktypes[type],task)
end
end
return tasktypes
end
function PLAYERTASKCONTROLLER:_CheckTargetQueue()
self:T(self.lid.."_CheckTargetQueue")
if self.TargetQueue:Count()>0 then
local object=self.TargetQueue:Pull()
local target=TARGET:New(object)
if object.menuname then
target.menuname=object.menuname
if object.freetext then
target.freetext=object.freetext
end
end
if object:IsInstanceOf("UNIT")or object:IsInstanceOf("GROUP")then
if self.UseTypeNames and object:IsGround()then
local threat=object:GetThreatLevel()
local typekey="INFANTRY"
if threat==0 or threat==2 then
typekey="TECHNICAL"
elseif threat==3 then
typekey="ARTILLERY"
elseif threat==4 or threat==5 then
typekey="TANKS"
elseif threat==6 or threat==7 then
typekey="AIRDEFENSE"
elseif threat>=8 then
typekey="SAM"
end
local typename=self.gettext:GetEntry(typekey,self.locale)
local gname=self.gettext:GetEntry("GROUP",self.locale)
target.TypeName=string.format("%s %s",typename,gname)
end
if self.UseTypeNames and object:IsShip()then
local threat=object:GetThreatLevel()
local typekey="UNARMEDSHIP"
if threat==1 then
typekey="LIGHTARMEDSHIP"
elseif threat==2 then
typekey="CORVETTE"
elseif threat==3 or threat==4 then
typekey="FRIGATE"
elseif threat==5 or threat==6 then
typekey="CRUISER"
elseif threat==7 or threat==8 then
typekey="DESTROYER"
elseif threat>=9 then
typekey="CARRIER"
end
local typename=self.gettext:GetEntry(typekey,self.locale)
target.TypeName=typename
end
end
self:_AddTask(target)
end
return self
end
function PLAYERTASKCONTROLLER:_CheckTaskQueue()
self:T(self.lid.."_CheckTaskQueue")
if self.TaskQueue:Count()>0 then
local tasks=self.TaskQueue:GetIDStack()
for _id,_entry in pairs(tasks)do
local data=_entry.data
self:T("Looking at Task: "..data.PlayerTaskNr.." Type: "..data.Type.." State: "..data:GetState())
if data:GetState()=="Done"or data:GetState()=="Stopped"then
local task=self.TaskQueue:ReadByID(_id)
local clientsattask=task.Clients:GetIDStackSorted()
for _,_id in pairs(clientsattask)do
self:T("*****Removing player ".._id)
self.TasksPerPlayer:PullByID(_id)
end
local clients=task:GetClientObjects()
for _,client in pairs(clients)do
self:_RemoveMenuEntriesForTask(task,client)
end
for _,client in pairs(clients)do
self:_SwitchMenuForClient(client,"Info",5)
end
local nexttasks={}
if task.FinalState=="Success"then
nexttasks=task.NextTaskSuccess
elseif task.FinalState=="Failed"then
nexttasks=task.NextTaskFailure
end
local clientlist,count=task:GetClientObjects()
if count>0 then
for _,_client in pairs(clientlist)do
local client=_client
local group=client:GetGroup()
for _,task in pairs(nexttasks)do
self:_JoinTask(task,true,group,client)
end
end
end
local TNow=timer.getAbsTime()
if TNow-task.timestamp>5 then
self:_RemoveMenuEntriesForTask(task)
local task=self.TaskQueue:PullByID(_id)
task=nil
end
end
end
end
return self
end
function PLAYERTASKCONTROLLER:_CheckPrecisionTasks()
self:T(self.lid.."_CheckPrecisionTasks")
self:T({count=self.PrecisionTasks:Count(),enabled=self.precisionbombing})
if self.PrecisionTasks:Count()>0 and self.precisionbombing 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 LasingDrone then
LasingDrone:_Respawn(1,nil,true)
else
end
end
end
)
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<mindist then
selected=grp
mindist=dist
end
end
end
)
return selected
end
local task=self.PrecisionTasks:Pull()
local taskpt=task.Target:GetCoordinate()
local SelectedDrone=SelectDrone(taskpt)
if SelectedDrone and SelectedDrone:IsAlive()then
if SelectedDrone.playertask and(not SelectedDrone.playertask.busy)then
self:T(self.lid.."Sending lasing unit to target")
local isassigned=self:_FindLasingDroneForTaskID(task.PlayerTaskNr)
local startpoint=SelectedDrone:GetCoordinate()
local endpoint=task.Target:GetCoordinate()
local dist=math.huge
if startpoint and endpoint then
dist=startpoint:Get2DDistance(endpoint)
end
if dist<=SelectedDrone.playertask.maxtravel and(not isassigned)then
SelectedDrone.playertask.id=task.PlayerTaskNr
SelectedDrone.playertask.busy=true
SelectedDrone.playertask.inreach=false
SelectedDrone.playertask.reachmessage=false
if SelectedDrone:IsFlightgroup()then
SelectedDrone:CancelAllMissions()
local auftrag=AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),SelectedDrone.playertask.alt,SelectedDrone.playertask.speed)
SelectedDrone:AddMission(auftrag)
elseif SelectedDrone:IsArmygroup()then
local tgtcoord=task.Target:GetCoordinate()
local tgtzone=ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000)
local finalpos=nil
for i=1,50 do
finalpos=tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER})
if finalpos then
if finalpos:IsLOS(tgtcoord,0)then
break
end
end
end
if finalpos then
SelectedDrone:CancelAllMissions()
local auftrag=AUFTRAG:NewARMOREDGUARD(finalpos,"Off road")
SelectedDrone:AddMission(auftrag)
else
self:E("***Could not find LOS position to post ArmyGroup for lasing!")
SelectedDrone.playertask.id=0
SelectedDrone.playertask.busy=false
SelectedDrone.playertask.inreach=false
SelectedDrone.playertask.reachmessage=false
end
end
else
self:T(self.lid.."Lasing unit too far from target")
end
end
end
self.PrecisionTasks:Push(task,task.PlayerTaskNr)
local function DronesWithTask(SelectedDrone)
if SelectedDrone.playertask and SelectedDrone.playertask.busy then
local task=self.PrecisionTasks:ReadByID(SelectedDrone.playertask.id)
self:T("Looking at Task: "..task.PlayerTaskNr.." Type: "..task.Type.." State: "..task:GetState())
if(not task)or task:GetState()=="Done"or task:GetState()=="Stopped"then
local task=self.PrecisionTasks:PullByID(SelectedDrone.playertask.id)
self:_CheckTaskQueue()
task=nil
if SelectedDrone:IsLasing()then
SelectedDrone:__LaserOff(-1)
end
SelectedDrone.playertask.busy=false
SelectedDrone.playertask.inreach=false
SelectedDrone.playertask.id=0
SelectedDrone.playertask.reachmessage=false
self:T(self.lid.."Laser Off")
else
self:T(self.lid.."Not done yet")
local dcoord=SelectedDrone:GetCoordinate()
local tcoord=task.Target:GetCoordinate()
tcoord.y=tcoord.y+2
local dist=dcoord:Get2DDistance(tcoord)
self:T(self.lid.."Dist "..dist)
if dist<3000 and not SelectedDrone:IsLasing()then
self:T(self.lid.."Laser On")
SelectedDrone:__LaserOn(-1,tcoord)
SelectedDrone.playertask.inreach=true
if not SelectedDrone.playertask.reachmessage then
SelectedDrone.playertask.reachmessage=true
local clients=task:GetClients()
local text=""
for _,playername in pairs(clients)do
local pointertext=self.gettext:GetEntry("POINTEROVERTARGET",self.locale)
local ttsplayername=playername
if self.customcallsigns[playername]then
ttsplayername=self.customcallsigns[playername]
end
text=string.format(pointertext,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr)
if not self.NoScreenOutput then
local client=nil
self.ClientSet:ForEachClient(
function(Client)
if Client:GetPlayerName()==playername then client=Client end
end
)
if client then
local m=MESSAGE:New(text,15,"Tasking"):ToClient(client)
end
end
end
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
end
end
end
end
end
self.LasingDroneSet:ForEachGroup(DronesWithTask)
end
return self
end
function PLAYERTASKCONTROLLER:_CheckPlayerHasTask(PlayerName)
self:T(self.lid.."_CheckPlayerHasTask")
return self.TasksPerPlayer:HasUniqueID(PlayerName)
end
function PLAYERTASKCONTROLLER:AddTarget(Target)
self:T(self.lid.."AddTarget")
self.TargetQueue:Push(Target)
return self
end
function PLAYERTASKCONTROLLER:_CheckTaskTypeAllowed(Type)
self:T(self.lid.."_CheckTaskTypeAllowed")
local Outcome=false
if self.UseWhiteList then
for _,_type in pairs(self.WhiteList)do
if Type==_type then
Outcome=true
break
end
end
else
return true
end
return Outcome
end
function PLAYERTASKCONTROLLER:_CheckTaskTypeDisallowed(Type)
self:T(self.lid.."_CheckTaskTypeDisallowed")
local Outcome=false
if self.UseBlackList then
for _,_type in pairs(self.BlackList)do
if Type==_type then
Outcome=true
break
end
end
else
return true
end
return Outcome
end
function PLAYERTASKCONTROLLER:SetTaskWhiteList(WhiteList)
self:T(self.lid.."SetTaskWhiteList")
self.WhiteList=WhiteList
self.UseWhiteList=true
return self
end
function PLAYERTASKCONTROLLER:SetTaskBlackList(BlackList)
self:T(self.lid.."SetTaskBlackList")
self.BlackList=BlackList
self.UseBlackList=true
return self
end
function PLAYERTASKCONTROLLER:SetSEADAttributes(Attributes)
self:T(self.lid.."SetSEADAttributes")
if type(Attributes)~="table"then
Attributes={Attributes}
end
self.SeadAttributes=Attributes
return self
end
function PLAYERTASKCONTROLLER:_IsAttributeSead(Attribute)
self:T(self.lid.."_IsAttributeSead?")
local IsSead=false
for _,_attribute in pairs(self.SeadAttributes)do
if Attribute==_attribute then
IsSead=true
break
end
end
return IsSead
end
function PLAYERTASKCONTROLLER:_AddTask(Target)
self:T(self.lid.."_AddTask")
local cat=Target:GetCategory()
local threat=Target:GetThreatLevelMax()
local type=AUFTRAG.Type.CAS
local ttstype=self.gettext:GetEntry("CASTTS",self.locale)
if cat==TARGET.Category.GROUND then
type=AUFTRAG.Type.CAS
local targetobject=Target:GetObject()
if targetobject:IsInstanceOf("UNIT")then
self:T("SEAD Check UNIT")
if targetobject:HasSEAD()then
type=AUFTRAG.Type.SEAD
ttstype=self.gettext:GetEntry("SEADTTS",self.locale)
end
elseif targetobject:IsInstanceOf("GROUP")then
self:T("SEAD Check GROUP")
local attribute=targetobject:GetAttribute()
if self:_IsAttributeSead(attribute)then
type=AUFTRAG.Type.SEAD
ttstype=self.gettext:GetEntry("SEADTTS",self.locale)
end
elseif targetobject:IsInstanceOf("SET_GROUP")then
self:T("SEAD Check SET_GROUP")
targetobject:ForEachGroup(
function(group)
local attribute=group:GetAttribute()
if self:_IsAttributeSead(attribute)then
type=AUFTRAG.Type.SEAD
ttstype=self.gettext:GetEntry("SEADTTS",self.locale)
end
end
)
elseif targetobject:IsInstanceOf("SET_UNIT")then
self:T("SEAD Check SET_UNIT")
targetobject:ForEachUnit(
function(unit)
if unit:HasSEAD()then
type=AUFTRAG.Type.SEAD
ttstype=self.gettext:GetEntry("SEADTTS",self.locale)
end
end
)
elseif targetobject:IsInstanceOf("SET_STATIC")or targetobject:IsInstanceOf("STATIC")then
self:T("(PRECISION-)BOMBING SET_STATIC or STATIC")
if self.precisionbombing then
type=AUFTRAG.Type.PRECISIONBOMBING
ttstype=self.gettext:GetEntry("PRECBOMBTTS",self.locale)
else
type=AUFTRAG.Type.BOMBING
ttstype=self.gettext:GetEntry("BOMBTTS",self.locale)
end
end
local targetcoord=Target:GetCoordinate()
local targetvec2=targetcoord:GetVec2()
local targetzone=ZONE_RADIUS:New(self.Name,targetvec2,self.TargetRadius)
local coalition=targetobject:GetCoalitionName()or"Blue"
coalition=string.lower(coalition)
self:T("Target coalition is "..tostring(coalition))
local filtercoalition="blue"
if coalition=="blue"then filtercoalition="red"end
local friendlyset=SET_GROUP:New():FilterCategoryGround():FilterCoalitions(filtercoalition):FilterZones({targetzone}):FilterOnce()
if friendlyset:Count()==0 and type==AUFTRAG.Type.CAS then
type=AUFTRAG.Type.BAI
ttstype=self.gettext:GetEntry("BAITTS",self.locale)
end
if(type==AUFTRAG.Type.BAI or type==AUFTRAG.Type.CAS)and(self.precisionbombing or self.buddylasing)then
if threat>2 and threat<7 then
type=AUFTRAG.Type.PRECISIONBOMBING
ttstype=self.gettext:GetEntry("PRECBOMBTTS",self.locale)
end
end
elseif cat==TARGET.Category.NAVAL then
type=AUFTRAG.Type.ANTISHIP
ttstype=self.gettext:GetEntry("ANTISHIPTTS",self.locale)
elseif cat==TARGET.Category.AIRCRAFT then
type=AUFTRAG.Type.INTERCEPT
ttstype=self.gettext:GetEntry("INTERCEPTTS",self.locale)
elseif cat==TARGET.Category.AIRBASE then
type=AUFTRAG.Type.BOMBRUNWAY
ttstype=self.gettext:GetEntry("BOMBRUNWAYTTS",self.locale)
elseif cat==TARGET.Category.COORDINATE or cat==TARGET.Category.ZONE then
local zone=Target:GetObject()
if cat==TARGET.Category.COORDINATE then
zone=ZONE_RADIUS:New("TargetZone-"..math.random(1,10000),Target:GetVec2(),self.TargetRadius)
end
local enemies=self.CoalitionName=="Blue"and"red"or"blue"
local enemysetg=SET_GROUP:New():FilterCoalitions(enemies):FilterCategoryGround():FilterActive(true):FilterZones({zone}):FilterOnce()
local enemysets=SET_STATIC:New():FilterCoalitions(enemies):FilterZones({zone}):FilterOnce()
local countg=enemysetg:Count()
local counts=enemysets:Count()
if countg>0 then
if Target.menuname then
enemysetg.menuname=Target.menuname
if Target.freetext then
enemysetg.freetext=Target.freetext
end
end
self:AddTarget(enemysetg)
end
if counts>0 then
if Target.menuname then
enemysets.menuname=Target.menuname
if Target.freetext then
enemysets.freetext=Target.freetext
end
end
self:AddTarget(enemysets)
end
return self
end
if self.UseWhiteList then
if not self:_CheckTaskTypeAllowed(type)then
return self
end
end
if self.UseBlackList then
if self:_CheckTaskTypeDisallowed(type)then
return self
end
end
local task=PLAYERTASK:New(type,Target,self.repeatonfailed,self.repeattimes,ttstype)
if Target.menuname then
task:SetMenuName(Target.menuname)
if Target.freetext then
task:AddFreetext(Target.freetext)
end
end
task.coalition=self.Coalition
task.TypeName=Target.TypeName
if type==AUFTRAG.Type.BOMBRUNWAY then
task:HandleEvent(EVENTS.Shot)
function task:OnEventShot(EventData)
local data=EventData
local wcat=Object.getCategory(data.Weapon)
local coord=data.IniUnit:GetCoordinate()or data.IniGroup:GetCoordinate()
local vec2=coord:GetVec2()or{x=0,y=0}
local coal=data.IniCoalition
local afbzone=AIRBASE:FindByName(Target:GetName()):GetZone()
local runways=AIRBASE:FindByName(Target:GetName()):GetRunways()or{}
local inrunwayzone=false
for _,_runway in pairs(runways)do
local runway=_runway
if runway.zone:IsVec2InZone(vec2)then
inrunwayzone=true
end
end
local inzone=afbzone:IsVec2InZone(vec2)
if coal==task.coalition and(wcat==2 or wcat==3)and(inrunwayzone or inzone)then
task:__Success(-20)
end
end
end
task:_SetController(self)
self.TaskQueue:Push(task)
self:__TaskAdded(10,task)
return self
end
function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter)
self:T(self.lid.."AddPlayerTaskToQueue")
if PlayerTask and PlayerTask.ClassName and PlayerTask.ClassName=="PLAYERTASK"then
if TaskFilter then
if self.UseWhiteList and(not self:_CheckTaskTypeAllowed(PlayerTask.Type))then
return self
end
if self.UseBlackList and self:_CheckTaskTypeDisallowed(PlayerTask.Type)then
return self
end
end
PlayerTask:_SetController(self)
PlayerTask:SetCoalition(self.Coalition)
self.TaskQueue:Push(PlayerTask)
if not Silent then
self:__TaskAdded(10,PlayerTask)
end
else
self:E(self.lid.."***** NO valid PAYERTASK object sent!")
end
return self
end
function PLAYERTASKCONTROLLER:_JoinTask(Task,Force,Group,Client)
self:T({Force,Group,Client})
self:T(self.lid.."_JoinTask")
local force=false
if type(Force)=="boolean"then
force=Force
end
local playername,ttsplayername=self:_GetPlayerName(Client)
if self.TasksPerPlayer:HasUniqueID(playername)and not force then
if not self.NoScreenOutput then
local text=self.gettext:GetEntry("HAVEACTIVETASK",self.locale)
local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client)
end
return self
end
local taskstate=Task:GetState()
if not Task:IsDone()then
if taskstate~="Executing"then
Task:__Requested(-1)
Task:__Executing(-2)
end
Task:AddClient(Client)
local joined=self.gettext:GetEntry("PILOTJOINEDTASK",self.locale)
local text=string.format(joined,ttsplayername,self.MenuName or self.Name,Task.TTSType,Task.PlayerTaskNr)
self:T(self.lid..text)
if not self.NoScreenOutput then
self:_SendMessageToClients(text)
end
if self.UseSRS then
self:T(self.lid..text)
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
self.TasksPerPlayer:Push(Task,playername)
self:__PlayerJoinedTask(1,Group,Client,Task)
self:_SwitchMenuForClient(Client,"Active",1)
end
if Task.Type==AUFTRAG.Type.PRECISIONBOMBING then
if not self.PrecisionTasks:HasUniqueID(Task.PlayerTaskNr)then
self.PrecisionTasks:Push(Task,Task.PlayerTaskNr)
end
end
return self
end
function PLAYERTASKCONTROLLER:_SwitchFlashing(Group,Client)
self:T(self.lid.."_SwitchFlashing")
local playername,ttsplayername=self:_GetPlayerName(Client)
if(not self.FlashPlayer[playername])or(self.FlashPlayer[playername]==false)then
self.FlashPlayer[playername]=Client
local flashtext=self.gettext:GetEntry("FLASHON",self.locale)
local text=string.format(flashtext,ttsplayername)
local m=MESSAGE:New(text,10,"Tasking"):ToClient(Client)
else
self.FlashPlayer[playername]=false
local flashtext=self.gettext:GetEntry("FLASHOFF",self.locale)
local text=string.format(flashtext,ttsplayername)
local m=MESSAGE:New(text,10,"Tasking"):ToClient(Client)
end
return self
end
function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group,Client)
self:T(self.lid.."_ShowRadioInfo")
local playername,ttsplayername=self:_GetPlayerName(Client)
if self.UseSRS then
local frequency=self.Frequency
local freqtext=""
if type(frequency)=="table"then
freqtext=self.gettext:GetEntry("FREQUENCIES",self.locale)
freqtext=freqtext..table.concat(frequency,", ")
else
local freqt=self.gettext:GetEntry("FREQUENCY",self.locale)
freqtext=string.format(freqt,frequency)
end
local switchtext=self.gettext:GetEntry("BROADCAST",self.locale)
playername=ttsplayername or self:_GetTextForSpeech(playername)
local text=string.format(switchtext,playername,self.MenuName or self.Name,freqtext)
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2,{Group},text,30,self.BCFrequency,self.BCModulation)
end
return self
end
function PLAYERTASKCONTROLLER:_FlashInfo()
self:T(self.lid.."_FlashInfo")
for _playername,_client in pairs(self.FlashPlayer)do
if _client and _client:IsAlive()then
if self.TasksPerPlayer:HasUniqueID(_playername)then
local task=self.TasksPerPlayer:ReadByID(_playername)
local Coordinate=task.Target:GetCoordinate()
local CoordText=""
if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then
CoordText=Coordinate:ToStringA2G(_client,nil,self.ShowMagnetic)
else
CoordText=Coordinate:ToStringA2A(_client,nil,self.ShowMagnetic)
end
local targettxt=self.gettext:GetEntry("TARGET",self.locale)
local text="Target: "..CoordText
local m=MESSAGE:New(text,10,"Tasking"):ToClient(_client)
end
end
end
return self
end
function PLAYERTASKCONTROLLER:_FindLasingDroneForTaskID(ID)
local drone=nil
self.LasingDroneSet:ForEachGroup(
function(grp)
if grp and grp:IsAlive()and grp.playertask and grp.playertask.id and grp.playertask.id==ID then
drone=grp
end
end
)
return drone
end
function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task,Group,Client)
self:T(self.lid.."_ActiveTaskInfo")
local playername,ttsplayername=self:_GetPlayerName(Client)
local text=""
local textTTS=""
local task=nil
if type(Task)~="string"then
task=Task
end
if self.TasksPerPlayer:HasUniqueID(playername)or task then
local task=task or self.TasksPerPlayer:ReadByID(playername)
local tname=self.gettext:GetEntry("TASKNAME",self.locale)
local ttsname=self.gettext:GetEntry("TASKNAMETTS",self.locale)
local taskname=string.format(tname,task.Type,task.PlayerTaskNr)
local ttstaskname=string.format(ttsname,task.TTSType,task.PlayerTaskNr)
local Coordinate=task.Target:GetCoordinate()or COORDINATE:New(0,0,0)
local Elevation=Coordinate:GetLandHeight()or 0
local CoordText=""
local CoordTextLLDM=nil
local LasingDrone=self:_FindLasingDroneForTaskID(task.PlayerTaskNr)
if self.Type~=PLAYERTASKCONTROLLER.Type.A2A then
CoordText=Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic)
else
CoordText=Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic)
end
local ThreatLevel=task.Target:GetThreatLevelMax()
local ThreatLevelText=self.gettext:GetEntry("THREATHIGH",self.locale)
if ThreatLevel>3 and ThreatLevel<8 then
ThreatLevelText=self.gettext:GetEntry("THREATMEDIUM",self.locale)
elseif ThreatLevel<=3 then
ThreatLevelText=self.gettext:GetEntry("THREATLOW",self.locale)
end
local targets=task.Target:CountTargets()or 0
local clientlist,clientcount=task:GetClients()
local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel
local ThreatLocaleText=self.gettext:GetEntry("THREATTEXT",self.locale)
text=string.format(ThreatLocaleText,taskname,ThreatGraph,targets,CoordText)
local settings=_DATABASE:GetPlayerSettings(playername)or _SETTINGS
local elevationmeasure=self.gettext:GetEntry("FEET",self.locale)
if settings:IsMetric()then
elevationmeasure=self.gettext:GetEntry("METER",self.locale)
else
Elevation=math.floor(UTILS.MetersToFeet(Elevation))
end
local elev=self.gettext:GetEntry("ELEVATION",self.locale)
text=text..string.format(elev,tostring(math.floor(Elevation)),elevationmeasure)
if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
if LasingDrone and LasingDrone.playertask then
local yes=self.gettext:GetEntry("YES",self.locale)
local no=self.gettext:GetEntry("NO",self.locale)
local inreach=LasingDrone.playertask.inreach==true and yes or no
local islasing=LasingDrone:IsLasing()==true and yes or no
local prectext=self.gettext:GetEntry("POINTERTARGETREPORT",self.locale)
prectext=string.format(prectext,inreach,islasing)
text=text..prectext.." ("..LasingDrone.playertask.lasercode..")"
end
end
if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.buddylasing then
if self.PlayerRecce then
local yes=self.gettext:GetEntry("YES",self.locale)
local no=self.gettext:GetEntry("NO",self.locale)
local reachdist=8000
local inreach=false
local pset=self.PlayerRecce.PlayerSet:GetAliveSet()
for _,_player in pairs(pset)do
local player=_player
local pcoord=player:GetCoordinate()
if pcoord:Get2DDistance(Coordinate)<=reachdist then
inreach=true
local callsign=player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local playername=player:GetPlayerName()
local islasing=no
if self.PlayerRecce.CanLase[player:GetTypeName()]and self.PlayerRecce.AutoLase[playername]then
islasing=yes
end
local inrtext=inreach==true and yes or no
local prectext=self.gettext:GetEntry("RECCETARGETREPORT",self.locale)
prectext=string.format(prectext,callsign,inrtext,islasing)
text=text..prectext
end
end
end
elseif task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR then
text=taskname
textTTS=taskname
local detail=task:GetFreetext()
local detailTTS=task:GetFreetextTTS()
local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale)
local locatxt=self.gettext:GetEntry("TARGETLOCATION",self.locale)
text=text..string.format("\n%s: %s\n%s %s",brieftxt,detail,locatxt,CoordText)
textTTS=textTTS..string.format("; %s: %s; %s %s",brieftxt,detailTTS,locatxt,CoordText)
end
local clienttxt=self.gettext:GetEntry("PILOTS",self.locale)
if clientcount>0 then
for _,_name in pairs(clientlist)do
if self.customcallsigns[_name]then
_name=self.customcallsigns[_name]
_name=string.gsub(_name,"(%d) ","%1")
end
clienttxt=clienttxt.._name..", "
end
clienttxt=string.gsub(clienttxt,", $",".")
else
local keine=self.gettext:GetEntry("NONE",self.locale)
clienttxt=clienttxt..keine
end
text=text..clienttxt
textTTS=textTTS..clienttxt
if self.InfoHasCoordinate then
if self.InfoHasLLDDM then
CoordTextLLDM=Coordinate:ToStringLLDDM()
else
CoordTextLLDM=Coordinate:ToStringLLDMS()
end
local locatxt=self.gettext:GetEntry("COORDINATE",self.locale)
text=string.format("%s\n%s: %s",text,locatxt,CoordTextLLDM)
end
if task:HasFreetext()and not(task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR)then
local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale)
text=text..string.format("\n%s: ",brieftxt)..task:GetFreetext()
end
if self.UseSRS then
if string.find(CoordText," BR, ")then
CoordText=string.gsub(CoordText," BR, "," Bee, Arr; ")
end
if self.ShowMagnetic then
text=string.gsub(text,"°M|","° magnetic; ")
end
if string.find(CoordText,"MGRS")then
local Text=string.gsub(CoordText,"MGRS ","")
Text=string.gsub(Text,"%s+","")
Text=string.gsub(Text,"([%a%d])","%1;")
Text=string.gsub(Text,"0","zero")
Text=string.gsub(Text,"9","niner")
CoordText="MGRS;"..Text
if self.PathToGoogleKey then
end
end
local ThreatLocaleTextTTS=self.gettext:GetEntry("THREATTEXTTTS",self.locale)
local ttstext=string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText,targets,CoordText)
if task.Type==AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing()then
local lasingtext=self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale)
ttstext=ttstext..lasingtext
end
elseif task.Type==AUFTRAG.Type.CTLD or task.Type==AUFTRAG.Type.CSAR then
ttstext=textTTS
if string.find(ttstext," BR, ")then
CoordText=string.gsub(ttstext," BR, "," Bee, Arr, ")
end
elseif task:HasFreetext()then
local brieftxt=self.gettext:GetEntry("BRIEFING",self.locale)
ttstext=ttstext..string.format("; %s: ",brieftxt)..task:GetFreetextTTS()
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2)
end
else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
if not self.NoScreenOutput then
local m=MESSAGE:New(text,self.TaskInfoDuration or 30,"Tasking"):ToClient(Client)
end
return self
end
function PLAYERTASKCONTROLLER:_MarkTask(Group,Client)
self:T(self.lid.."_MarkTask")
local playername,ttsplayername=self:_GetPlayerName(Client)
local text=""
if self.TasksPerPlayer:HasUniqueID(playername)then
local task=self.TasksPerPlayer:ReadByID(playername)
text=string.format("Task ID #%03d | Type: %s | Threat: %d",task.PlayerTaskNr,task.Type,task.Target:GetThreatLevelMax())
task:MarkTargetOnF10Map(text,self.Coalition,self.MarkerReadOnly)
local textmark=self.gettext:GetEntry("MARKTASK",self.locale)
text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr)
self:T(self.lid..text)
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
if not self.NoScreenOutput then
local m=MESSAGE:New(text,"10","Tasking"):ToClient(Client)
end
return self
end
function PLAYERTASKCONTROLLER:_SmokeTask(Group,Client)
self:T(self.lid.."_SmokeTask")
local playername,ttsplayername=self:_GetPlayerName(Client)
local text=""
if self.TasksPerPlayer:HasUniqueID(playername)then
local task=self.TasksPerPlayer:ReadByID(playername)
task:SmokeTarget()
local textmark=self.gettext:GetEntry("SMOKETASK",self.locale)
text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr)
self:T(self.lid..text)
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
self:__TaskTargetSmoked(5,task)
else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
if not self.NoScreenOutput then
local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client)
end
return self
end
function PLAYERTASKCONTROLLER:_FlareTask(Group,Client)
self:T(self.lid.."_FlareTask")
local playername,ttsplayername=self:_GetPlayerName(Client)
local text=""
if self.TasksPerPlayer:HasUniqueID(playername)then
local task=self.TasksPerPlayer:ReadByID(playername)
task:FlareTarget()
local textmark=self.gettext:GetEntry("FLARETASK",self.locale)
text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr)
self:T(self.lid..text)
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
self:__TaskTargetFlared(5,task)
else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
if not self.NoScreenOutput then
local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client)
end
return self
end
function PLAYERTASKCONTROLLER:_IlluminateTask(Group,Client)
self:T(self.lid.."_IlluminateTask")
local playername,ttsplayername=self:_GetPlayerName(Client)
local text=""
if self.TasksPerPlayer:HasUniqueID(playername)then
local task=self.TasksPerPlayer:ReadByID(playername)
task:FlareTarget()
local textmark=self.gettext:GetEntry("FLARETASK",self.locale)
text=string.format(textmark,ttsplayername,self.MenuName or self.Name,task.PlayerTaskNr)
self:T(self.lid..text)
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
self:__TaskTargetIlluminated(5,task)
else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
if not self.NoScreenOutput then
local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client)
end
return self
end
function PLAYERTASKCONTROLLER:_AbortTask(Group,Client)
self:T(self.lid.."_AbortTask")
local playername,ttsplayername=self:_GetPlayerName(Client)
local text=""
if self.TasksPerPlayer:HasUniqueID(playername)then
local task=self.TasksPerPlayer:PullByID(playername)
task:ClientAbort(Client)
local textmark=self.gettext:GetEntry("ABORTTASK",self.locale)
text=string.format(textmark,self.MenuName or self.Name,ttsplayername,task.TTSType,task.PlayerTaskNr)
self:T(self.lid..text)
if self.UseSRS then
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
self:__PlayerAbortedTask(1,Group,Client,task)
else
text=self.gettext:GetEntry("NOACTIVETASK",self.locale)
end
if not self.NoScreenOutput then
local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client)
end
self:_SwitchMenuForClient(Client,"Info",1)
return self
end
function PLAYERTASKCONTROLLER:_UpdateJoinMenuTemplate()
self:T("_UpdateJoinMenuTemplate")
if self.TaskQueue:Count()>0 then
local taskpertype=self:_GetTasksPerType()
local JoinMenu=self.JoinMenu
local controller=self.JoinTaskMenuTemplate
local actcontroller=self.ActiveTaskMenuTemplate
local actinfomenu=self.ActiveInfoMenu
if self.TaskQueue:Count()==0 and self.MenuNoTask==nil then
local menunotasks=self.gettext:GetEntry("MENUNOTASKS",self.locale)
self.MenuNoTask=controller:NewEntry(menunotasks,self.JoinMenu)
controller:AddEntry(self.MenuNoTask)
end
if self.TaskQueue:Count()>0 and self.MenuNoTask~=nil then
controller:DeleteGenericEntry(self.MenuNoTask)
controller:DeleteF10Entry(self.MenuNoTask)
self.MenuNoTask=nil
end
local maxn=self.menuitemlimit
for _type,_ in pairs(taskpertype)do
local found=controller:FindEntriesByText(_type)
if#found==0 then
local newentry=controller:NewEntry(_type,JoinMenu)
controller:AddEntry(newentry)
if self.JoinInfoMenu then
local newentry=controller:NewEntry(_type,self.JoinInfoMenu)
controller:AddEntry(newentry)
end
if actinfomenu then
local newentry=actcontroller:NewEntry(_type,self.ActiveInfoMenu)
actcontroller:AddEntry(newentry)
end
end
end
local typelist=self:_GetAvailableTaskTypes()
for _tasktype,_data in pairs(typelist)do
self:T("**** Building for TaskType: ".._tasktype)
for _,_task in pairs(taskpertype[_tasktype])do
_task=_task
self:T("**** Building for Task: ".._task.Target:GetName())
if _task.InMenu then
self:T("**** Task already in Menu ".._task.Target:GetName())
else
local menutaskno=self.gettext:GetEntry("MENUTASKNO",self.locale)
local text=string.format("%s %03d",menutaskno,_task.PlayerTaskNr)
if self.UseGroupNames then
local name=_task.Target:GetName()
if name~="Unknown"then
text=string.format("%s (%03d)",name,_task.PlayerTaskNr)
end
end
local parenttable,number=controller:FindEntriesByText(_tasktype,JoinMenu)
if number>0 then
local Parent=parenttable[1]
local matches,count=controller:FindEntriesByParent(Parent)
self:T("***** Join Menu ".._tasktype.." # of entries: "..count)
if count<self.menuitemlimit then
local taskentry=controller:NewEntry(text,Parent,self._JoinTask,self,_task,"false")
controller:AddEntry(taskentry)
_task.InMenu=true
if not _task.UUIDS then _task.UUIDS={}end
table.insert(_task.UUIDS,taskentry.UUID)
end
end
if self.JoinInfoMenu then
local parenttable,number=controller:FindEntriesByText(_tasktype,self.JoinInfoMenu)
if number>0 then
local Parent=parenttable[1]
local matches,count=controller:FindEntriesByParent(Parent)
self:T("***** Join Info Menu ".._tasktype.." # of entries: "..count)
if count<self.menuitemlimit then
local taskentry=controller:NewEntry(text,Parent,self._ActiveTaskInfo,self,_task)
controller:AddEntry(taskentry)
_task.InMenu=true
if not _task.UUIDS then _task.UUIDS={}end
table.insert(_task.UUIDS,taskentry.UUID)
end
end
end
if actinfomenu then
local parenttable,number=actcontroller:FindEntriesByText(_tasktype,self.ActiveInfoMenu)
if number>0 then
local Parent=parenttable[1]
local matches,count=actcontroller:FindEntriesByParent(Parent)
self:T("***** Active Info Menu ".._tasktype.." # of entries: "..count)
if count<self.menuitemlimit then
local taskentry=actcontroller:NewEntry(text,Parent,self._ActiveTaskInfo,self,_task)
actcontroller:AddEntry(taskentry)
_task.InMenu=true
if not _task.AUUIDS then _task.AUUIDS={}end
table.insert(_task.AUUIDS,taskentry.UUID)
end
end
end
end
end
end
end
return self
end
function PLAYERTASKCONTROLLER:_RemoveMenuEntriesForTask(Task,Client)
self:T("_RemoveMenuEntriesForTask")
if Task then
if Task.UUIDS and self.JoinTaskMenuTemplate then
local controller=self.JoinTaskMenuTemplate
for _,_uuid in pairs(Task.UUIDS)do
local Entry=controller:FindEntryByUUID(_uuid)
if Entry then
controller:DeleteF10Entry(Entry,Client)
controller:DeleteGenericEntry(Entry)
end
end
end
if Task.AUUIDS and self.ActiveTaskMenuTemplate then
for _,_uuid in pairs(Task.AUUIDS)do
local controller=self.ActiveTaskMenuTemplate
local Entry=controller:FindEntryByUUID(_uuid)
if Entry then
controller:DeleteF10Entry(Entry,Client)
controller:DeleteGenericEntry(Entry)
end
end
end
Task.UUIDS=nil
Task.AUUIDS=nil
end
return self
end
function PLAYERTASKCONTROLLER:_CreateJoinMenuTemplate()
self:T("_CreateActiveTaskMenuTemplate")
local menujoin=self.gettext:GetEntry("MENUJOIN",self.locale)
local menunotasks=self.gettext:GetEntry("MENUNOTASKS",self.locale)
local flashtext=self.gettext:GetEntry("FLASHMENU",self.locale)
local JoinTaskMenuTemplate=CLIENTMENUMANAGER:New(self.ClientSet,"JoinTask")
if not self.JoinTopMenu then
local taskings=self.gettext:GetEntry("MENUTASKING",self.locale)
local longname=self.Name..taskings..self.Type
local menuname=self.MenuName or longname
self.JoinTopMenu=JoinTaskMenuTemplate:NewEntry(menuname,self.MenuParent)
end
if self.AllowFlash then
JoinTaskMenuTemplate:NewEntry(flashtext,self.JoinTopMenu,self._SwitchFlashing,self)
end
self.JoinMenu=JoinTaskMenuTemplate:NewEntry(menujoin,self.JoinTopMenu)
if self.taskinfomenu then
local menutaskinfo=self.gettext:GetEntry("MENUTASKINFO",self.locale)
self.JoinInfoMenu=JoinTaskMenuTemplate:NewEntry(menutaskinfo,self.JoinTopMenu)
end
if self.TaskQueue:Count()==0 and self.MenuNoTask==nil then
self.MenuNoTask=JoinTaskMenuTemplate:NewEntry(menunotasks,self.JoinMenu)
end
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
function PLAYERTASKCONTROLLER:_CreateActiveTaskMenuTemplate()
self:T("_CreateActiveTaskMenuTemplate")
local menuactive=self.gettext:GetEntry("MENUACTIVE",self.locale)
local menuinfo=self.gettext:GetEntry("MENUINFO",self.locale)
local menumark=self.gettext:GetEntry("MENUMARK",self.locale)
local menusmoke=self.gettext:GetEntry("MENUSMOKE",self.locale)
local menuflare=self.gettext:GetEntry("MENUFLARE",self.locale)
local menuillu=self.gettext:GetEntry("MENUILLU",self.locale)
local menuabort=self.gettext:GetEntry("MENUABORT",self.locale)
local ActiveTaskMenuTemplate=CLIENTMENUMANAGER:New(self.ActiveClientSet,"ActiveTask")
if not self.ActiveTopMenu then
local taskings=self.gettext:GetEntry("MENUTASKING",self.locale)
local longname=self.Name..taskings..self.Type
local menuname=self.MenuName or longname
self.ActiveTopMenu=ActiveTaskMenuTemplate:NewEntry(menuname,self.MenuParent)
end
if self.AllowFlash then
local flashtext=self.gettext:GetEntry("FLASHMENU",self.locale)
ActiveTaskMenuTemplate:NewEntry(flashtext,self.ActiveTopMenu,self._SwitchFlashing,self)
end
local active=ActiveTaskMenuTemplate:NewEntry(menuactive,self.ActiveTopMenu)
ActiveTaskMenuTemplate:NewEntry(menuinfo,active,self._ActiveTaskInfo,self,"NONE")
ActiveTaskMenuTemplate:NewEntry(menumark,active,self._MarkTask,self)
if self.Type~=PLAYERTASKCONTROLLER.Type.A2A and self.noflaresmokemenu~=true then
ActiveTaskMenuTemplate:NewEntry(menusmoke,active,self._SmokeTask,self)
ActiveTaskMenuTemplate:NewEntry(menuflare,active,self._FlareTask,self)
if self.illumenu then
ActiveTaskMenuTemplate:NewEntry(menuillu,active,self._IlluminateTask,self)
end
end
ActiveTaskMenuTemplate:NewEntry(menuabort,active,self._AbortTask,self)
self.ActiveTaskMenuTemplate=ActiveTaskMenuTemplate
if self.taskinfomenu and self.activehasinfomenu then
local menutaskinfo=self.gettext:GetEntry("MENUTASKINFO",self.locale)
self.ActiveInfoMenu=ActiveTaskMenuTemplate:NewEntry(menutaskinfo,self.ActiveTopMenu)
end
return self
end
function PLAYERTASKCONTROLLER:_SwitchMenuForClient(Client,MenuType,Delay)
self:T(self.lid.."_SwitchMenuForClient")
if Delay then
self:ScheduleOnce(Delay,self._SwitchMenuForClient,self,Client,MenuType)
return self
end
if MenuType=="Info"then
self.ClientSet:AddClientsByName(Client:GetName())
self.ActiveClientSet:Remove(Client:GetName(),true)
self.ActiveTaskMenuTemplate:ResetMenu(Client)
self.JoinTaskMenuTemplate:ResetMenu(Client)
self.JoinTaskMenuTemplate:Propagate(Client)
elseif MenuType=="Active"then
self.ActiveClientSet:AddClientsByName(Client:GetName())
self.ClientSet:Remove(Client:GetName(),true)
self.ActiveTaskMenuTemplate:ResetMenu(Client)
self.JoinTaskMenuTemplate:ResetMenu(Client)
self.ActiveTaskMenuTemplate:Propagate(Client)
else
self:E(self.lid.."Unknown menu type in _SwitchMenuForClient:"..tostring(MenuType))
end
return self
end
function PLAYERTASKCONTROLLER:AddAgent(Recce)
self:T(self.lid.."AddAgent")
if self.Intel then
self.Intel:AddAgent(Recce)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER:AddAgentSet(RecceSet)
self:T(self.lid.."AddAgentSet")
if self.Intel then
local Set=RecceSet:GetAliveSet()
for _,_Recce in pairs(Set)do
self.Intel:AddAgent(_Recce)
end
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER:SwitchDetectStatics(OnOff)
self:T(self.lid.."SwitchDetectStatics")
if self.Intel then
self.Intel:SetDetectStatics(OnOff)
else
self:E(self.lid.."***** NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER:AddAcceptZone(AcceptZone)
self:T(self.lid.."AddAcceptZone")
if self.Intel then
self.Intel:AddAcceptZone(AcceptZone)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER:AddAcceptZoneSet(AcceptZoneSet)
self:T(self.lid.."AddAcceptZoneSet")
if self.Intel then
self.Intel.acceptzoneset:AddSet(AcceptZoneSet)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER:AddRejectZone(RejectZone)
self:T(self.lid.."AddRejectZone")
if self.Intel then
self.Intel:AddRejectZone(RejectZone)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER:AddRejectZoneSet(RejectZoneSet)
self:T(self.lid.."AddRejectZoneSet")
if self.Intel then
self.Intel.rejectzoneset:AddSet(RejectZoneSet)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER: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
self.Intel:RemoveAcceptZone(AcceptZone)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone)
self:T(self.lid.."RemoveRejectZone")
if self.Intel then
self.Intel:RemoveRejectZone(RejectZone)
else
self:E(self.lid.."*****NO detection has been set up (yet)!")
end
return self
end
function PLAYERTASKCONTROLLER: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:SetMenuName(Name)
self:T(self.lid.."SetMenuName: "..Name)
self.MenuName=Name
return self
end
function PLAYERTASKCONTROLLER:SetParentMenu(Menu)
self:T(self.lid.."SetParentMenu")
return self
end
function PLAYERTASKCONTROLLER:SetupIntel(RecceName)
self:T(self.lid.."SetupIntel")
self.RecceSet=SET_GROUP:New():FilterCoalitions(self.CoalitionName):FilterPrefixes(RecceName):FilterStart()
self.Intel=INTEL:New(self.RecceSet,self.Coalition,self.Name.."-Intel")
self.Intel:SetClusterAnalysis(true,false,false)
self.Intel:SetClusterRadius(self.ClusterRadius or 0.5)
self.Intel.statusupdate=25
self.Intel:SetAcceptZones()
self.Intel:SetRejectZones()
if self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS then
self.Intel:SetDetectStatics(true)
end
self.Intel:__Start(2)
local function NewCluster(Cluster)
if not self.usecluster then return self end
local cluster=Cluster
local type=cluster.ctype
self:T({type,self.Type})
if(type==INTEL.Ctype.AIRCRAFT and self.Type==PLAYERTASKCONTROLLER.Type.A2A)or(type==INTEL.Ctype.NAVAL and(self.Type==PLAYERTASKCONTROLLER.Type.A2S or self.Type==PLAYERTASKCONTROLLER.Type.A2GS))then
self:T("A2A or A2S")
local contacts=cluster.Contacts
local targetset=SET_GROUP:New()
for _,_object in pairs(contacts)do
local contact=_object
self:T("Adding group: "..contact.groupname)
targetset:AddGroup(contact.group,true)
end
self:AddTarget(targetset)
elseif(type==INTEL.Ctype.GROUND or type==INTEL.Ctype.STRUCTURE)and(self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS)then
self:T("A2G")
local contacts=cluster.Contacts
local targetset=nil
if type==INTEL.Ctype.GROUND then
targetset=SET_GROUP:New()
for _,_object in pairs(contacts)do
local contact=_object
self:T("Adding group: "..contact.groupname)
targetset:AddGroup(contact.group,true)
end
elseif type==INTEL.Ctype.STRUCTURE then
targetset=SET_STATIC:New()
for _,_object in pairs(contacts)do
local contact=_object
self:T("Adding static: "..contact.groupname)
targetset:AddStatic(contact.group)
end
end
if targetset then
self:AddTarget(targetset)
end
end
end
local function NewContact(Contact)
if self.usecluster then return self end
local contact=Contact
local type=contact.ctype
self:T({type,self.Type})
if(type==INTEL.Ctype.AIRCRAFT and self.Type==PLAYERTASKCONTROLLER.Type.A2A)or(type==INTEL.Ctype.NAVAL and(self.Type==PLAYERTASKCONTROLLER.Type.A2S or self.Type==PLAYERTASKCONTROLLER.Type.A2GS))then
self:T("A2A or A2S")
self:T("Adding group: "..contact.groupname)
self:AddTarget(contact.group)
elseif(type==INTEL.Ctype.GROUND or type==INTEL.Ctype.STRUCTURE)and(self.Type==PLAYERTASKCONTROLLER.Type.A2G or self.Type==PLAYERTASKCONTROLLER.Type.A2GS)then
self:T("A2G")
self:T("Adding group: "..contact.groupname)
self:AddTarget(contact.group)
end
end
function self.Intel:OnAfterNewCluster(From,Event,To,Cluster)
NewCluster(Cluster)
end
function self.Intel:OnAfterNewContact(From,Event,To,Contact)
NewContact(Contact)
end
return self
end
function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend)
self:T(self.lid.."SetSRS")
self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender=Gender or MSRS.gender or"male"
self.Culture=Culture or MSRS.culture or"en-US"
self.Port=Port or MSRS.port or 5002
self.Voice=Voice or MSRS.voice
self.PathToGoogleKey=PathToGoogleKey
self.AccessKey=AccessKey
self.Volume=Volume or 1.0
self.UseSRS=true
self.Frequency=Frequency or{127,251}
self.BCFrequency=self.Frequency
self.Modulation=Modulation or{radio.modulation.FM,radio.modulation.AM}
self.BCModulation=self.Modulation
self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend)
self.SRS:SetCoalition(self.Coalition)
self.SRS:SetLabel(self.MenuName or self.Name)
self.SRS:SetGender(self.Gender)
self.SRS:SetCulture(self.Culture)
self.SRS:SetPort(self.Port)
self.SRS:SetVolume(self.Volume)
if self.PathToGoogleKey then
self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
end
if(not PathToGoogleKey)and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then
self.PathToGoogleKey=MSRS.poptions.gcloud.credentials
self.Voice=Voice or MSRS.poptions.gcloud.voice
self.AccessKey=AccessKey or MSRS.poptions.gcloud.key
end
if Coordinate then
self.SRS:SetCoordinate(Coordinate)
end
self.SRS:SetVoice(self.Voice)
self.SRSQueue=MSRSQUEUE:New(self.MenuName or self.Name)
self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers)
return self
end
function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation)
self:T(self.lid.."SetSRSBroadcast")
if self.SRS then
self.BCFrequency=Frequency
self.BCModulation=Modulation
end
return self
end
function PLAYERTASKCONTROLLER:onafterStart(From,Event,To)
self:T({From,Event,To})
self:T(self.lid.."onafterStart")
self:_CreateJoinMenuTemplate()
self:_CreateActiveTaskMenuTemplate()
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.UnitLost,self._EventHandler)
self:SetEventPriority(5)
return self
end
function PLAYERTASKCONTROLLER:onafterStatus(From,Event,To)
self:T({From,Event,To})
self:_CheckTargetQueue()
self:_CheckTaskQueue()
self:_CheckPrecisionTasks()
if self.AllowFlash then
self:_FlashInfo()
end
local targetcount=self.TargetQueue:Count()
local taskcount=self.TaskQueue:Count()
local playercount=self.ClientSet:CountAlive()
local assignedtasks=self.TasksPerPlayer:Count()
local enforcedmenu=false
if taskcount~=self.lasttaskcount then
self.lasttaskcount=taskcount
if taskcount<self.menuitemlimit then
enforcedmenu=true
end
end
self:_UpdateJoinMenuTemplate()
if self.verbose then
local text=string.format("%s | New Targets: %02d | Active Tasks: %02d | Active Players: %02d | Assigned Tasks: %02d",self.MenuName,targetcount,taskcount,playercount,assignedtasks)
self:I(text)
end
if self:GetState()~="Stopped"then
self:__Status(-30)
end
return self
end
function PLAYERTASKCONTROLLER:onafterTaskDone(From,Event,To,Task)
self:T({From,Event,To})
self:T(self.lid.."TaskDone")
return self
end
function PLAYERTASKCONTROLLER:onafterTaskCancelled(From,Event,To,Task)
self:T({From,Event,To})
self:T(self.lid.."TaskCancelled")
local canceltxt=self.gettext:GetEntry("TASKCANCELLED",self.locale)
local canceltxttts=self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale)
local taskname=string.format(canceltxt,Task.PlayerTaskNr,tostring(Task.Type))
if not self.NoScreenOutput then
self:_SendMessageToClients(taskname,15)
end
if self.UseSRS then
taskname=string.format(canceltxttts,self.MenuName or self.Name,Task.PlayerTaskNr,tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
local clients=Task:GetClientObjects()
for _,client in pairs(clients)do
self:_RemoveMenuEntriesForTask(Task,client)
end
for _,client in pairs(clients)do
self:_SwitchMenuForClient(client,"Info",5)
end
return self
end
function PLAYERTASKCONTROLLER:onafterTaskSuccess(From,Event,To,Task)
self:T({From,Event,To})
self:T(self.lid.."TaskSuccess")
local succtxt=self.gettext:GetEntry("TASKSUCCESS",self.locale)
local succtxttts=self.gettext:GetEntry("TASKSUCCESSTTS",self.locale)
local taskname=string.format(succtxt,Task.PlayerTaskNr,tostring(Task.Type))
if not self.NoScreenOutput then
self:_SendMessageToClients(taskname,15)
end
if self.UseSRS then
taskname=string.format(succtxttts,self.MenuName or self.Name,Task.PlayerTaskNr,tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
local clients=Task:GetClientObjects()
for _,client in pairs(clients)do
self:_RemoveMenuEntriesForTask(Task,client)
end
for _,client in pairs(clients)do
self:_SwitchMenuForClient(client,"Info",5)
end
return self
end
function PLAYERTASKCONTROLLER:onafterTaskFailed(From,Event,To,Task)
self:T({From,Event,To})
self:T(self.lid.."TaskFailed")
local failtxt=self.gettext:GetEntry("TASKFAILED",self.locale)
local failtxttts=self.gettext:GetEntry("TASKFAILEDTTS",self.locale)
local taskname=string.format(failtxt,Task.PlayerTaskNr,tostring(Task.Type))
if not self.NoScreenOutput then
self:_SendMessageToClients(taskname,15)
end
if self.UseSRS then
taskname=string.format(failtxttts,self.MenuName or self.Name,Task.PlayerTaskNr,tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
local clients=Task:GetClientObjects()
for _,client in pairs(clients)do
self:_RemoveMenuEntriesForTask(Task,client)
end
for _,client in pairs(clients)do
self:_SwitchMenuForClient(client,"Info",5)
end
return self
end
function PLAYERTASKCONTROLLER:onafterTaskRepeatOnFailed(From,Event,To,Task)
self:T({From,Event,To})
self:T(self.lid.."RepeatOnFailed")
local repfailtxt=self.gettext:GetEntry("TASKFAILEDREPLAN",self.locale)
local repfailtxttts=self.gettext:GetEntry("TASKFAILEDREPLANTTS",self.locale)
local taskname=string.format(repfailtxt,Task.PlayerTaskNr,tostring(Task.Type))
if not self.NoScreenOutput then
self:_SendMessageToClients(taskname,15)
end
if self.UseSRS then
taskname=string.format(repfailtxttts,self.MenuName or self.Name,Task.PlayerTaskNr,tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
return self
end
function PLAYERTASKCONTROLLER:onafterTaskAdded(From,Event,To,Task)
self:T({From,Event,To})
self:T(self.lid.."TaskAdded")
local addtxt=self.gettext:GetEntry("TASKADDED",self.locale)
local taskname=string.format(addtxt,self.MenuName or self.Name,tostring(Task.Type))
if not self.NoScreenOutput then
self:_SendMessageToClients(taskname,15)
end
if self.UseSRS then
taskname=string.format(addtxt,self.MenuName or self.Name,tostring(Task.TTSType))
self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2)
end
return self
end
function PLAYERTASKCONTROLLER:onafterStop(From,Event,To)
self:T({From,Event,To})
self:T(self.lid.."Stopped.")
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
return self
end
end
PLAYERRECCE={
ClassName="PLAYERRECCE",
verbose=true,
lid=nil,
version="0.1.26",
ViewZone={},
ViewZoneVisual={},
ViewZoneLaser={},
LaserFOV={},
LaserTarget={},
PlayerSet=nil,
debug=false,
LaserSpots={},
UnitLaserCodes={},
LaserCodes={},
ClientMenus={},
OnStation={},
minthreatlevel=0,
lasingtime=60,
AutoLase={},
AttackSet=nil,
TransmitOnlyWithPlayers=true,
UseController=false,
Controller=nil,
ShortCallsign=true,
Keepnumber=true,
CallsignTranslations=nil,
ReferencePoint=nil,
TForget=600,
TargetCache=nil,
smokeownposition=false,
SmokeOwn={},
smokeaveragetargetpos=true,
reporttostringbullsonly=true,
}
PLAYERRECCE.LaserRelativePos={
["SA342M"]={x=1.7,y=1.2,z=0},
["SA342Mistral"]={x=1.7,y=1.2,z=0},
["SA342Minigun"]={x=1.7,y=1.2,z=0},
["SA342L"]={x=1.7,y=1.2,z=0},
["Ka-50"]={x=6.1,y=-0.85,z=0},
["Ka-50_3"]={x=6.1,y=-0.85,z=0},
["OH58D"]={x=0,y=2.8,z=0},
}
PLAYERRECCE.MaxViewDistance={
["SA342M"]=8000,
["SA342Mistral"]=8000,
["SA342Minigun"]=8000,
["SA342L"]=8000,
["Ka-50"]=8000,
["Ka-50_3"]=8000,
["OH58D"]=8000,
}
PLAYERRECCE.Cameraheight={
["SA342M"]=2.85,
["SA342Mistral"]=2.85,
["SA342Minigun"]=2.85,
["SA342L"]=2.85,
["Ka-50"]=0.5,
["Ka-50_3"]=0.5,
["OH58D"]=4.25,
}
PLAYERRECCE.CanLase={
["SA342M"]=true,
["SA342Mistral"]=true,
["SA342Minigun"]=false,
["SA342L"]=true,
["Ka-50"]=true,
["Ka-50_3"]=true,
["OH58D"]=false,
}
PLAYERRECCE.SmokeColor={
["highsmoke"]=SMOKECOLOR.Orange,
["medsmoke"]=SMOKECOLOR.White,
["lowsmoke"]=SMOKECOLOR.Green,
["lasersmoke"]=SMOKECOLOR.Red,
["ownsmoke"]=SMOKECOLOR.Blue,
}
PLAYERRECCE.FlareColor={
["highflare"]=FLARECOLOR.Yellow,
["medflare"]=FLARECOLOR.White,
["lowflare"]=FLARECOLOR.Green,
["laserflare"]=FLARECOLOR.Red,
["ownflare"]=FLARECOLOR.Green,
}
function PLAYERRECCE:New(Name,Coalition,PlayerSet)
local self=BASE:Inherit(self,FSM:New())
self.Name=Name or"Blue FACA"
self.Coalition=Coalition or coalition.side.BLUE
self.CoalitionName=UTILS.GetCoalitionName(Coalition)
self.PlayerSet=PlayerSet
self.lid=string.format("PlayerForwardController %s %s | ",self.Name,self.version)
self:SetLaserCodes({1688,1130,4785,6547,1465,4578})
self.lasingtime=60
self.minthreatlevel=0
self.reporttostringbullsonly=true
self.TForget=600
self.TargetCache=FIFO:New()
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("*","Status","*")
self:AddTransition("*","RecceOnStation","*")
self:AddTransition("*","RecceOffStation","*")
self:AddTransition("*","TargetDetected","*")
self:AddTransition("*","TargetsSmoked","*")
self:AddTransition("*","TargetsFlared","*")
self:AddTransition("*","Illumination","*")
self:AddTransition("*","TargetLasing","*")
self:AddTransition("*","TargetLOSLost","*")
self:AddTransition("*","TargetReport","*")
self:AddTransition("*","TargetReportSent","*")
self:AddTransition("*","Shack","*")
self:AddTransition("Running","Stop","Stopped")
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:__Start(-1)
local starttime=math.random(5,10)
self:__Status(-starttime)
self:I(self.lid.." Started.")
return self
end
function PLAYERRECCE:_EventHandler(EventData)
self:T(self.lid.."_EventHandler: "..EventData.id)
if EventData.id==EVENTS.PlayerLeaveUnit or EventData.id==EVENTS.Ejection or EventData.id==EVENTS.Crash or EventData.id==EVENTS.PilotDead then
if EventData.IniPlayerName then
self:T(self.lid.."Event for player: "..EventData.IniPlayerName)
if self.ClientMenus[EventData.IniPlayerName]then
self.ClientMenus[EventData.IniPlayerName]:Remove()
end
self.ClientMenus[EventData.IniPlayerName]=nil
self.LaserSpots[EventData.IniPlayerName]=nil
self.OnStation[EventData.IniPlayerName]=false
self.LaserFOV[EventData.IniPlayerName]=nil
self.UnitLaserCodes[EventData.IniPlayerName]=nil
self.LaserTarget[EventData.IniPlayerName]=nil
self.AutoLase[EventData.IniPlayerName]=false
if self.ViewZone[EventData.IniPlayerName]then self.ViewZone[EventData.IniPlayerName]:UndrawZone()end
if self.ViewZoneLaser[EventData.IniPlayerName]then self.ViewZoneLaser[EventData.IniPlayerName]:UndrawZone()end
if self.ViewZoneVisual[EventData.IniPlayerName]then self.ViewZoneVisual[EventData.IniPlayerName]:UndrawZone()end
end
elseif EventData.id==EVENTS.PlayerEnterAircraft and EventData.IniCoalition==self.Coalition then
if EventData.IniPlayerName then
self:T(self.lid.."Event for player: "..EventData.IniPlayerName)
self.UnitLaserCodes[EventData.IniPlayerName]=1688
self.ClientMenus[EventData.IniPlayerName]=nil
self.LaserSpots[EventData.IniPlayerName]=nil
self.OnStation[EventData.IniPlayerName]=false
self.LaserFOV[EventData.IniPlayerName]=nil
self.UnitLaserCodes[EventData.IniPlayerName]=nil
self.LaserTarget[EventData.IniPlayerName]=nil
self.AutoLase[EventData.IniPlayerName]=false
if self.ViewZone[EventData.IniPlayerName]then self.ViewZone[EventData.IniPlayerName]:UndrawZone()end
if self.ViewZoneLaser[EventData.IniPlayerName]then self.ViewZoneLaser[EventData.IniPlayerName]:UndrawZone()end
if self.ViewZoneVisual[EventData.IniPlayerName]then self.ViewZoneVisual[EventData.IniPlayerName]:UndrawZone()end
self:_BuildMenus()
end
end
return self
end
function PLAYERRECCE:_GetClockDirection(unit,target)
self:T(self.lid.." _GetClockDirection")
local _playerPosition=unit:GetCoordinate()
local _targetpostions=target:GetCoordinate()
local _heading=unit:GetHeading()
local DirectionVec3=_playerPosition:GetDirectionVec3(_targetpostions)
local Angle=_playerPosition:GetAngleDegrees(DirectionVec3)
local clock=12
local hours=0
if _heading and Angle then
clock=12
clock=_heading-Angle
hours=(clock/30)*-1
clock=12+hours
clock=UTILS.Round(clock,0)
if clock>12 then clock=clock-12 end
if clock==0 then clock=12 end
end
return clock
end
function PLAYERRECCE:SetLaserCodes(LaserCodes)
self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes}
return self
end
function PLAYERRECCE:SetReferencePoint(Coordinate,Name)
self.ReferencePoint=Coordinate
self.RPName=Name
if self.RPMarker then
self.RPMarker:Remove()
end
local llddm=Coordinate:ToStringLLDDM()
local lldms=Coordinate:ToStringLLDMS()
local mgrs=Coordinate:ToStringMGRS()
local text=string.format("%s RP %s\n%s\n%s\n%s",self.Name,Name,llddm,lldms,mgrs)
self.RPMarker=MARKER:New(Coordinate,text)
self.RPMarker:ReadOnly()
self.RPMarker:ToCoalition(self.Coalition)
return self
end
function PLAYERRECCE:SetPlayerTaskController(Controller)
self.UseController=true
self.Controller=Controller
return self
end
function PLAYERRECCE:SetAttackSet(AttackSet)
self.AttackSet=AttackSet
return self
end
function PLAYERRECCE:_CameraOn(client,playername)
local camera=true
local unit=client
if unit and unit:IsAlive()then
local typename=unit:GetTypeName()
if string.find(typename,"SA342")then
local dcsunit=Unit.getByName(client:GetName())
local vivihorizontal=dcsunit:getDrawArgumentValue(215)or 0
if vivihorizontal<-0.7 or vivihorizontal>0.7 then
camera=false
end
elseif string.find(typename,"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
if unit and unit:IsAlive()then
local dcsunit=Unit.getByName(Gazelle:GetName())
local vivihorizontal=dcsunit:getDrawArgumentValue(215)or 0
local vivivertical=dcsunit:getDrawArgumentValue(216)or 0
local vivioff=false
if vivihorizontal<-0.67 then
vivihorizontal=-0.67
vivioff=false
elseif vivihorizontal>0.67 then
vivihorizontal=0.67
vivioff=true
return 0,0,0,false
end
local horizontalview=vivihorizontal*-180
local verticalview=math.atan(vivivertical)
local heading=unit:GetHeading()
local viviheading=(heading+horizontalview)%360
local maxview=self:_GetActualMaxLOSight(unit,viviheading,verticalview,vivioff)
if maxview>8000 then maxview=8000 end
return viviheading,verticalview,maxview,not vivioff
end
return 0,0,0,false
end
function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading,vnod,vivoff)
self:T(self.lid.."_GetActualMaxLOSight")
if vivoff then return 0 end
local maxview=0
if unit and unit:IsAlive()then
local typename=unit:GetTypeName()
maxview=self.MaxViewDistance[typename]or 8000
local CamHeight=self.Cameraheight[typename]or 1
if vnod<-2 then
local beta=90
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.ceil(math.abs(maxview))
end
function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
if not ShortCallsign or ShortCallsign==false then
self.ShortCallsign=false
else
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)
self:T(self.lid.."_GetViewZone")
local viewzone=nil
if not camon then return nil end
if unit and unit:IsAlive()then
local unitname=unit:GetName()
if not laser then
local startpos=unit:GetCoordinate()
local heading1=(vheading+angle)%360
local heading2=(vheading-angle)%360
local pos1=startpos:Translate(maxview,heading1)
local pos2=startpos:Translate(maxview,heading2)
local array={}
table.insert(array,startpos:GetVec2())
table.insert(array,pos1:GetVec2())
table.insert(array,pos2:GetVec2())
viewzone=ZONE_POLYGON:NewFromPointsArray(unitname,array)
else
local startp=unit:GetCoordinate()
local heading1=(vheading+90)%360
local heading2=(vheading-90)%360
self:T({heading1,heading2})
local startpos=startp:Translate(minview,vheading)
local pos1=startpos:Translate(12.5,heading1)
local pos2=startpos:Translate(12.5,heading2)
local pos3=pos1:Translate(maxview,vheading)
local pos4=pos2:Translate(maxview,vheading)
local array={}
table.insert(array,pos1:GetVec2())
table.insert(array,pos2:GetVec2())
table.insert(array,pos4:GetVec2())
table.insert(array,pos3:GetVec2())
viewzone=ZONE_POLYGON:NewFromPointsArray(unitname,array)
end
end
return viewzone
end
function PLAYERRECCE:_GetKnownTargets(client)
self:T(self.lid.."_GetKnownTargets")
local finaltargets=SET_UNIT:New()
local targets=self.TargetCache:GetDataTable()
local playername=client:GetPlayerName()
for _,_target in pairs(targets)do
local targetdata=_target.PlayerRecceDetected
if targetdata.playername==playername then
finaltargets:Add(_target:GetName(),_target)
end
end
return finaltargets,finaltargets:CountAlive()
end
function PLAYERRECCE:_CleanupTargetCache()
self:T(self.lid.."_CleanupTargetCache")
local cleancache=FIFO:New()
self.TargetCache:ForEach(
function(unit)
local pull=false
if unit and unit:IsAlive()and unit:GetLife()>1 then
if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then
local TNow=timer.getTime()
if TNow-unit.PlayerRecceDetected.timestamp>self.TForget then
pull=true
unit.PlayerRecceDetected=nil
end
else
pull=true
end
else
pull=true
end
if not pull then
cleancache:Push(unit,unit:GetName())
end
end
)
self.TargetCache=nil
self.TargetCache=cleancache
return self
end
function PLAYERRECCE:_GetTargetSet(unit,camera,laser)
self:T(self.lid.."_GetTargetSet")
local finaltargets=SET_UNIT:New()
local finalcount=0
local minview=0
local typename=unit:GetTypeName()
local playername=unit:GetPlayerName()
local maxview=self.MaxViewDistance[typename]or 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 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 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,3000,true
maxview=self.MaxViewDistance[typename]or 3000
angle=45
end
if laser then
if not self.LaserFOV[playername]then
minview=100
maxview=2000
self.LaserFOV[playername]={
min=100,
max=2000,
}
else
minview=self.LaserFOV[playername].min
maxview=self.LaserFOV[playername].max
end
end
local zone=self:_GetViewZone(unit,heading,minview,maxview,angle,camon,laser)
if zone then
local redcoalition="red"
if self.Coalition==coalition.side.RED then
redcoalition="blue"
end
local startpos=unit:GetCoordinate()
local targetset=SET_UNIT:New():FilterCategories("ground"):FilterActive(true):FilterZones({zone}):FilterCoalitions(redcoalition):FilterOnce()
self:T("Prefilter Target Count = "..targetset:CountAlive())
targetset:ForEach(
function(_unit)
local _unit=_unit
local _unitpos=_unit:GetCoordinate()
if startpos:IsLOS(_unitpos)and _unit:IsAlive()and _unit:GetLife()>1 then
self:T("Adding to final targets: ".._unit:GetName())
finaltargets:Add(_unit:GetName(),_unit)
end
end
)
finalcount=finaltargets:CountAlive()
self:T(string.format("%s Unit: %s | Targets in view %s",self.lid,name,finalcount))
end
return finaltargets,finalcount,zone
end
function PLAYERRECCE:_GetHVTTarget(targetset)
self:T(self.lid.."_GetHVTTarget")
local unitsbythreat={}
local minthreat=self.minthreatlevel or 0
for _,_unit in pairs(targetset.Set)do
local unit=_unit
if unit and unit:IsAlive()and unit:GetLife()>1 then
local threat=unit:GetThreatLevel()
if threat>=minthreat then
if unit:HasAttribute("RADAR_BAND1_FOR_ARM")or unit:HasAttribute("RADAR_BAND2_FOR_ARM")or unit:HasAttribute("Optical Tracker")then
threat=11
end
table.insert(unitsbythreat,{unit,threat})
end
end
end
table.sort(unitsbythreat,function(a,b)
local aNum=a[2]
local bNum=b[2]
return aNum>bNum
end)
if unitsbythreat[1]and unitsbythreat[1][1]then
return unitsbythreat[1][1]
else
return nil
end
end
function PLAYERRECCE:_LaseTarget(client,targetset)
self:T(self.lid.."_LaseTarget")
local target=self:_GetHVTTarget(targetset)
local playername=client:GetPlayerName()
local laser=nil
if not self.LaserSpots[playername]then
laser=SPOT:New(client)
if not self.UnitLaserCodes[playername]then
self.UnitLaserCodes[playername]=1688
end
laser.LaserCode=self.UnitLaserCodes[playername]or 1688
self.LaserSpots[playername]=laser
else
laser=self.LaserSpots[playername]
end
if self.LaserTarget[playername]then
local target=self.LaserTarget[playername]
local oldtarget=target:GetObject()
self:T("Targetstate: "..target:GetState())
self:T("Laser State: "..tostring(laser:IsLasing()))
if(not oldtarget)or targetset:IsNotInSet(oldtarget)or target:IsDead()or target:IsDestroyed()then
laser:LaseOff()
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)
end
self.LaserTarget[playername]=nil
oldtarget=nil
self.LaserSpots[playername]=nil
elseif oldtarget and laser and(not laser:IsLasing())then
self:T("Switching laser back on ..")
local lasercode=self.UnitLaserCodes[playername]or laser.LaserCode or 1688
local lasingtime=self.lasingtime or 60
laser:LaseOn(oldtarget,lasercode,lasingtime)
else
self:T("Target alive and laser is on!")
end
elseif(not laser:IsLasing())and target then
local relativecam=self.LaserRelativePos[client:GetTypeName()]
laser:SetRelativeStartPosition(relativecam)
local lasercode=self.UnitLaserCodes[playername]or laser.LaserCode or 1688
local lasingtime=self.lasingtime or 60
laser:LaseOn(target,lasercode,lasingtime)
self.LaserTarget[playername]=TARGET:New(target)
self:__TargetLasing(-1,client,target,lasercode,lasingtime)
end
return self
end
function PLAYERRECCE:_SetClientLaserCode(client,group,playername,code)
self:T(self.lid.."_SetClientLaserCode")
self.UnitLaserCodes[playername]=code or 1688
if self.ClientMenus[playername]then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
self:_BuildMenus()
return self
end
function PLAYERRECCE:_SwitchOnStation(client,group,playername)
self:T(self.lid.."_SwitchOnStation")
if not self.OnStation[playername]then
self.OnStation[playername]=true
self:__RecceOnStation(-1,client,playername)
else
self.OnStation[playername]=false
self:__RecceOffStation(-1,client,playername)
end
if self.ClientMenus[playername]then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
self:_BuildMenus(client)
return self
end
function PLAYERRECCE:_SwitchSmoke(client,group,playername)
self:T(self.lid.."_SwitchLasing")
if not self.SmokeOwn[playername]then
self.SmokeOwn[playername]=true
MESSAGE:New("Smoke self is now ON",10,self.Name or"FACA"):ToClient(client)
else
self.SmokeOwn[playername]=false
MESSAGE:New("Smoke self is now OFF",10,self.Name or"FACA"):ToClient(client)
end
if self.ClientMenus[playername]then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
self:_BuildMenus(client)
return self
end
function PLAYERRECCE:_SwitchLasing(client,group,playername)
self:T(self.lid.."_SwitchLasing")
if not self.AutoLase[playername]then
self.AutoLase[playername]=true
MESSAGE:New("Lasing is now ON",10,self.Name or"FACA"):ToClient(client)
else
self.AutoLase[playername]=false
if self.LaserSpots[playername]then
local laser=self.LaserSpots[playername]
if laser:IsLasing()then
laser:LaseOff()
end
self.LaserSpots[playername]=nil
end
MESSAGE:New("Lasing is now OFF",10,self.Name or"FACA"):ToClient(client)
end
if self.ClientMenus[playername]then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
self:_BuildMenus(client)
return self
end
function PLAYERRECCE:_SwitchLasingDist(client,group,playername,mindist,maxdist)
self:T(self.lid.."_SwitchLasingDist")
local mind=mindist or 100
local maxd=maxdist or 2000
if not self.LaserFOV[playername]then
self.LaserFOV[playername]={
min=mind,
max=maxd,
}
else
self.LaserFOV[playername].min=mind
self.LaserFOV[playername].max=maxd
end
MESSAGE:New(string.format("Laser distance set to %d-%dm!",mindist,maxdist),10,"FACA"):ToClient(client)
if self.ClientMenus[playername]then
self.ClientMenus[playername]:Remove()
self.ClientMenus[playername]=nil
end
self:_BuildMenus(client)
return self
end
function PLAYERRECCE:_WIP(client,group,playername)
self:T(self.lid.."_WIP")
return self
end
function PLAYERRECCE:_SmokeTargets(client,group,playername)
self:T(self.lid.."_SmokeTargets")
local cameraset=self:_GetTargetSet(client,true)
local visualset=self:_GetTargetSet(client,false)
if cameraset:CountAlive()>0 or visualset:CountAlive()>0 then
self:__TargetsSmoked(-1,client,playername,cameraset)
else
return self
end
local highsmoke=self.SmokeColor.highsmoke
local medsmoke=self.SmokeColor.medsmoke
local lowsmoke=self.SmokeColor.lowsmoke
local lasersmoke=self.SmokeColor.lasersmoke
local laser=self.LaserSpots[playername]
if laser and laser.Target and laser.Target:IsAlive()then
laser.Target:GetCoordinate():Smoke(lasersmoke)
end
local coord=visualset:GetCoordinate()
if coord and self.smokeaveragetargetpos then
coord:SetAtLandheight()
coord:Smoke(medsmoke)
else
for _,_unit in pairs(visualset.Set)do
local unit=_unit
if unit and unit:IsAlive()then
local coord=unit:GetCoordinate()
local threat=unit:GetThreatLevel()
if coord then
local color=lowsmoke
if threat>7 then
color=highsmoke
elseif threat>2 then
color=medsmoke
end
coord:Smoke(color)
end
end
end
end
if self.SmokeOwn[playername]then
local cc=client:GetVec2()
local lc=COORDINATE:NewFromVec2(cc,1)
local color=self.SmokeColor.ownsmoke
lc:Smoke(color)
end
return self
end
function PLAYERRECCE:_FlareTargets(client,group,playername)
self:T(self.lid.."_FlareTargets")
local cameraset=self:_GetTargetSet(client,true)
local visualset=self:_GetTargetSet(client,false)
cameraset:AddSet(visualset)
if cameraset:CountAlive()>0 then
self:__TargetsFlared(-1,client,playername,cameraset)
end
local highsmoke=self.FlareColor.highflare
local medsmoke=self.FlareColor.medflare
local lowsmoke=self.FlareColor.lowflare
local lasersmoke=self.FlareColor.laserflare
local laser=self.LaserSpots[playername]
if laser and laser.Target and laser.Target:IsAlive()then
laser.Target:GetCoordinate():Flare(lasersmoke)
if cameraset:IsInSet(laser.Target)then
cameraset:Remove(laser.Target:GetName(),true)
end
end
for _,_unit in pairs(cameraset.Set)do
local unit=_unit
if unit and unit:IsAlive()then
local coord=unit:GetCoordinate()
local threat=unit:GetThreatLevel()
if coord then
local color=lowsmoke
if threat>7 then
color=highsmoke
elseif threat>2 then
color=medsmoke
end
coord:Flare(color)
end
end
end
return self
end
function PLAYERRECCE:_IlluTargets(client,group,playername)
self:T(self.lid.."_IlluTargets")
local totalset,count=self:_GetKnownTargets(client)
if count>0 then
local coord=totalset:GetCoordinate()
coord.y=coord.y+200
coord:IlluminationBomb(nil,1)
self:__Illumination(1,client,playername,totalset)
end
return self
end
function PLAYERRECCE:_UploadTargets(client,group,playername)
self:T(self.lid.."_UploadTargets")
local totalset,count=self:_GetKnownTargets(client)
if count>0 then
self.Controller:AddTarget(totalset)
self:__TargetReportSent(1,client,playername,totalset)
end
return self
end
function PLAYERRECCE:_ReportLaserTargets(client,group,playername)
self:T(self.lid.."_ReportLaserTargets")
local targetset,number=self:_GetTargetSet(client,true,true)
if number>0 and self.AutoLase[playername]then
local Settings=(client and _DATABASE:GetPlayerSettings(playername))or _SETTINGS
local target=self:_GetHVTTarget(targetset)
local ThreatLevel=target:GetThreatLevel()or 1
local ThreatLevelText="high"
if ThreatLevel>3 and ThreatLevel<8 then
ThreatLevelText="medium"
elseif ThreatLevel<=3 then
ThreatLevelText="low"
end
local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel
local report=REPORT:New("Lasing Report")
report:Add(string.rep("-",15))
report:Add("Target type: "..target:GetTypeName()or"unknown")
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
if not self.ReferencePoint then
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
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
report:Add("Laser Code: "..self.UnitLaserCodes[playername]or 1688)
report:Add(string.rep("-",15))
local text=report:Text()
self:__TargetReport(1,client,targetset,target,text)
else
local report=REPORT:New("Lasing Report")
report:Add(string.rep("-",15))
report:Add("N O  T A R G E T S")
report:Add(string.rep("-",15))
local text=report:Text()
self:__TargetReport(1,client,nil,nil,text)
end
return self
end
function PLAYERRECCE:_ReportVisualTargets(client,group,playername)
self:T(self.lid.."_ReportVisualTargets")
local targetset,number=self:_GetKnownTargets(client)
if number>0 then
local Settings=(client and _DATABASE:GetPlayerSettings(playername))or _SETTINGS
local ThreatLevel=targetset:CalculateThreatLevelA2G()
local ThreatLevelText="high"
if ThreatLevel>3 and ThreatLevel<8 then
ThreatLevelText="medium"
elseif ThreatLevel<=3 then
ThreatLevelText="low"
end
local ThreatGraph="["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]: "..ThreatLevel
local report=REPORT:New("Target Report")
report:Add(string.rep("-",15))
report:Add("Target count: "..number)
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
if not self.ReferencePoint then
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
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()
self:__TargetReport(1,client,targetset,nil,text)
else
local report=REPORT:New("Target Report")
report:Add(string.rep("-",15))
report:Add("N O  T A R G E T S")
report:Add(string.rep("-",15))
local text=report:Text()
self:__TargetReport(1,client,nil,nil,text)
end
return self
end
function PLAYERRECCE:_BuildMenus(Client)
self:T(self.lid.."_BuildMenus")
local clients=self.PlayerSet
local clientset=clients:GetSetObjects()
if Client then clientset={Client}end
for _,_client in pairs(clientset)do
local client=_client
if client and client:IsAlive()then
local playername=client:GetPlayerName()
self:T("Menu for "..playername)
if not self.UnitLaserCodes[playername]then
self:_SetClientLaserCode(nil,nil,playername,1688)
end
if self.SmokeOwn[playername]==nil then
self.SmokeOwn[playername]=self.smokeownposition
end
local group=client:GetGroup()
if not self.ClientMenus[playername]then
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"
local text=string.format("Switch On-Station (%s)",txtonstation)
local onstationmenu=MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchOnStation,self,client,group,playername)
if self.OnStation[playername]then
local smoketopmenu=MENU_GROUP:New(group,"Visual Markers",self.ClientMenus[playername])
local smokemenu=MENU_GROUP_COMMAND:New(group,"Smoke Targets",smoketopmenu,self._SmokeTargets,self,client,group,playername)
local flaremenu=MENU_GROUP_COMMAND:New(group,"Flare Targets",smoketopmenu,self._FlareTargets,self,client,group,playername)
local illumenu=MENU_GROUP_COMMAND:New(group,"Illuminate Area",smoketopmenu,self._IlluTargets,self,client,group,playername)
local ownsm=self.SmokeOwn[playername]and"ON"or"OFF"
local owntxt=string.format("Switch smoke self (%s)",ownsm)
local ownsmoke=MENU_GROUP_COMMAND:New(group,owntxt,smoketopmenu,self._SwitchSmoke,self,client,group,playername)
if canlase then
local txtonstation=self.AutoLase[playername]and"ON"or"OFF"
local text=string.format("Switch Lasing (%s)",txtonstation)
local lasemenu=MENU_GROUP_COMMAND:New(group,text,self.ClientMenus[playername],self._SwitchLasing,self,client,group,playername)
local lasedist=MENU_GROUP:New(group,"Set Laser Distance",self.ClientMenus[playername])
local mindist=100
local maxdist=2000
if self.LaserFOV[playername]and self.LaserFOV[playername].max then
maxdist=self.LaserFOV[playername].max
end
local laselist={}
for i=2,8 do
local dist1=(i*1000)-1000
local dist2=i*1000
dist1=dist1==1000 and 100 or dist1
local text=string.format("%d-%dm",dist1,dist2)
if dist2==maxdist then
text=text.." (*)"
end
laselist[i]=MENU_GROUP_COMMAND:New(group,text,lasedist,self._SwitchLasingDist,self,client,group,playername,dist1,dist2)
end
end
local targetmenu=MENU_GROUP:New(group,"Target Report",self.ClientMenus[playername])
if canlase then
local reportL=MENU_GROUP_COMMAND:New(group,"Laser Target",targetmenu,self._ReportLaserTargets,self,client,group,playername)
end
local reportV=MENU_GROUP_COMMAND:New(group,"Visual Targets",targetmenu,self._ReportVisualTargets,self,client,group,playername)
if self.UseController then
local text=string.format("Target Upload to %s",self.Controller.MenuName or self.Controller.Name)
local upload=MENU_GROUP_COMMAND:New(group,text,targetmenu,self._UploadTargets,self,client,group,playername)
end
if canlase then
local lasecodemenu=MENU_GROUP:New(group,"Set Laser Code",self.ClientMenus[playername])
local codemenu={}
for _,_code in pairs(self.LaserCodes)do
if _code==self.UnitLaserCodes[playername]then
_code=tostring(_code).."(*)"
end
codemenu[playername.._code]=MENU_GROUP_COMMAND:New(group,tostring(_code),lasecodemenu,self._SetClientLaserCode,self,client,group,playername,_code)
end
end
end
end
end
end
return self
end
function PLAYERRECCE:_CheckNewTargets(targetset,client,playername)
self:T(self.lid.."_CheckNewTargets")
local tempset=SET_UNIT:New()
targetset:ForEach(
function(unit)
if unit and unit:IsAlive()then
self:T("Report unit: "..unit:GetName())
if not unit.PlayerRecceDetected then
self:T("New unit: "..unit:GetName())
unit.PlayerRecceDetected={
detected=true,
recce=client,
playername=playername,
timestamp=timer.getTime()
}
tempset:Add(unit:GetName(),unit)
if not self.TargetCache:HasUniqueID(unit:GetName())then
self.TargetCache:Push(unit,unit:GetName())
end
end
if unit.PlayerRecceDetected and unit.PlayerRecceDetected.timestamp then
local TNow=timer.getTime()
if TNow-unit.PlayerRecceDetected.timestamp>self.TForget then
unit.PlayerRecceDetected={
detected=true,
recce=client,
playername=playername,
timestamp=timer.getTime()
}
if not self.TargetCache:HasUniqueID(unit:GetName())then
self.TargetCache:Push(unit,unit:GetName())
end
tempset:Add(unit:GetName(),unit)
end
end
end
end
)
local targetsbyclock={}
for i=1,12 do
targetsbyclock[i]={}
end
tempset:ForEach(
function(object)
local obj=object
local clock=self:_GetClockDirection(client,obj)
table.insert(targetsbyclock[clock],obj)
end
)
self:T("Known target Count: "..self.TargetCache:Count())
if tempset:CountAlive()>0 then
self:TargetDetected(targetsbyclock,client,playername)
end
return self
end
function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend)
self:T(self.lid.."SetSRS")
self.PathToSRS=PathToSRS or MSRS.path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender=Gender or MSRS.gender or"male"
self.Culture=Culture or MSRS.culture or"en-US"
self.Port=Port or MSRS.port or 5002
self.Voice=Voice or MSRS.voice
self.PathToGoogleKey=PathToGoogleKey
self.Volume=Volume or 1.0
self.UseSRS=true
self.Frequency=Frequency or{127,251}
self.BCFrequency=self.Frequency
self.Modulation=Modulation or{radio.modulation.FM,radio.modulation.AM}
self.BCModulation=self.Modulation
self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation)
self.SRS:SetCoalition(self.Coalition)
self.SRS:SetLabel(self.MenuName or self.Name)
self.SRS:SetGender(self.Gender)
self.SRS:SetCulture(self.Culture)
self.SRS:SetPort(self.Port)
self.SRS:SetVolume(self.Volume)
if Backend then
self.SRS:SetBackend(Backend)
end
if self.PathToGoogleKey then
self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
end
if(not PathToGoogleKey)and self.SRS:GetProvider()==MSRS.Provider.GOOGLE then
self.PathToGoogleKey=MSRS.poptions.gcloud.credentials
self.Voice=Voice or MSRS.poptions.gcloud.voice
end
self.SRS:SetVoice(self.Voice)
self.SRSQueue=MSRSQUEUE:New(self.MenuName or self.Name)
self.SRSQueue:SetTransmitOnlyWithPlayers(self.TransmitOnlyWithPlayers)
return self
end
function PLAYERRECCE:SetTransmitOnlyWithPlayers(Switch)
self.TransmitOnlyWithPlayers=Switch
if self.SRSQueue then
self.SRSQueue:SetTransmitOnlyWithPlayers(Switch)
end
return self
end
function PLAYERRECCE:SetMenuName(Name)
self:T(self.lid.."SetMenuName: "..Name)
self.MenuName=Name
return self
end
function PLAYERRECCE: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
return self
end
function PLAYERRECCE:EnableSmokeAverageTargetPosition()
self:T(self.lid.."ENableSmokeOwnPosition")
self.smokeaveragetargetpos=true
return self
end
function PLAYERRECCE:DisableSmokeAverageTargetPosition()
self:T(self.lid.."DisableSmokeAverageTargetPosition")
self.smokeaveragetargetpos=false
return self
end
function PLAYERRECCE:_GetTextForSpeech(text)
text=string.gsub(text,"%d","%1 ")
text=string.gsub(text,"^%s*","")
text=string.gsub(text,"%s*$","")
text=string.gsub(text,"0","zero")
text=string.gsub(text,"9","niner")
text=string.gsub(text,"  "," ")
return text
end
function PLAYERRECCE:onafterStatus(From,Event,To)
self:T({From,Event,To})
if not self.timestamp then
self.timestamp=timer.getTime()
else
local tNow=timer.getTime()
if tNow-self.timestamp>=60 then
self:_CleanupTargetCache()
self.timestamp=timer.getTime()
end
end
self:_BuildMenus()
self.PlayerSet:ForEachClient(
function(Client)
local client=Client
local playername=client:GetPlayerName()
local cameraison=self:_CameraOn(client,playername)
if client and client:IsAlive()and self.OnStation[playername]then
local targetset,targetcount,tzone=nil,0,nil
local laserset,lzone=nil,nil
local vistargetset,vistargetcount,viszone=nil,0,nil
if cameraison then
targetset,targetcount,tzone=self:_GetTargetSet(client,true)
if targetset then
if self.ViewZone[playername]then
self.ViewZone[playername]:UndrawZone()
end
if self.debug and tzone then
self.ViewZone[playername]=tzone:DrawZone(self.Coalition,{0,0,1},nil,nil,nil,1)
end
end
self:T({targetcount=targetcount})
end
if self.AutoLase[playername]and cameraison then
laserset,targetcount,lzone=self:_GetTargetSet(client,true,true)
if targetcount>0 or self.LaserTarget[playername]then
if self.CanLase[client:GetTypeName()]then
self:_LaseTarget(client,laserset)
end
end
if lzone then
if self.ViewZoneLaser[playername]then
self.ViewZoneLaser[playername]:UndrawZone()
end
if self.debug and tzone then
self.ViewZoneLaser[playername]=lzone:DrawZone(self.Coalition,{0,1,0},nil,nil,nil,1)
end
end
self:T({lasercount=targetcount})
end
vistargetset,vistargetcount,viszone=self:_GetTargetSet(client,false)
if vistargetset then
if self.ViewZoneVisual[playername]then
self.ViewZoneVisual[playername]:UndrawZone()
end
if self.debug and viszone then
self.ViewZoneVisual[playername]=viszone:DrawZone(self.Coalition,{1,0,0},nil,nil,nil,3)
end
end
self:T({visualtargetcount=vistargetcount})
if targetset then
vistargetset:AddSet(targetset)
end
if laserset then
vistargetset:AddSet(laserset)
end
if not cameraison and self.debug then
if self.ViewZoneLaser[playername]then
self.ViewZoneLaser[playername]:UndrawZone()
end
if self.ViewZone[playername]then
self.ViewZone[playername]:UndrawZone()
end
end
self:_CheckNewTargets(vistargetset,client,playername)
end
end
)
self:__Status(-10)
return self
end
function PLAYERRECCE:onafterRecceOnStation(From,Event,To,Client,Playername)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.ReferencePoint then
local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings)
end
local text1="Party time!"
local text2=string.format("All stations, FACA %s on station\nat %s!",callsign,coordtext)
local text2tts=string.format(" All stations, FACA %s on station at %s!",callsign,coordtext)
text2tts=self:_GetTextForSpeech(text2tts)
if self.debug then
self:T(text2.."\n"..text2tts)
end
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2)
self.SRSQueue:NewTransmission(text2tts,nil,self.SRS,nil,3)
MESSAGE:New(text2,10,self.Name or"FACA"):ToCoalition(self.Coalition)
else
MESSAGE:New(text1,10,self.Name or"FACA"):ToClient(Client)
MESSAGE:New(text2,10,self.Name or"FACA"):ToCoalition(self.Coalition)
end
return self
end
function PLAYERRECCE:onafterRecceOffStation(From,Event,To,Client,Playername)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.ReferencePoint then
local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings)
end
local text=string.format("All stations, FACA %s leaving station\nat %s, good bye!",callsign,coordtext)
local texttts=string.format("All stations, FACA %s leaving station at %s, good bye!",callsign,coordtext)
texttts=self:_GetTextForSpeech(texttts)
if self.debug then
self:T(text.."\n"..texttts)
end
local text1="Going home!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(text1,nil,self.SRS,nil,2)
self.SRSQueue:NewTransmission(texttts,nil,self.SRS,nil,3)
MESSAGE:New(text,10,self.Name or"FACA"):ToCoalition(self.Coalition)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToCoalition(self.Coalition)
end
return self
end
function PLAYERRECCE:onafterTargetDetected(From,Event,To,Targetsbyclock,Client,Playername)
self:T({From,Event,To})
local dunits="meters"
local Settings=Client and _DATABASE:GetPlayerSettings(Playername)or _SETTINGS
local clientcoord=Client:GetCoordinate()
for i=1,12 do
local targets=Targetsbyclock[i]
local targetno=#targets
if targetno==1 then
local targetdistance=clientcoord:Get2DDistance(targets[1]:GetCoordinate())or 100
local Threatlvl=targets[1]:GetThreatLevel()
local ThreatTxt="Low"
if Threatlvl>=7 then
ThreatTxt="Medium"
elseif Threatlvl>=3 then
ThreatTxt="High"
end
if Settings:IsMetric()then
targetdistance=UTILS.Round(targetdistance,-2)
if targetdistance>=1000 then
targetdistance=UTILS.Round(targetdistance/1000,0)
dunits="kilometer"
end
else
if UTILS.MetersToNM(targetdistance)>=1 then
targetdistance=UTILS.Round(UTILS.MetersToNM(targetdistance),0)
dunits="miles"
else
targetdistance=UTILS.Round(UTILS.MetersToFeet(targetdistance),-2)
dunits="feet"
end
end
local text=string.format("Target! %s! %s o\'clock, %d %s!",ThreatTxt,i,targetdistance,dunits)
local ttstext=string.format("Target! %s! %s oh clock, %d %s!",ThreatTxt,i,targetdistance,dunits)
if self.UseSRS then
local grp=Client:GetGroup()
if clientcoord then
self.SRS:SetCoordinate(clientcoord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
elseif targetno>1 then
local function GetNearest(TTable)
local distance=10000000
for _,_unit in pairs(TTable)do
local dist=clientcoord:Get2DDistance(_unit:GetCoordinate())or 100
if dist<distance then
distance=dist
end
end
return distance
end
local targetdistance=GetNearest(targets)
if Settings:IsMetric()then
targetdistance=UTILS.Round(targetdistance,-2)
if targetdistance>=1000 then
targetdistance=UTILS.Round(targetdistance/1000,0)
dunits="kilometer"
end
else
if UTILS.MetersToNM(targetdistance)>=1 then
targetdistance=UTILS.Round(UTILS.MetersToNM(targetdistance),0)
dunits="miles"
else
targetdistance=UTILS.Round(UTILS.MetersToFeet(targetdistance),-2)
dunits="feet"
end
end
local text=string.format(" %d targets! %s o\'clock, %d %s!",targetno,i,targetdistance,dunits)
local ttstext=string.format("%d targets! %s oh clock, %d %s!",targetno,i,targetdistance,dunits)
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
end
end
return self
end
function PLAYERRECCE:onafterIllumination(From,Event,To,Client,Playername,TargetSet)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.AttackSet then
for _,_client in pairs(self.AttackSet.Set)do
local client=_client
if client and client:IsAlive()then
local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS
local coordtext=coord:ToStringA2G(client,Settings)
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)
end
local text=string.format("All stations, %s fired illumination\nat %s!",callsign,coordtext)
MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client)
end
end
end
local text="Sunshine!"
local ttstext="Sunshine!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
return self
end
function PLAYERRECCE:onafterTargetsSmoked(From,Event,To,Client,Playername,TargetSet)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.AttackSet then
for _,_client in pairs(self.AttackSet.Set)do
local client=_client
if client and client:IsAlive()then
local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS
local coordtext=coord:ToStringA2G(client,Settings)
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)
end
local text=string.format("All stations, %s smoked targets\nat %s!",callsign,coordtext)
MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client)
end
end
end
local text="Smoke on!"
local ttstext="Smoke and Mirrors!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
return self
end
function PLAYERRECCE:onafterTargetsFlared(From,Event,To,Client,Playername,TargetSet)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
local coord=Client:GetCoordinate()
local coordtext=coord:ToStringBULLS(self.Coalition)
if self.AttackSet then
for _,_client in pairs(self.AttackSet.Set)do
local client=_client
if client and client:IsAlive()then
local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)
end
local coordtext=coord:ToStringA2G(client,Settings)
local text=string.format("All stations, %s flared targets\nat %s!",callsign,coordtext)
MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client)
end
end
end
local text="Fireworks!"
local ttstext="Fire works!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
return self
end
function PLAYERRECCE:onafterTargetLasing(From,Event,To,Client,Target,Lasercode,Lasingtime)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,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)
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings)
end
local targettype=Target:GetTypeName()
if self.AttackSet then
for _,_client in pairs(self.AttackSet.Set)do
local client=_client
if client and client:IsAlive()then
local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)
end
local coordtext=coord:ToStringA2G(client,Settings)
local text=string.format("All stations, %s lasing %s\nat %s!\nCode %d, Duration %d plus seconds!",callsign,targettype,coordtext,Lasercode,Lasingtime)
MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client)
end
end
end
local text="Lasing!"
local ttstext="Laser on!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
return self
end
function PLAYERRECCE:onafterShack(From,Event,To,Client,Target)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,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)
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings)
end
local targettype="target"
if self.AttackSet then
for _,_client in pairs(self.AttackSet.Set)do
local client=_client
if client and client:IsAlive()then
local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)
end
local coordtext=coord:ToStringA2G(client,Settings)
local text=string.format("All stations, %s good hit on %s\nat %s!",callsign,targettype,coordtext)
MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client)
end
end
end
local text="Shack!"
local ttstext="Shack!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
return self
end
function PLAYERRECCE:onafterTargetLOSLost(From,Event,To,Client,Target)
self:T({From,Event,To})
local callsign=Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,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)
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,Client,Settings)
end
local targettype="target"
if self.AttackSet then
for _,_client in pairs(self.AttackSet.Set)do
local client=_client
if client and client:IsAlive()then
local Settings=client and _DATABASE:GetPlayerSettings(client:GetPlayerName())or _SETTINGS
if self.ReferencePoint then
coordtext=coord:ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)
end
local coordtext=coord:ToStringA2G(client,Settings)
local text=string.format("All stations, %s lost sight of %s\nat %s!",callsign,targettype,coordtext)
MESSAGE:New(text,15,self.Name or"FACA"):ToClient(client)
end
end
end
local text="Lost LOS!"
local ttstext="Lost L O S!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
return self
end
function PLAYERRECCE:onafterTargetReport(From,Event,To,Client,TargetSet,Target,Text)
self:T({From,Event,To})
MESSAGE:New(Text,45,self.Name or"FACA"):ToClient(Client)
if self.AttackSet then
for _,_client in pairs(self.AttackSet.Set)do
local client=_client
if client and client:IsAlive()and client~=Client then
MESSAGE:New(Text,45,self.Name or"FACA"):ToClient(client)
end
end
end
return self
end
function PLAYERRECCE:onafterTargetReportSent(From,Event,To,Client,Playername,TargetSet)
self:T({From,Event,To})
local text="Upload completed!"
if self.UseSRS then
local grp=Client:GetGroup()
local coord=grp:GetCoordinate()
if coord then
self.SRS:SetCoordinate(coord)
end
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,1,{grp},text,10)
else
MESSAGE:New(text,10,self.Name or"FACA"):ToClient(Client)
end
return self
end
function PLAYERRECCE:onafterStop(From,Event,To)
self:I({From,Event,To})
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
return self
end
SQUADRON={
ClassName="SQUADRON",
verbose=0,
modex=nil,
modexcounter=0,
callsignName=nil,
callsigncounter=11,
tankerSystem=nil,
refuelSystem=nil,
}
SQUADRON.version="0.8.1"
function SQUADRON:New(TemplateGroupName,Ngroups,SquadronName)
local self=BASE:Inherit(self,COHORT:New(TemplateGroupName,Ngroups,SquadronName))
self:AddMissionCapability(AUFTRAG.Type.ORBIT)
self.isAir=true
self.refuelSystem=select(2,self.templategroup:GetUnit(1):IsRefuelable())
self.tankerSystem=select(2,self.templategroup:GetUnit(1):IsTanker())
return self
end
function SQUADRON:SetGrouping(nunits)
self.ngrouping=nunits or 2
if self.ngrouping<1 then self.ngrouping=1 end
if self.ngrouping>4 then self.ngrouping=4 end
return self
end
function SQUADRON:SetParkingIDs(ParkingIDs)
if type(ParkingIDs)~="table"then
ParkingIDs={ParkingIDs}
end
self.parkingIDs=ParkingIDs
return self
end
function SQUADRON:SetTakeoffType(TakeoffType)
TakeoffType=TakeoffType or"Cold"
if TakeoffType:lower()=="hot"then
self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot
elseif TakeoffType:lower()=="cold"then
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
elseif TakeoffType:lower()=="air"then
self.takeoffType=COORDINATE.WaypointType.TurningPoint
else
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
end
return self
end
function SQUADRON:SetTakeoffCold()
self:SetTakeoffType("Cold")
return self
end
function SQUADRON:SetTakeoffHot()
self:SetTakeoffType("Hot")
return self
end
function SQUADRON:SetTakeoffAir()
self:SetTakeoffType("Air")
return self
end
function SQUADRON:SetDespawnAfterLanding(Switch)
if Switch then
self.despawnAfterLanding=Switch
else
self.despawnAfterLanding=true
end
return self
end
function SQUADRON:SetDespawnAfterHolding(Switch)
if Switch then
self.despawnAfterHolding=Switch
else
self.despawnAfterHolding=true
end
return self
end
function SQUADRON:SetFuelLowThreshold(LowFuel)
self.fuellow=LowFuel or 25
return self
end
function SQUADRON:SetFuelLowRefuel(switch)
if switch==false then
self.fuellowRefuel=false
else
self.fuellowRefuel=true
end
return self
end
function SQUADRON:SetAirwing(Airwing)
self.legion=Airwing
return self
end
function SQUADRON:GetAirwing(Airwing)
return self.legion
end
function SQUADRON:onafterStart(From,Event,To)
local text=string.format("Starting SQUADRON",self.name)
self:T(self.lid..text)
self:__Status(-1)
end
function SQUADRON:onafterStatus(From,Event,To)
if self.verbose>=1 then
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName)or"N/A"
local modex=self.modex and self.modex or-1
local skill=self.skill and tostring(self.skill)or"N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0;local NassetsP=0;local NassetsQ=0
if self.legion then
NassetsQP,NassetsP,NassetsQ=self.legion:CountAssetsOnMission(nil,self)
end
local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate,self.aircrafttype,callsign,modex,skill,NassetsTot,NassetsInS,NassetsQP,NassetsP,NassetsQ)
self:I(self.lid..text)
self:_CheckAssetStatus()
end
if not self:IsStopped()then
self:__Status(-60)
end
end
TARGET={
ClassName="TARGET",
verbose=0,
lid=nil,
targets={},
targetcounter=0,
life=0,
life0=0,
N0=0,
Ntargets0=0,
Ndestroyed=0,
Ndead=0,
elements={},
casualties={},
threatlevel0=0,
conditionStart={},
TStatus=30,
}
TARGET.ObjectType={
GROUP="Group",
UNIT="Unit",
STATIC="Static",
SCENERY="Scenery",
COORDINATE="Coordinate",
AIRBASE="Airbase",
ZONE="Zone",
OPSZONE="OpsZone"
}
TARGET.Category={
AIRCRAFT="Aircraft",
GROUND="Ground",
NAVAL="Naval",
AIRBASE="Airbase",
COORDINATE="Coordinate",
ZONE="Zone",
}
TARGET.ObjectStatus={
ALIVE="Alive",
DEAD="Dead",
DAMAGED="Damaged",
}
_TARGETID=0
TARGET.version="0.7.1"
function TARGET:New(TargetObject)
local self=BASE:Inherit(self,FSM:New())
_TARGETID=_TARGETID+1
self.uid=_TARGETID
if TargetObject then
self:AddObject(TargetObject)
end
self:SetPriority()
self:SetImportance()
self.TStatus=30
self.lid=string.format("TARGET #%03d | ",_TARGETID)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Alive")
self:AddTransition("*","Status","*")
self:AddTransition("*","Stop","Stopped")
self:AddTransition("*","ObjectDamaged","*")
self:AddTransition("*","ObjectDestroyed","*")
self:AddTransition("*","ObjectDead","*")
self:AddTransition("*","Damaged","Damaged")
self:AddTransition("*","Destroyed","Dead")
self:AddTransition("*","Dead","Dead")
self:__Start(-0.1)
return self
end
function TARGET:AddObject(Object)
if Object:IsInstanceOf("SET_GROUP")or
Object:IsInstanceOf("SET_UNIT")or
Object:IsInstanceOf("SET_STATIC")or
Object:IsInstanceOf("SET_SCENERY")or
Object:IsInstanceOf("SET_OPSGROUP")or
Object:IsInstanceOf("SET_OPSZONE")then
local set=Object
for _,object in pairs(set.Set)do
self:AddObject(object)
end
elseif Object:IsInstanceOf("SET_ZONE")then
local set=Object
set:SortByName()
for index,ZoneName in pairs(set.Index)do
local zone=set.Set[ZoneName]
self:_AddObject(zone)
end
else
if Object:IsInstanceOf("OPSGROUP")then
self:_AddObject(Object:GetGroup())
else
self:_AddObject(Object)
end
end
return self
end
function TARGET:SetPriority(Priority)
self.prio=Priority or 50
return self
end
function TARGET:SetImportance(Importance)
self.importance=Importance
return self
end
function TARGET:AddConditionStart(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionStart,condition)
return self
end
function TARGET:AddConditionStop(ConditionFunction,...)
local condition={}
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionStop,condition)
return self
end
function TARGET:EvalConditionsAll(Conditions)
for _,_condition in pairs(Conditions or{})do
local condition=_condition
local istrue=condition.func(unpack(condition.arg))
if not istrue then
return false
end
end
return true
end
function TARGET:EvalConditionsAny(Conditions)
for _,_condition in pairs(Conditions or{})do
local condition=_condition
local istrue=condition.func(unpack(condition.arg))
if istrue then
return true
end
end
return false
end
function TARGET:AddResource(MissionType,Nmin,Nmax,Attributes,Properties)
if Attributes and type(Attributes)~="table"then
Attributes={Attributes}
end
if Properties and type(Properties)~="table"then
Properties={Properties}
end
local resource={}
resource.MissionType=MissionType
resource.Nmin=Nmin or 1
resource.Nmax=Nmax or 1
resource.Attributes=Attributes or{}
resource.Properties=Properties or{}
self.resources=self.resources or{}
table.insert(self.resources,resource)
if self.verbose>10 then
local text="Resource:"
for _,_r in pairs(self.resources)do
local r=_r
text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s",r.MissionType,r.Nmin,r.Nmax,tostring(r.Attributes[1]),tostring(r.Properties[1]))
end
self:I(self.lid..text)
end
return resource
end
function TARGET:IsAlive()
for _,_target in pairs(self.targets)do
local target=_target
if target.Status~=TARGET.ObjectStatus.DEAD then
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
return false
end
function TARGET:IsDestroyed()
return self.isDestroyed
end
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")
self:T(self.lid..text)
self:HandleEvent(EVENTS.Dead,self.OnEventUnitDeadOrLost)
self:HandleEvent(EVENTS.UnitLost,self.OnEventUnitDeadOrLost)
self:HandleEvent(EVENTS.RemoveUnit,self.OnEventUnitDeadOrLost)
self:__Status(-1)
return self
end
function TARGET:onafterStatus(From,Event,To)
local fsmstate=self:GetState()
local damaged=false
for i,_target in pairs(self.targets)do
local target=_target
local life=target.Life
target.Life=self:GetTargetLife(target)
if target.Life>target.Life0 then
local delta=2*(target.Life-target.Life0)
target.Life0=target.Life0+delta
life=target.Life0
self.life0=self.life0+delta
end
if target.Life<life then
self:ObjectDamaged(target)
damaged=true
end
if target.Life<1 and target.Status~=TARGET.ObjectStatus.DEAD then
self:E(self.lid..string.format("FF life is zero but no object dead event fired ==> object dead now for target object %s!",tostring(target.Name)))
self:ObjectDead(target)
damaged=true
end
end
if damaged then
self:Damaged()
end
if self.verbose>=1 then
local text=string.format("%s: Targets=%d/%d [%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!"
elseif damaged then
text=text.." - Damaged!"
end
self:I(self.lid..text)
end
if self.verbose>=2 then
local text="Target:"
for i,_target in pairs(self.targets)do
local target=_target
local damage=(1-target.Life/target.Life0)*100
text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f, 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: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
function TARGET:onafterObjectDamaged(From,Event,To,Target)
self:T({From,Event,To})
self:T(self.lid..string.format("Object %s damaged",Target.Name))
return self
end
function TARGET:onafterObjectDestroyed(From,Event,To,Target)
self:T({From,Event,To})
self:T(self.lid..string.format("Object %s destroyed",Target.Name))
self.Ndestroyed=self.Ndestroyed+1
Target.Ndestroyed=Target.Ndestroyed+1
Target.Life=0
self:ObjectDead(Target)
return self
end
function TARGET:onafterObjectDead(From,Event,To,Target)
self:T({From,Event,To})
self:T(self.lid..string.format("Object %s dead",Target.Name))
Target.Status=TARGET.ObjectStatus.DEAD
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
if self.Ndestroyed==self.Ntargets0 then
self.isDestroyed=true
self:Destroyed()
else
self:Dead()
end
else
self:Damaged()
end
return self
end
function TARGET:onafterDamaged(From,Event,To)
self:T({From,Event,To})
self:T(self.lid..string.format("TARGET damaged"))
return self
end
function TARGET:onafterDestroyed(From,Event,To)
self:T({From,Event,To})
self:T(self.lid..string.format("TARGET destroyed"))
self:Dead()
return self
end
function TARGET:onafterDead(From,Event,To)
self:T({From,Event,To})
self:T(self.lid..string.format("TARGET dead"))
return self
end
function TARGET:OnEventUnitDeadOrLost(EventData)
local Name=EventData and EventData.IniUnitName or nil
if self:IsElement(Name)and not self:IsCasualty(Name)then
self:T(self.lid..string.format("EVENT ID=%d: Unit %s dead or lost!",EventData.id,tostring(Name)))
table.insert(self.casualties,Name)
local target=self:GetTargetByName(EventData.IniGroupName)
if not target then
target=self:GetTargetByName(EventData.IniUnitName)
end
if target then
local Ndead=target.Ndead
local Ndestroyed=target.Ndestroyed
if EventData.id==EVENTS.RemoveUnit then
Ndead=Ndead+1
else
Ndestroyed=Ndestroyed+1
Ndead=Ndead+1
end
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)
else
self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead",EventData.id,tostring(target.Name)))
target.Life=0
self:ObjectDead(target)
end
end
end
end
return self
end
function TARGET:_AddObject(Object)
local target={}
target.N0=0
target.Ndead=0
target.Ndestroyed=0
if Object:IsInstanceOf("GROUP")then
local group=Object
target.Type=TARGET.ObjectType.GROUP
target.Name=group:GetName()
target.Coordinate=group:GetCoordinate()
local units=group:GetUnits()
target.Life=0;target.Life0=0
for _,_unit in pairs(units or{})do
local unit=_unit
local life=unit:GetLife()
target.Life=target.Life+life
target.Life0=target.Life0+math.max(unit:GetLife0(),life)
self.threatlevel0=self.threatlevel0+unit:GetThreatLevel()
table.insert(self.elements,unit:GetName())
target.N0=target.N0+1
end
elseif Object:IsInstanceOf("UNIT")then
local unit=Object
target.Type=TARGET.ObjectType.UNIT
target.Name=unit:GetName()
target.Coordinate=unit:GetCoordinate()
if unit then
target.Life=unit:GetLife()
target.Life0=math.max(unit:GetLife0(),target.Life)
self.threatlevel0=self.threatlevel0+unit:GetThreatLevel()
table.insert(self.elements,unit:GetName())
target.N0=target.N0+1
end
elseif Object:IsInstanceOf("STATIC")then
local static=Object
target.Type=TARGET.ObjectType.STATIC
target.Name=static:GetName()
target.Coordinate=static:GetCoordinate()
if static and static:IsAlive()then
target.Life0=static:GetLife0()
target.Life=static:GetLife()
target.N0=target.N0+1
table.insert(self.elements,target.Name)
end
elseif Object:IsInstanceOf("SCENERY")then
local scenery=Object
target.Type=TARGET.ObjectType.SCENERY
target.Name=scenery:GetName()
target.Coordinate=scenery:GetCoordinate()
target.Life0=scenery:GetLife0()
if target.Life0==0 then target.Life0=1 end
target.Life=scenery:GetLife()
target.N0=target.N0+1
table.insert(self.elements,target.Name)
elseif Object:IsInstanceOf("AIRBASE")then
local airbase=Object
target.Type=TARGET.ObjectType.AIRBASE
target.Name=airbase:GetName()
target.Coordinate=airbase:GetCoordinate()
target.Life0=1
target.Life=1
target.N0=target.N0+1
table.insert(self.elements,target.Name)
elseif Object:IsInstanceOf("COORDINATE")then
local coord=UTILS.DeepCopy(Object)
target.Type=TARGET.ObjectType.COORDINATE
target.Name=coord:ToStringMGRS()
target.Coordinate=coord
target.Life0=1
target.Life=1
target.N0=target.N0+1
elseif Object:IsInstanceOf("ZONE_BASE")then
local zone=Object
Object=zone
target.Type=TARGET.ObjectType.ZONE
target.Name=zone:GetName()
target.Coordinate=zone:GetCoordinate()
target.Life0=1
target.Life=1
target.N0=target.N0+1
elseif Object:IsInstanceOf("OPSZONE")then
local zone=Object
Object=zone
target.Type=TARGET.ObjectType.OPSZONE
target.Name=zone:GetName()
target.Coordinate=zone:GetCoordinate()
target.N0=target.N0+1
target.Life0=1
target.Life=1
else
self:E(self.lid.."ERROR: Unknown object type!")
return nil
end
self.life=self.life+target.Life
self.life0=self.life0+target.Life0
self.N0=self.N0+target.N0
self.Ntargets0=self.Ntargets0+1
self.targetcounter=self.targetcounter+1
target.ID=self.targetcounter
target.Status=TARGET.ObjectStatus.ALIVE
target.Object=Object
table.insert(self.targets,target)
if self.name==nil then
self.name=self:GetTargetName(target)
end
if self.category==nil then
self.category=self:GetTargetCategory(target)
end
return self
end
function TARGET:GetLife0()
return self.life0
end
function TARGET:GetDamage()
local life=self:GetLife()/self:GetLife0()
local damage=1-life
return damage*100
end
function TARGET:GetTargetLife(Target)
if Target.Type==TARGET.ObjectType.GROUP then
if Target.Object and Target.Object:IsAlive()then
local units=Target.Object:GetUnits()
local life=0
for _,_unit in pairs(units or{})do
local unit=_unit
life=life+unit:GetLife()
end
return life
else
return 0
end
elseif Target.Type==TARGET.ObjectType.UNIT then
local unit=Target.Object
if unit and unit:IsAlive()then
local life=unit:GetLife()
return life
else
return 0
end
elseif Target.Type==TARGET.ObjectType.STATIC then
if Target.Object and Target.Object:IsAlive()then
local life=Target.Object:GetLife()
return life
else
return 0
end
elseif Target.Type==TARGET.ObjectType.SCENERY then
if Target.Object and Target.Object:IsAlive(25)then
local life=Target.Object:GetLife()
return life
else
return 0
end
elseif Target.Type==TARGET.ObjectType.AIRBASE then
if Target.Status==TARGET.ObjectStatus.ALIVE then
return 1
else
return 0
end
elseif Target.Type==TARGET.ObjectType.COORDINATE then
return 1
elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then
return 1
else
self:E("ERROR: unknown target object type in GetTargetLife!")
end
return self
end
function TARGET:GetLife()
local N=0
for _,_target in pairs(self.targets)do
local Target=_target
N=N+self:GetTargetLife(Target)
end
return N
end
function TARGET:GetTargetThreatLevelMax(Target)
if Target.Type==TARGET.ObjectType.GROUP then
local group=Target.Object
if group and group:IsAlive()then
local tl=group:GetThreatLevel()
return tl
else
return 0
end
elseif Target.Type==TARGET.ObjectType.UNIT then
local unit=Target.Object
if unit and unit:IsAlive()then
local life=unit:GetThreatLevel()
return life
else
return 0
end
elseif Target.Type==TARGET.ObjectType.STATIC then
return 0
elseif Target.Type==TARGET.ObjectType.SCENERY then
return 0
elseif Target.Type==TARGET.ObjectType.AIRBASE then
return 0
elseif Target.Type==TARGET.ObjectType.COORDINATE then
return 0
elseif Target.Type==TARGET.ObjectType.ZONE then
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
function TARGET:GetThreatLevelMax()
local N=0
for _,_target in pairs(self.targets)do
local Target=_target
local n=self:GetTargetThreatLevelMax(Target)
if n>N then
N=n
end
end
return N
end
function TARGET:GetTargetVec2(Target)
local vec3=self:GetTargetVec3(Target)
if vec3 then
return{x=vec3.x,y=vec3.z}
end
return nil
end
function TARGET:GetTargetVec3(Target,Average)
if Target.Type==TARGET.ObjectType.GROUP then
local object=Target.Object
if object and object:IsAlive()then
local vec3=object:GetVec3()
if Average then
vec3=object:GetAverageVec3()
end
if vec3 then
return vec3
else
return nil
end
else
return nil
end
elseif Target.Type==TARGET.ObjectType.UNIT then
local object=Target.Object
if object and object:IsAlive()then
local vec3=object:GetVec3()
return vec3
else
return nil
end
elseif Target.Type==TARGET.ObjectType.STATIC then
local object=Target.Object
if object and object:IsAlive()then
local vec3=object:GetVec3()
return vec3
else
return nil
end
elseif Target.Type==TARGET.ObjectType.SCENERY then
local object=Target.Object
if object then
local vec3=object:GetVec3()
return vec3
else
return nil
end
elseif Target.Type==TARGET.ObjectType.AIRBASE then
local object=Target.Object
local vec3=object:GetVec3()
return vec3
elseif Target.Type==TARGET.ObjectType.COORDINATE then
local object=Target.Object
local vec3={x=object.x,y=object.y,z=object.z}
return vec3
elseif Target.Type==TARGET.ObjectType.ZONE then
local object=Target.Object
local vec3=object:GetVec3()
return vec3
elseif Target.Type==TARGET.ObjectType.OPSZONE then
local object=Target.Object
local vec3=object:GetZone():GetVec3()
return vec3
end
self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3")
end
function TARGET:GetTargetHeading(Target)
if Target.Type==TARGET.ObjectType.GROUP then
local object=Target.Object
if object and object:IsAlive()then
local heading=object:GetHeading()
if heading then
return heading
else
return nil
end
else
return nil
end
elseif Target.Type==TARGET.ObjectType.UNIT then
local object=Target.Object
if object and object:IsAlive()then
local heading=object:GetHeading()
return heading
else
return nil
end
elseif Target.Type==TARGET.ObjectType.STATIC then
local object=Target.Object
if object and object:IsAlive()then
local heading=object:GetHeading()
return heading
else
return nil
end
elseif Target.Type==TARGET.ObjectType.SCENERY then
local object=Target.Object
if object then
local heading=object:GetHeading()
return heading
else
return nil
end
elseif Target.Type==TARGET.ObjectType.AIRBASE then
local object=Target.Object
return 0
elseif Target.Type==TARGET.ObjectType.COORDINATE then
local object=Target.Object
return 0
elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then
local object=Target.Object
return 0
end
self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get heading")
end
function TARGET:GetTargetCoordinate(Target,Average)
if Target.Type==TARGET.ObjectType.COORDINATE then
return Target.Object
else
local vec3=self:GetTargetVec3(Target,Average)
if vec3 then
Target.Coordinate.x=vec3.x
Target.Coordinate.y=vec3.y
Target.Coordinate.z=vec3.z
end
return Target.Coordinate
end
return nil
end
function TARGET:GetTargetName(Target)
if Target.Type==TARGET.ObjectType.GROUP then
if Target.Object and Target.Object:IsAlive()then
return Target.Object:GetName()
end
elseif Target.Type==TARGET.ObjectType.UNIT then
if Target.Object and Target.Object:IsAlive()then
return Target.Object:GetName()
end
elseif Target.Type==TARGET.ObjectType.STATIC then
if Target.Object and Target.Object:IsAlive()then
return Target.Object:GetName()
end
elseif Target.Type==TARGET.ObjectType.AIRBASE then
if Target.Status==TARGET.ObjectStatus.ALIVE then
return Target.Object:GetName()
end
elseif Target.Type==TARGET.ObjectType.COORDINATE then
local coord=Target.Object
return coord:ToStringMGRS()
elseif Target.Type==TARGET.ObjectType.ZONE then
local Zone=Target.Object
return Zone:GetName()
elseif Target.Type==TARGET.ObjectType.SCENERY then
local Zone=Target.Object
return Zone:GetName()
end
return"Unknown"
end
function TARGET:GetName()
local name=self.name or"Unknown"
return name
end
function TARGET:GetVec2()
for _,_target in pairs(self.targets)do
local Target=_target
local coordinate=self:GetTargetVec2(Target)
if coordinate then
return coordinate
end
end
self:E(self.lid..string.format("ERROR: Cannot get Vec2 of target %s",self.name))
return nil
end
function TARGET:GetVec3()
for _,_target in pairs(self.targets)do
local Target=_target
local coordinate=self:GetTargetVec3(Target)
if coordinate then
return coordinate
end
end
self:E(self.lid..string.format("ERROR: Cannot get Vec3 of target %s",self.name))
return nil
end
function TARGET:GetCoordinate()
for _,_target in pairs(self.targets)do
local Target=_target
local coordinate=self:GetTargetCoordinate(Target)
if coordinate then
return coordinate
end
end
self:E(self.lid..string.format("ERROR: Cannot get coordinate of target %s",tostring(self.name)))
return nil
end
function TARGET:GetAverageCoordinate()
for _,_target in pairs(self.targets)do
local Target=_target
local coordinate=self:GetTargetCoordinate(Target,true)
if coordinate then
return coordinate
end
end
self:E(self.lid..string.format("ERROR: Cannot get average coordinate of target %s",tostring(self.name)))
return nil
end
function TARGET:GetHeading()
for _,_target in pairs(self.targets)do
local Target=_target
local heading=self:GetTargetHeading(Target)
if heading then
return heading
end
end
self:E(self.lid..string.format("ERROR: Cannot get heading of target %s",tostring(self.name)))
return nil
end
function TARGET:GetCategory()
return self.category
end
function TARGET:GetTargetCategory(Target)
local category=nil
if Target.Type==TARGET.ObjectType.GROUP then
if Target.Object and Target.Object:IsAlive()~=nil then
local group=Target.Object
local cat=group:GetCategory()
if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then
category=TARGET.Category.AIRCRAFT
elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then
category=TARGET.Category.GROUND
elseif cat==Group.Category.SHIP then
category=TARGET.Category.NAVAL
end
end
elseif Target.Type==TARGET.ObjectType.UNIT then
if Target.Object and Target.Object:IsAlive()~=nil then
local unit=Target.Object
local group=unit:GetGroup()
local cat=group:GetCategory()
if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then
category=TARGET.Category.AIRCRAFT
elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then
category=TARGET.Category.GROUND
elseif cat==Group.Category.SHIP then
category=TARGET.Category.NAVAL
end
end
elseif Target.Type==TARGET.ObjectType.STATIC then
return TARGET.Category.GROUND
elseif Target.Type==TARGET.ObjectType.SCENERY then
return TARGET.Category.GROUND
elseif Target.Type==TARGET.ObjectType.AIRBASE then
return TARGET.Category.AIRBASE
elseif Target.Type==TARGET.ObjectType.COORDINATE then
return TARGET.Category.COORDINATE
elseif Target.Type==TARGET.ObjectType.ZONE then
return TARGET.Category.ZONE
elseif Target.Type==TARGET.ObjectType.OPSZONE then
return TARGET.Category.OPSZONE
else
self:E("ERROR: unknown target category!")
end
return category
end
function TARGET:GetTargetCoalition(Target)
local coal=coalition.side.NEUTRAL
if Target.Type==TARGET.ObjectType.GROUP then
if Target.Object and Target.Object:IsAlive()~=nil then
local object=Target.Object
coal=object:GetCoalition()
end
elseif Target.Type==TARGET.ObjectType.UNIT then
if Target.Object and Target.Object:IsAlive()~=nil then
local object=Target.Object
coal=object:GetCoalition()
end
elseif Target.Type==TARGET.ObjectType.STATIC then
local object=Target.Object
coal=object:GetCoalition()
elseif Target.Type==TARGET.ObjectType.SCENERY then
elseif Target.Type==TARGET.ObjectType.AIRBASE then
local object=Target.Object
coal=object:GetCoalition()
elseif Target.Type==TARGET.ObjectType.COORDINATE then
elseif Target.Type==TARGET.ObjectType.ZONE then
elseif Target.Type==TARGET.ObjectType.OPSZONE then
local object=Target.Object
coal=object:GetOwner()
else
self:E("ERROR: unknown target category!")
end
return coal
end
function TARGET:GetTargetByName(ObjectName)
for _,_target in pairs(self.targets)do
local target=_target
if ObjectName==target.Name then
return target
end
end
return nil
end
function TARGET:GetObjective(RefCoordinate,Coalitions)
if RefCoordinate then
local dmin=math.huge
local tmin=nil
for _,_target in pairs(self.targets)do
local target=_target
if target.Status~=TARGET.ObjectStatus.DEAD and(Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions),self:GetTargetCoalition(target)))then
local vec3=self:GetTargetVec3(target)
local d=UTILS.VecDist3D(vec3,RefCoordinate)
if d<dmin then
dmin=d
tmin=target
end
end
end
return tmin
else
for _,_target in pairs(self.targets)do
local target=_target
if target.Status~=TARGET.ObjectStatus.DEAD and(Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions),self:GetTargetCoalition(target)))then
return target
end
end
end
return nil
end
function TARGET:GetObject(RefCoordinate,Coalitions)
local target=self:GetObjective(RefCoordinate,Coalitions)
if target then
return target.Object
end
return nil
end
function TARGET:CountObjectives(Target,Coalitions)
local N=0
if Target.Type==TARGET.ObjectType.GROUP then
local target=Target.Object
local units=target:GetUnits()
for _,_unit in pairs(units or{})do
local unit=_unit
if unit and unit:IsAlive()~=nil and unit:GetLife()>1 then
if Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions),unit:GetCoalition())then
N=N+1
end
end
end
elseif Target.Type==TARGET.ObjectType.UNIT then
local target=Target.Object
if target and target:IsAlive()~=nil and target:GetLife()>1 then
if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then
N=N+1
end
end
elseif Target.Type==TARGET.ObjectType.STATIC then
local target=Target.Object
if target and target:IsAlive()then
if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then
N=N+1
end
end
elseif Target.Type==TARGET.ObjectType.SCENERY then
if Target.Status~=TARGET.ObjectStatus.DEAD then
N=N+1
end
elseif Target.Type==TARGET.ObjectType.AIRBASE then
local target=Target.Object
if Target.Status==TARGET.ObjectStatus.ALIVE then
if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetCoalition())then
N=N+1
end
end
elseif Target.Type==TARGET.ObjectType.COORDINATE then
N=N+1
elseif Target.Type==TARGET.ObjectType.ZONE then
N=N+1
elseif Target.Type==TARGET.ObjectType.OPSZONE then
local target=Target.Object
if Coalitions==nil or UTILS.IsInTable(Coalitions,target:GetOwner())then
N=N+1
end
else
self:E(self.lid.."ERROR: Unknown target type! Cannot count targets")
end
return N
end
function TARGET:CountTargets(Coalitions)
local N=0
for _,_target in pairs(self.targets)do
local Target=_target
N=N+self:CountObjectives(Target,Coalitions)
end
return N
end
function TARGET:IsElement(Name)
if Name==nil then
return false
end
for _,name in pairs(self.elements)do
if name==Name then
return true
end
end
return false
end
function TARGET:IsCasualty(Name)
if Name==nil then
return false
end
for _,name in pairs(self.casualties)do
if tostring(name)==tostring(Name)then
return true
end
end
return false
end
EASYGCICAP={
ClassName="EASYGCICAP",
overhead=0.75,
capgrouping=2,
airbasename=nil,
airbase=nil,
coalition="blue",
alias=nil,
wings={},
Intel=nil,
resurrection=900,
capspeed=300,
capalt=25000,
capdir=45,
capleg=15,
maxinterceptsize=2,
missionrange=100,
noalert5=4,
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={}
}
EASYGCICAP.version="0.1.20"
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.coalitionname=="blue"and coalition.side.BLUE or coalition.side.RED
self.wings={}
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
self.capdir=90
self.capleg=15
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.lid=string.format("EASYGCICAP %s | ",self.alias)
self:SetStartState("Stopped")
self:AddTransition("Stopped","Start","Running")
self:AddTransition("Running","Stop","Stopped")
self:AddTransition("*","Status","*")
self:AddAirwing(self.airbasename,self.alias,self.CapZoneName)
self:I(self.lid.."Created new instance (v"..self.version..")")
self:__Start(math.random(6,12))
return self
end
function EASYGCICAP:SetCAPFormation(Formation)
self:T(self.lid.."SetCAPFormation")
self.CapFormation=Formation
return self
end
function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
self:T(self.lid.."SetTankerAndAWACSInvisible")
self.TankerInvisible=Switch
return self
end
function EASYGCICAP:_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.."SetMaxAliveMissions")
self.MaxAliveMissions=Maxiumum or 8
return self
end
function EASYGCICAP:SetDefaultResurrection(Seconds)
self:T(self.lid.."SetDefaultResurrection")
self.resurrection=Seconds or 900
return self
end
function EASYGCICAP:SetDefaultRepeatOnFailure(Retries)
self:T(self.lid.."SetDefaultRepeatOnFailure")
self.repeatsonfailure=Retries or 3
return self
end
function EASYGCICAP:SetDefaultCAPSpeed(Speed)
self:T(self.lid.."SetDefaultSpeed")
self.capspeed=Speed or 300
return self
end
function EASYGCICAP:SetDefaultCAPAlt(Altitude)
self:T(self.lid.."SetDefaultAltitude")
self.capalt=Altitude or 25000
return self
end
function EASYGCICAP:SetDefaultCAPDirection(Direction)
self:T(self.lid.."SetDefaultDirection")
self.capdir=Direction or 90
return self
end
function EASYGCICAP:SetDefaultCAPLeg(Leg)
self:T(self.lid.."SetDefaultLeg")
self.capleg=Leg or 15
return self
end
function EASYGCICAP:SetDefaultCAPGrouping(Grouping)
self:T(self.lid.."SetDefaultCAPGrouping")
self.capgrouping=Grouping or 2
return self
end
function EASYGCICAP:SetDefaultMissionRange(Range)
self:T(self.lid.."SetDefaultMissionRange")
self.missionrange=Range or 100
return self
end
function EASYGCICAP: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.."SetDefaultEngageRange")
self.engagerange=Range or 50
return self
end
function EASYGCICAP:SetDefaultOverhead(Overhead)
self:T(self.lid.."SetDefaultOverhead")
self.overhead=Overhead or 0.75
return self
end
function EASYGCICAP: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:AddAirwing(Airbasename,Alias)
self:T(self.lid.."AddAirwing "..Airbasename)
local AWEntry={}
AWEntry.AirbaseName=Airbasename
AWEntry.Alias=Alias
self.ManagedAW[Airbasename]=AWEntry
return self
end
function EASYGCICAP:_CreateAirwings()
self:T(self.lid.."_CreateAirwings")
for airbase,data in pairs(self.ManagedAW)do
local wing=data
local afb=wing.AirbaseName
local alias=wing.Alias
self:_AddAirwing(airbase,alias)
end
return self
end
function EASYGCICAP:_AddAirwing(Airbasename,Alias)
self:T(self.lid.."_AddAirwing "..Airbasename)
local CapFormation=self.CapFormation
local DespawnAfterLanding=self.DespawnAfterLanding
local DespawnAfterHolding=self.DespawnAfterHolding
local CAP_Wing=AIRWING:New(Airbasename,Alias)
CAP_Wing:SetVerbosityLevel(0)
CAP_Wing:SetReportOff()
CAP_Wing:SetMarker(false)
CAP_Wing:SetAirbase(AIRBASE:FindByName(Airbasename))
CAP_Wing:SetRespawnAfterDestroyed()
CAP_Wing:SetNumberCAP(self.capgrouping)
CAP_Wing:SetCapCloseRaceTrack(true)
if self.capOptionVaryStartTime then
CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime)
end
if CapFormation then
CAP_Wing:SetCAPFormation(CapFormation)
end
if#self.ManagedTK>0 then
CAP_Wing:SetNumberTankerBoom(1)
CAP_Wing:SetNumberTankerProbe(1)
end
if#self.ManagedEWR>0 then
CAP_Wing:SetNumberAWACS(1)
end
if#self.ManagedREC>0 then
CAP_Wing:SetNumberRecon(1)
end
CAP_Wing:SetTakeoffHot()
CAP_Wing:SetLowFuelThreshold(0.3)
CAP_Wing.RandomAssetScore=math.random(50,100)
CAP_Wing:Start()
local Intel=self.Intel
local TankerInvisible=self.TankerInvisible
local engagerange=self.engagerange
local GoZoneSet=self.GoZoneSet
local NoGoZoneSet=self.NoGoZoneSet
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()
if Mission.type~=AUFTRAG.Type.TANKER and Mission.type~=AUFTRAG.Type.AWACS and Mission.type~=AUFTRAG.Type.RECON then
flightgroup:SetDetection(true)
flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet)
flightgroup:SetOutOfAAMRTB()
if CapFormation then
flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation)
end
end
if Mission.type==AUFTRAG.Type.TANKER or Mission.type==AUFTRAG.Type.AWACS or Mission.type==AUFTRAG.Type.RECON then
if TankerInvisible then
flightgroup:GetGroup():SetCommandInvisible(true)
end
if Mission.type==AUFTRAG.Type.RECON then
flightgroup:SetDetection(true)
end
end
flightgroup:GetGroup():OptionROTEvadeFire()
flightgroup:SetFuelLowRTB(true)
Intel:AddAgent(flightgroup)
if DespawnAfterHolding then
function flightgroup:onbeforeHolding(From,Event,To)
self:Despawn(1,true)
end
end
end
if self.noalert5>0 then
local alert=AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
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())
local EntryCAP={}
EntryCAP.AirbaseName=AirbaseName
EntryCAP.Coordinate=Coordinate
EntryCAP.Altitude=Altitude or 25000
EntryCAP.Speed=Speed or 300
EntryCAP.Heading=Heading or 90
EntryCAP.LegLength=LegLength or 15
self.ManagedCP[#self.ManagedCP+1]=EntryCAP
if self.debug then
local mark=MARKER:New(Coordinate,self.lid.."Patrol Point"):ToAll()
end
return self
end
function EASYGCICAP:AddPatrolPointRecon(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointRecon "..Coordinate:ToStringLLDDM())
local EntryCAP={}
EntryCAP.AirbaseName=AirbaseName
EntryCAP.Coordinate=Coordinate
EntryCAP.Altitude=Altitude or 25000
EntryCAP.Speed=Speed or 300
EntryCAP.Heading=Heading or 90
EntryCAP.LegLength=LegLength or 15
self.ManagedREC[#self.ManagedREC+1]=EntryCAP
if self.debug then
local mark=MARKER:New(Coordinate,self.lid.."Patrol Point Recon"):ToAll()
end
return self
end
function EASYGCICAP:AddPatrolPointTanker(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointTanker "..Coordinate:ToStringLLDDM())
local EntryCAP={}
EntryCAP.AirbaseName=AirbaseName
EntryCAP.Coordinate=Coordinate
EntryCAP.Altitude=Altitude or 25000
EntryCAP.Speed=Speed or 300
EntryCAP.Heading=Heading or 90
EntryCAP.LegLength=LegLength or 15
self.ManagedTK[#self.ManagedTK+1]=EntryCAP
if self.debug then
local mark=MARKER:New(Coordinate,self.lid.."Patrol Point Tanker"):ToAll()
end
return self
end
function EASYGCICAP:AddPatrolPointAwacs(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength)
self:T(self.lid.."AddPatrolPointAwacs "..Coordinate:ToStringLLDDM())
local EntryCAP={}
EntryCAP.AirbaseName=AirbaseName
EntryCAP.Coordinate=Coordinate
EntryCAP.Altitude=Altitude or 25000
EntryCAP.Speed=Speed or 300
EntryCAP.Heading=Heading or 90
EntryCAP.LegLength=LegLength or 15
self.ManagedEWR[#self.ManagedEWR+1]=EntryCAP
if self.debug then
local mark=MARKER:New(Coordinate,self.lid.."Patrol Point AWACS"):ToAll()
end
return self
end
function EASYGCICAP:_SetTankerPatrolPoints()
self:T(self.lid.."_SetTankerPatrolPoints")
for _,_data in pairs(self.ManagedTK)do
local data=_data
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
local Speed=data.Speed
local Heading=data.Heading
local LegLength=data.LegLength
Wing:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
function EASYGCICAP:_SetAwacsPatrolPoints()
self:T(self.lid.."_SetAwacsPatrolPoints")
for _,_data in pairs(self.ManagedEWR)do
local data=_data
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
local Speed=data.Speed
local Heading=data.Heading
local LegLength=data.LegLength
Wing:AddPatrolPointAWACS(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
function EASYGCICAP:_SetCAPPatrolPoints()
self:T(self.lid.."_SetCAPPatrolPoints")
for _,_data in pairs(self.ManagedCP)do
local data=_data
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
local Speed=data.Speed
local Heading=data.Heading
local LegLength=data.LegLength
Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
function EASYGCICAP:_SetReconPatrolPoints()
self:T(self.lid.."_SetReconPatrolPoints")
for _,_data in pairs(self.ManagedREC)do
local data=_data
local Wing=self.wings[data.AirbaseName][1]
local Coordinate=data.Coordinate
local Altitude=data.Altitude
local Speed=data.Speed
local Heading=data.Heading
local LegLength=data.LegLength
Wing:AddPatrolPointRecon(Coordinate,Altitude,Speed,Heading,LegLength)
end
return self
end
function EASYGCICAP:_CreateSquads()
self:T(self.lid.."_CreateSquads")
for name,data in pairs(self.ManagedSQ)do
local squad=data
local SquadName=name
local TemplateName=squad.TemplateName
local AirbaseName=squad.AirbaseName
local AirFrames=squad.AirFrames
local Skill=squad.Skill
local Modex=squad.Modex
local Livery=squad.Livery
local Frequeny=squad.Frequency
local Modulation=squad.Modulation
local TACAN=squad.TACAN
if squad.Tanker then
self:_AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation,TACAN)
elseif squad.AWACS then
self:_AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequeny,Modulation)
elseif squad.RECON then
self:_AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery)
else
self:_AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery)
end
end
return self
end
function EASYGCICAP:AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery)
self:T(self.lid.."AddSquadron "..SquadName)
local EntrySQ={}
EntrySQ.TemplateName=TemplateName
EntrySQ.SquadName=SquadName
EntrySQ.AirbaseName=AirbaseName
EntrySQ.AirFrames=AirFrames or 20
EntrySQ.Skill=Skill or AI.Skill.AVERAGE
EntrySQ.Modex=Modex or 402
EntrySQ.Livery=Livery
self.ManagedSQ[SquadName]=EntrySQ
return self
end
function EASYGCICAP:AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery)
self:T(self.lid.."AddReconSquadron "..SquadName)
local EntrySQ={}
EntrySQ.TemplateName=TemplateName
EntrySQ.SquadName=SquadName
EntrySQ.AirbaseName=AirbaseName
EntrySQ.AirFrames=AirFrames or 20
EntrySQ.Skill=Skill or AI.Skill.AVERAGE
EntrySQ.Modex=Modex or 402
EntrySQ.Livery=Livery
EntrySQ.RECON=true
self.ManagedSQ[SquadName]=EntrySQ
return self
end
function EASYGCICAP:AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation,TACAN)
self:T(self.lid.."AddTankerSquadron "..SquadName)
local EntrySQ={}
EntrySQ.TemplateName=TemplateName
EntrySQ.SquadName=SquadName
EntrySQ.AirbaseName=AirbaseName
EntrySQ.AirFrames=AirFrames or 20
EntrySQ.Skill=Skill or AI.Skill.AVERAGE
EntrySQ.Modex=Modex or 602
EntrySQ.Livery=Livery
EntrySQ.Frequency=Frequency
EntrySQ.Modulation=Livery
EntrySQ.TACAN=TACAN
EntrySQ.Tanker=true
self.ManagedSQ[SquadName]=EntrySQ
return self
end
function EASYGCICAP:AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation)
self:T(self.lid.."AddAWACSSquadron "..SquadName)
local EntrySQ={}
EntrySQ.TemplateName=TemplateName
EntrySQ.SquadName=SquadName
EntrySQ.AirbaseName=AirbaseName
EntrySQ.AirFrames=AirFrames or 20
EntrySQ.Skill=Skill or AI.Skill.AVERAGE
EntrySQ.Modex=Modex or 702
EntrySQ.Livery=Livery
EntrySQ.Frequency=Frequency
EntrySQ.Modulation=Livery
EntrySQ.AWACS=true
self.ManagedSQ[SquadName]=EntrySQ
return self
end
function EASYGCICAP:_AddSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation)
self:T(self.lid.."_AddSquadron "..SquadName)
local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP,AUFTRAG.Type.GCICAP,AUFTRAG.Type.INTERCEPT,AUFTRAG.Type.PATROLRACETRACK,AUFTRAG.Type.ALERT5})
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
local wing=self.wings[AirbaseName][1]
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAP,AUFTRAG.Type.GCICAP,AUFTRAG.Type.INTERCEPT,AUFTRAG.Type.PATROLRACETRACK,AUFTRAG.Type.ALERT5},75)
return self
end
function EASYGCICAP:_AddReconSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery)
self:T(self.lid.."_AddReconSquadron "..SquadName)
local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON})
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
local wing=self.wings[AirbaseName][1]
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.RECON},75)
return self
end
function EASYGCICAP:_AddTankerSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation,TACAN)
self:T(self.lid.."_AddTankerSquadron "..SquadName)
local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER})
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
Squadron_One:SetRadio(Frequency,Modulation)
Squadron_One:AddTacanChannel(TACAN,TACAN)
local wing=self.wings[AirbaseName][1]
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75)
return self
end
function EASYGCICAP:_AddAWACSSquadron(TemplateName,SquadName,AirbaseName,AirFrames,Skill,Modex,Livery,Frequency,Modulation)
self:T(self.lid.."_AddAWACSSquadron "..SquadName)
local Squadron_One=SQUADRON:New(TemplateName,AirFrames,SquadName)
Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS})
Squadron_One:SetFuelLowThreshold(0.3)
Squadron_One:SetTurnoverTime(10,20)
Squadron_One:SetModex(Modex)
Squadron_One:SetLivery(Livery)
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
Squadron_One:SetRadio(Frequency,Modulation)
local wing=self.wings[AirbaseName][1]
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.AWACS},75)
return self
end
function EASYGCICAP:AddAcceptZone(Zone)
self:T(self.lid.."AddAcceptZone")
self.GoZoneSet:AddZone(Zone)
return self
end
function EASYGCICAP:AddRejectZone(Zone)
self:T(self.lid.."AddRejectZone")
self.NoGoZoneSet:AddZone(Zone)
return self
end
function EASYGCICAP:AddConflictZone(Zone)
self:T(self.lid.."AddConflictZone")
self.ConflictZoneSet:AddZone(Zone)
self.GoZoneSet:AddZone(Zone)
return self
end
function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize)
self:I("_TryAssignIntercept for size "..WingSize or 1)
local assigned=false
local wingsize=WingSize or 1
local mindist=0
local disttable={}
if Group and Group:IsAlive()then
local gcoord=Group:GetCoordinate()or COORDINATE:New(0,0,0)
self:I(self.lid..string.format("Assignment for %s",Group:GetName()))
for _name,_FG in pairs(ReadyFlightGroups or{})do
local FG=_FG
local fcoord=FG:GetCoordinate()
local dist=math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1))
self:I(self.lid..string.format("FG %s Distance %dkm",_name,dist))
disttable[#disttable+1]={FG=FG,dist=dist}
if dist>mindist then mindist=dist end
end
local function sortDistance(a,b)
return a.dist<b.dist
end
table.sort(disttable,sortDistance)
for _,_entry in ipairs(disttable)do
local FG=_entry.FG
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))
if wingsize==0 then
assigned=true
break
end
end
end
return assigned,wingsize
end
function EASYGCICAP:_AssignIntercept(Cluster)
local overhead=self.overhead
local capspeed=self.capspeed+100
local capalt=self.capalt
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 Interceptors need wingsize %d",UTILS.GetCoalitionName(self.coalition),wingsize),15,"CAPGCI"):ToAllIf(self.debug):ToLog()
for _,_data in pairs(wings)do
local airwing=_data[1]
local zone=_data[2]
local zonecoord=zone:GetCoordinate()
local name=_data[3]
local 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<bestdistance and airframes>=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
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<bestdistance and airframes>=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,"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 missioncount<MaxAliveMissions then
local repeats=repeatsonfailure
local InterceptAuftrag=AUFTRAG:NewINTERCEPT(contact.group)
:SetMissionRange(150)
:SetPriority(1,true,1)
:SetRepeatOnFailure(repeats)
:SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt))
:SetMissionAltitude(capalt)
if nogozoneset:Count()>0 then
InterceptAuftrag:AddConditionSuccess(
function(group,zoneset,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
end
return success
end,
contact.group,
nogozoneset,
conflictzoneset
)
end
table.insert(self.ListOfAuftrag,InterceptAuftrag)
local assigned,rest=self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize)
if not assigned then
InterceptAuftrag:SetRequiredAssets(rest)
targetairwing:AddMission(InterceptAuftrag)
end
Cluster.mission=InterceptAuftrag
end
else
MESSAGE:New("**** Not enough airframes available or max mission limit reached!",15,"CAPGCI"):ToAllIf(self.debug):ToLog()
end
end
end
function EASYGCICAP:_StartIntel()
self:T(self.lid.."_StartIntel")
local BlueAir_DetectionSetGroup=SET_GROUP:New()
BlueAir_DetectionSetGroup:FilterPrefixes(self.EWRName)
BlueAir_DetectionSetGroup:FilterStart()
local BlueIntel=INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname,self.alias)
BlueIntel:SetClusterAnalysis(true,false,false)
BlueIntel:SetForgetTime(300)
BlueIntel:SetAcceptZones(self.GoZoneSet)
BlueIntel:SetRejectZones(self.NoGoZoneSet)
BlueIntel:SetConflictZones(self.ConflictZoneSet)
BlueIntel:SetVerbosity(0)
BlueIntel:Start()
if self.debug then
BlueIntel.debug=true
end
local function AssignCluster(Cluster)
self:_AssignIntercept(Cluster)
end
function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
AssignCluster(Cluster)
end
self.Intel=BlueIntel
return self
end
function EASYGCICAP:onafterStart(From,Event,To)
self:T({From,Event,To})
self:_StartIntel()
self:_CreateAirwings()
self:_CreateSquads()
self:_SetCAPPatrolPoints()
self:_SetTankerPatrolPoints()
self:_SetAwacsPatrolPoints()
self:_SetReconPatrolPoints()
self:__Status(-10)
return self
end
function EASYGCICAP:onbeforeStatus(From,Event,To)
self:T({From,Event,To})
if self:GetState()=="Stopped"then return false end
return self
end
function EASYGCICAP:onafterStatus(From,Event,To)
self:T({From,Event,To})
local 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
for _,_wing in pairs(self.wings)do
local count=_wing[1]:CountAssetsOnMission(MissionTypes,Cohort)
local count2=_wing[1]:CountAssets(true,MissionTypes,Attributes)
capmission=capmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
interceptmission=interceptmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.INTERCEPT})
reconmission=reconmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.RECON})
awacsmission=awacsmission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.AWACS})
tankermission=tankermission+_wing[1]:CountMissionsInQueue({AUFTRAG.Type.TANKER})
assets=assets+count
instock=instock+count2
local assetsonmission=_wing[1]:GetAssetsOnMission({AUFTRAG.Type.GCICAP,AUFTRAG.Type.PATROLRACETRACK})
self.ReadyFlightGroups=nil
self.ReadyFlightGroups={}
for _,_asset in pairs(assetsonmission or{})do
local asset=_asset
local FG=asset.flightgroup
if FG then
local name=FG:GetName()
local engage=FG:IsEngaging()
local hasmissiles=FG:IsOutOfMissiles()==nil and true or false
local ready=hasmissiles and FG:IsFuelGood()and FG:IsAirborne()
if ready then
self.ReadyFlightGroups[name]=FG
end
end
end
end
if self.Monitor then
local threatcount=#self.Intel.Clusters or 0
local text="GCICAP "..self.alias
text=text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
text=text.."\nThreats: "..threatcount
text=text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission
text=text.."\n - CAP: "..capmission
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)
return self
end
function EASYGCICAP:onafterStop(From,Event,To)
self:T({From,Event,To})
self.Intel:Stop()
return self
end
SHAPE_BASE={
ClassName="SHAPE_BASE",
Name="",
CenterVec2=nil,
Points={},
Coords={},
MarkIDs={},
ColorString="",
ColorRGBA={}
}
function SHAPE_BASE:New()
local self=BASE:Inherit(self,BASE:New())
return self
end
function SHAPE_BASE:FindOnMap(shape_name)
local self=BASE:Inherit(self,BASE:New())
local found=false
for _,layer in pairs(env.mission.drawings.layers)do
for _,object in pairs(layer["objects"])do
if object["name"]==shape_name then
self.Name=object["name"]
self.CenterVec2={x=object["mapX"],y=object["mapY"]}
self.ColorString=object["colorString"]
self.ColorRGBA=UTILS.HexToRGBA(self.ColorString)
found=true
end
end
end
if not found then
self:E("Can't find a shape with name "..shape_name)
end
return self
end
function SHAPE_BASE:GetAllShapes(filter)
filter=filter or""
local return_shapes={}
for _,layer in pairs(env.mission.drawings.layers)do
for _,object in pairs(layer["objects"])do
if string.contains(object["name"],filter)then
table.add(return_shapes,object)
end
end
end
return return_shapes
end
function SHAPE_BASE:Offset(new_vec2)
local offset_vec2=UTILS.Vec2Subtract(new_vec2,self.CenterVec2)
self.CenterVec2=new_vec2
if self.ClassName=="POLYGON"then
for _,point in pairs(self.Points)do
point.x=point.x+offset_vec2.x
point.y=point.y+offset_vec2.y
end
end
end
function SHAPE_BASE:GetName()
return self.Name
end
function SHAPE_BASE:GetColorString()
return self.ColorString
end
function SHAPE_BASE:GetColorRGBA()
return self.ColorRGBA
end
function SHAPE_BASE:GetColorRed()
return self.ColorRGBA.R
end
function SHAPE_BASE:GetColorGreen()
return self.ColorRGBA.G
end
function SHAPE_BASE:GetColorBlue()
return self.ColorRGBA.B
end
function SHAPE_BASE:GetColorAlpha()
return self.ColorRGBA.A
end
function SHAPE_BASE:GetCenterVec2()
return self.CenterVec2
end
function SHAPE_BASE:GetCenterCoordinate()
return COORDINATE:NewFromVec2(self.CenterVec2)
end
function SHAPE_BASE:GetCoordinate()
return self:GetCenterCoordinate()
end
function SHAPE_BASE:ContainsPoint(_)
self:E("This needs to be set in the derived class")
end
function SHAPE_BASE:ContainsUnit(unit_name)
local unit=UNIT:FindByName(unit_name)
if unit==nil or not unit:IsAlive()then
return false
end
if self:ContainsPoint(unit:GetVec2())then
return true
end
return false
end
function SHAPE_BASE:ContainsAnyOfGroup(group_name)
local group=GROUP:FindByName(group_name)
if group==nil or not group:IsAlive()then
return false
end
for _,unit in pairs(group:GetUnits())do
if self:ContainsPoint(unit:GetVec2())then
return true
end
end
return false
end
function SHAPE_BASE:ContainsAllOfGroup(group_name)
local group=GROUP:FindByName(group_name)
if group==nil or not group:IsAlive()then
return false
end
for _,unit in pairs(group:GetUnits())do
if not self:ContainsPoint(unit:GetVec2())then
return false
end
end
return true
end
CIRCLE={
ClassName="CIRCLE",
Radius=nil,
}
function CIRCLE:FindOnMap(shape_name)
local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name))
for _,layer in pairs(env.mission.drawings.layers)do
for _,object in pairs(layer["objects"])do
if string.find(object["name"],shape_name,1,true)then
if object["polygonMode"]=="circle"then
self.Radius=object["radius"]
end
end
end
end
return self
end
function CIRCLE:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
function CIRCLE:New(vec2,radius)
local self=BASE:Inherit(self,SHAPE_BASE:New())
self.CenterVec2=vec2
self.Radius=radius
return self
end
function CIRCLE:GetRadius()
return self.Radius
end
function CIRCLE:ContainsPoint(point)
if((point.x-self.CenterVec2.x)^2+(point.y-self.CenterVec2.y)^2)^0.5<=self.Radius then
return true
end
return false
end
function CIRCLE:PointInSector(point,sector_start,sector_end,center,radius)
center=center or self.CenterVec2
radius=radius or self.Radius
local function are_clockwise(v1,v2)
return-v1.x*v2.y+v1.y*v2.x>0
end
local function is_in_radius(rp)
return rp.x*rp.x+rp.y*rp.y<=radius^2
end
local rel_pt={
x=point.x-center.x,
y=point.y-center.y
}
local rel_sector_start={
x=sector_start.x-center.x,
y=sector_start.y-center.y,
}
local rel_sector_end={
x=sector_end.x-center.x,
y=sector_end.y-center.y,
}
return not are_clockwise(rel_sector_start,rel_pt)and
are_clockwise(rel_sector_end,rel_pt)and
is_in_radius(rel_pt,radius)
end
function CIRCLE:UnitInSector(unit_name,sector_start,sector_end,center,radius)
center=center or self.CenterVec2
radius=radius or self.Radius
if self:PointInSector(UNIT:FindByName(unit_name):GetVec2(),sector_start,sector_end,center,radius)then
return true
end
return false
end
function CIRCLE:AnyOfGroupInSector(group_name,sector_start,sector_end,center,radius)
center=center or self.CenterVec2
radius=radius or self.Radius
for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do
if self:PointInSector(unit:GetVec2(),sector_start,sector_end,center,radius)then
return true
end
end
return false
end
function CIRCLE:AllOfGroupInSector(group_name,sector_start,sector_end,center,radius)
center=center or self.CenterVec2
radius=radius or self.Radius
for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do
if not self:PointInSector(unit:GetVec2(),sector_start,sector_end,center,radius)then
return false
end
end
return true
end
function CIRCLE:UnitInRadius(unit_name,center,radius)
center=center or self.CenterVec2
radius=radius or self.Radius
if UTILS.IsInRadius(center,UNIT:FindByName(unit_name):GetVec2(),radius)then
return true
end
return false
end
function CIRCLE:AnyOfGroupInRadius(group_name,center,radius)
center=center or self.CenterVec2
radius=radius or self.Radius
for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do
if UTILS.IsInRadius(center,unit:GetVec2(),radius)then
return true
end
end
return false
end
function CIRCLE:AllOfGroupInRadius(group_name,center,radius)
center=center or self.CenterVec2
radius=radius or self.Radius
for _,unit in pairs(GROUP:FindByName(group_name):GetUnits())do
if not UTILS.IsInRadius(center,unit:GetVec2(),radius)then
return false
end
end
return true
end
function CIRCLE:GetRandomVec2()
local angle=math.random()*2*math.pi
local rx=math.random(0,self.Radius)*math.cos(angle)+self.CenterVec2.x
local ry=math.random(0,self.Radius)*math.sin(angle)+self.CenterVec2.y
return{x=rx,y=ry}
end
function CIRCLE:GetRandomVec2OnBorder()
local angle=math.random()*2*math.pi
local rx=self.Radius*math.cos(angle)+self.CenterVec2.x
local ry=self.Radius*math.sin(angle)+self.CenterVec2.y
return{x=rx,y=ry}
end
function CIRCLE:GetBoundingBox()
local min_x=self.CenterVec2.x-self.Radius
local min_y=self.CenterVec2.y-self.Radius
local max_x=self.CenterVec2.x+self.Radius
local max_y=self.CenterVec2.y+self.Radius
return{
{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y}
}
end
CUBE={
ClassName="CUBE",
Points={},
Coords={}
}
function CUBE:New(p1,p2,p3,p4,p5,p6,p7,p8)
local self=BASE:Inherit(self,SHAPE_BASE)
self.Points={p1,p2,p3,p4,p5,p6,p7,p8}
for _,point in spairs(self.Points)do
table.insert(self.Coords,COORDINATE:NewFromVec3(point))
end
return self
end
function CUBE:GetCenter()
local center={x=0,y=0,z=0}
for _,point in pairs(self.Points)do
center.x=center.x+point.x
center.y=center.y+point.y
center.z=center.z+point.z
end
center.x=center.x/8
center.y=center.y/8
center.z=center.z/8
return center
end
function CUBE:ContainsPoint(point,cube_points)
cube_points=cube_points or self.Points
local min_x,min_y,min_z=math.huge,math.huge,math.huge
local max_x,max_y,max_z=-math.huge,-math.huge,-math.huge
for _,p in ipairs(cube_points)do
if p.x<min_x then min_x=p.x end
if p.y<min_y then min_y=p.y end
if p.z<min_z then min_z=p.z end
if p.x>max_x then max_x=p.x end
if p.y>max_y then max_y=p.y end
if p.z>max_z then max_z=p.z end
end
return point.x>=min_x and point.x<=max_x and point.y>=min_y and point.y<=max_y and point.z>=min_z and point.z<=max_z
end
LINE={
ClassName="LINE",
Points={},
Coords={},
}
function LINE:FindOnMap(line_name)
local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(line_name))
for _,layer in pairs(env.mission.drawings.layers)do
for _,object in pairs(layer["objects"])do
if object["name"]==line_name then
if object["primitiveType"]=="Line"then
for _,point in UTILS.spairs(object["points"])do
local p={x=object["mapX"]+point["x"],
y=object["mapY"]+point["y"]}
local coord=COORDINATE:NewFromVec2(p)
table.insert(self.Points,p)
table.insert(self.Coords,coord)
end
end
end
end
end
self:I(#self.Points)
if#self.Points==0 then
return nil
end
self.MarkIDs={}
return self
end
function LINE:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
function LINE:New(...)
local self=BASE:Inherit(self,SHAPE_BASE:New())
self.Points={...}
self:I(self.Points)
for _,point in UTILS.spairs(self.Points)do
table.insert(self.Coords,COORDINATE:NewFromVec2(point))
end
return self
end
function LINE:NewFromCircle(center_point,radius,angle_degrees)
local self=BASE:Inherit(self,SHAPE_BASE:New())
self.CenterVec2=center_point
local angleRadians=math.rad(angle_degrees)
local point1={
x=center_point.x+radius*math.cos(angleRadians),
y=center_point.y+radius*math.sin(angleRadians)
}
local point2={
x=center_point.x+radius*math.cos(angleRadians+math.pi),
y=center_point.y+radius*math.sin(angleRadians+math.pi)
}
for _,point in pairs{point1,point2}do
table.insert(self.Points,point)
table.insert(self.Coords,COORDINATE:NewFromVec2(point))
end
return self
end
function LINE:Coordinates()
return self.Coords
end
function LINE:GetStartCoordinate()
return self.Coords[1]
end
function LINE:GetEndCoordinate()
return self.Coords[#self.Coords]
end
function LINE:GetStartPoint()
return self.Points[1]
end
function LINE:GetEndPoint()
return self.Points[#self.Points]
end
function LINE:GetLength()
local total_length=0
for i=1,#self.Points-1 do
local x1,y1=self.Points[i]["x"],self.Points[i]["y"]
local x2,y2=self.Points[i+1]["x"],self.Points[i+1]["y"]
local segment_length=math.sqrt((x2-x1)^2+(y2-y1)^2)
total_length=total_length+segment_length
end
return total_length
end
function LINE:GetRandomPoint(points)
points=points or self.Points
local rand=math.random()
local random_x=points[1].x+rand*(points[2].x-points[1].x)
local random_y=points[1].y+rand*(points[2].y-points[1].y)
return{x=random_x,y=random_y}
end
function LINE:GetHeading(points)
points=points or self.Points
local angle=math.atan2(points[2].y-points[1].y,points[2].x-points[1].x)
angle=math.deg(angle)
if angle<0 then
angle=angle+360
end
return angle
end
function LINE:GetIndividualParts()
local parts={}
if#self.Points==2 then
parts={self}
end
for i=1,#self.Points-1 do
local p1=self.Points[i]
local p2=self.Points[i%#self.Points+1]
table.add(parts,LINE:New(p1,p2))
end
return parts
end
function LINE:GetPointsInbetween(amount,start_point,end_point)
start_point=start_point or self:GetStartPoint()
end_point=end_point or self:GetEndPoint()
if amount==0 then return{start_point,end_point}end
amount=amount+1
local points={}
local difference={x=end_point.x-start_point.x,y=end_point.y-start_point.y}
local divided={x=difference.x/amount,y=difference.y/amount}
for j=0,amount do
local part_pos={x=divided.x*j,y=divided.y*j}
local point={x=start_point.x+part_pos.x,y=start_point.y+part_pos.y}
table.insert(points,point)
end
return points
end
function LINE:GetCoordinatesInBetween(amount,start_point,end_point)
local coords={}
for _,pt in pairs(self:GetPointsInbetween(amount,start_point,end_point))do
table.add(coords,COORDINATE:NewFromVec2(pt))
end
return coords
end
function LINE:GetRandomPoint(start_point,end_point)
start_point=start_point or self:GetStartPoint()
end_point=end_point or self:GetEndPoint()
local fraction=math.random()
local difference={x=end_point.x-start_point.x,y=end_point.y-start_point.y}
local part_pos={x=difference.x*fraction,y=difference.y*fraction}
local random_point={x=start_point.x+part_pos.x,y=start_point.y+part_pos.y}
return random_point
end
function LINE:GetRandomCoordinate(start_point,end_point)
start_point=start_point or self:GetStartPoint()
end_point=end_point or self:GetEndPoint()
return COORDINATE:NewFromVec2(self:GetRandomPoint(start_point,end_point))
end
function LINE:GetPointsBetweenAsSineWave(amount,start_point,end_point,frequency,phase,amplitude)
amount=amount or 20
start_point=start_point or self:GetStartPoint()
end_point=end_point or self:GetEndPoint()
frequency=frequency or 1
phase=phase or 0
amplitude=amplitude or 100
local points={}
local function sine_wave(x)
return amplitude*math.sin(2*math.pi*frequency*(x-start_point.x)+phase)
end
local x=start_point.x
local step=(end_point.x-start_point.x)/20
for _=1,amount do
local y=sine_wave(x)
x=x+step
table.add(points,{x=x,y=y})
end
return points
end
function LINE:GetBoundingBox()
local min_x,min_y,max_x,max_y=self.Points[1].x,self.Points[1].y,self.Points[2].x,self.Points[2].y
for i=2,#self.Points do
local x,y=self.Points[i].x,self.Points[i].y
if x<min_x then
min_x=x
end
if y<min_y then
min_y=y
end
if x>max_x then
max_x=x
end
if y>max_y then
max_y=y
end
end
return{
{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y}
}
end
function LINE:Draw()
for i=1,#self.Coords-1 do
local c1=self.Coords[i]
local c2=self.Coords[i%#self.Coords+1]
table.add(self.MarkIDs,c1:LineToAll(c2))
end
end
function LINE:RemoveDraw()
for _,mark_id in pairs(self.MarkIDs)do
UTILS.RemoveMark(mark_id)
end
end
OVAL={
ClassName="OVAL",
MajorAxis=nil,
MinorAxis=nil,
Angle=0,
DrawPoly=nil
}
function OVAL:FindOnMap(shape_name)
local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name))
for _,layer in pairs(env.mission.drawings.layers)do
for _,object in pairs(layer["objects"])do
if string.find(object["name"],shape_name,1,true)then
if object["polygonMode"]=="oval"then
self.CenterVec2={x=object["mapX"],y=object["mapY"]}
self.MajorAxis=object["r1"]
self.MinorAxis=object["r2"]
self.Angle=object["angle"]
end
end
end
end
return self
end
function OVAL:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
function OVAL:New(vec2,major_axis,minor_axis,angle)
local self=BASE:Inherit(self,SHAPE_BASE:New())
self.CenterVec2=vec2
self.MajorAxis=major_axis
self.MinorAxis=minor_axis
self.Angle=angle or 0
return self
end
function OVAL:GetMajorAxis()
return self.MajorAxis
end
function OVAL:GetMinorAxis()
return self.MinorAxis
end
function OVAL:GetAngle()
return self.Angle
end
function OVAL:SetMajorAxis(value)
self.MajorAxis=value
end
function OVAL:SetMinorAxis(value)
self.MinorAxis=value
end
function OVAL:SetAngle(value)
self.Angle=value
end
function OVAL:ContainsPoint(point)
local cos,sin=math.cos,math.sin
local dx=point.x-self.CenterVec2.x
local dy=point.y-self.CenterVec2.y
local rx=dx*cos(self.Angle)+dy*sin(self.Angle)
local ry=-dx*sin(self.Angle)+dy*cos(self.Angle)
return rx*rx/(self.MajorAxis*self.MajorAxis)+ry*ry/(self.MinorAxis*self.MinorAxis)<=1
end
function OVAL:GetRandomVec2()
local theta=math.rad(self.Angle)
local random_point=math.sqrt(math.random())
local phi=math.random()*2*math.pi
local x_c=random_point*math.cos(phi)
local y_c=random_point*math.sin(phi)
local x_e=x_c*self.MajorAxis
local y_e=y_c*self.MinorAxis
local rx=(x_e*math.cos(theta)-y_e*math.sin(theta))+self.CenterVec2.x
local ry=(x_e*math.sin(theta)+y_e*math.cos(theta))+self.CenterVec2.y
return{x=rx,y=ry}
end
function OVAL:GetBoundingBox()
local min_x=self.CenterVec2.x-self.MajorAxis
local min_y=self.CenterVec2.y-self.MinorAxis
local max_x=self.CenterVec2.x+self.MajorAxis
local max_y=self.CenterVec2.y+self.MinorAxis
return{
{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y}
}
end
function OVAL:Draw()
self.DrawPoly=POLYGON:NewFromPoints(self:PointsOnEdge(20))
self.DrawPoly:Draw(true)
end
function OVAL:RemoveDraw()
self.DrawPoly:RemoveDraw()
end
function OVAL:PointsOnEdge(num_points)
num_points=num_points or 20
local points={}
local dtheta=2*math.pi/num_points
for i=0,num_points-1 do
local theta=i*dtheta
local x=self.CenterVec2.x+self.MajorAxis*math.cos(theta)*math.cos(self.Angle)-self.MinorAxis*math.sin(theta)*math.sin(self.Angle)
local y=self.CenterVec2.y+self.MajorAxis*math.cos(theta)*math.sin(self.Angle)+self.MinorAxis*math.sin(theta)*math.cos(self.Angle)
table.insert(points,{x=x,y=y})
end
return points
end
POLYGON={
ClassName="POLYGON",
Points={},
Coords={},
Triangles={},
SurfaceArea=0,
TriangleMarkIDs={},
OutlineMarkIDs={},
Angle=nil,
Heading=nil
}
function POLYGON:FindOnMap(shape_name)
local self=BASE:Inherit(self,SHAPE_BASE:FindOnMap(shape_name))
for _,layer in pairs(env.mission.drawings.layers)do
for _,object in pairs(layer["objects"])do
if object["name"]==shape_name then
if(object["primitiveType"]=="Line"and object["closed"]==true)or(object["polygonMode"]=="free")then
for _,point in UTILS.spairs(object["points"])do
local p={x=object["mapX"]+point["x"],
y=object["mapY"]+point["y"]}
local coord=COORDINATE:NewFromVec2(p)
self.Points[#self.Points+1]=p
self.Coords[#self.Coords+1]=coord
end
elseif object["polygonMode"]=="rect"then
local angle=object["angle"]
local half_width=object["width"]/2
local half_height=object["height"]/2
local p1=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x-half_height,y=self.CenterVec2.y+half_width},self.CenterVec2,angle)
local p2=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x+half_height,y=self.CenterVec2.y+half_width},self.CenterVec2,angle)
local p3=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x+half_height,y=self.CenterVec2.y-half_width},self.CenterVec2,angle)
local p4=UTILS.RotatePointAroundPivot({x=self.CenterVec2.x-half_height,y=self.CenterVec2.y-half_width},self.CenterVec2,angle)
self.Points={p1,p2,p3,p4}
for _,point in pairs(self.Points)do
self.Coords[#self.Coords+1]=COORDINATE:NewFromVec2(point)
end
elseif object["polygonMode"]=="arrow"then
for _,point in UTILS.spairs(object["points"])do
local p={x=object["mapX"]+point["x"],
y=object["mapY"]+point["y"]}
local coord=COORDINATE:NewFromVec2(p)
self.Points[#self.Points+1]=p
self.Coords[#self.Coords+1]=coord
end
self.Angle=object["angle"]
self.Heading=UTILS.ClampAngle(self.Angle+90)
end
end
end
end
if#self.Points==0 then
return nil
end
self.CenterVec2=self:GetCentroid()
self.Triangles=self:Triangulate()
self.SurfaceArea=self:__CalculateSurfaceArea()
self.TriangleMarkIDs={}
self.OutlineMarkIDs={}
return self
end
function POLYGON:FromZone(zone_name)
for _,zone in pairs(env.mission.triggers.zones)do
if zone["name"]==zone_name then
return POLYGON:New(unpack(zone["verticies"]or{}))
end
end
end
function POLYGON:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
function POLYGON:New(...)
local self=BASE:Inherit(self,SHAPE_BASE:New())
self.Points={...}
self.Coords={}
for _,point in UTILS.spairs(self.Points)do
table.insert(self.Coords,COORDINATE:NewFromVec2(point))
end
self.Triangles=self:Triangulate()
self.SurfaceArea=self:__CalculateSurfaceArea()
return self
end
function POLYGON:GetCentroid()
local function sum(t)
local total=0
for _,value in pairs(t)do
total=total+value
end
return total
end
local x_values={}
local y_values={}
local length=table.length(self.Points)
for _,point in pairs(self.Points)do
table.insert(x_values,point.x)
table.insert(y_values,point.y)
end
local x=sum(x_values)/length
local y=sum(y_values)/length
return{
["x"]=x,
["y"]=y
}
end
function POLYGON:GetCoordinates()
return self.Coords
end
function POLYGON:GetStartCoordinate()
return self.Coords[1]
end
function POLYGON:GetEndCoordinate()
return self.Coords[#self.Coords]
end
function POLYGON:GetStartPoint()
return self.Points[1]
end
function POLYGON:GetEndPoint()
return self.Points[#self.Points]
end
function POLYGON:GetPoints()
return self.Points
end
function POLYGON:GetSurfaceArea()
return self.SurfaceArea
end
function POLYGON:GetBoundingBox()
local min_x,min_y,max_x,max_y=self.Points[1].x,self.Points[1].y,self.Points[1].x,self.Points[1].y
for i=2,#self.Points do
local x,y=self.Points[i].x,self.Points[i].y
if x<min_x then
min_x=x
end
if y<min_y then
min_y=y
end
if x>max_x then
max_x=x
end
if y>max_y then
max_y=y
end
end
return{
{x=min_x,y=min_x},{x=max_x,y=min_y},{x=max_x,y=max_y},{x=min_x,y=max_y}
}
end
function POLYGON:Triangulate(points)
points=points or self.Points
local triangles={}
local function get_orientation(shape_points)
local sum=0
for i=1,#shape_points do
local j=i%#shape_points+1
sum=sum+(shape_points[j].x-shape_points[i].x)*(shape_points[j].y+shape_points[i].y)
end
return sum>=0 and"clockwise"or"counter-clockwise"
end
local function ensure_clockwise(shape_points)
local orientation=get_orientation(shape_points)
if orientation=="counter-clockwise"then
local reversed={}
for i=#shape_points,1,-1 do
table.insert(reversed,shape_points[i])
end
return reversed
end
return shape_points
end
local function is_clockwise(p1,p2,p3)
local cross_product=(p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x)
return cross_product<0
end
local function divide_recursively(shape_points)
if#shape_points==3 then
table.insert(triangles,TRIANGLE:New(shape_points[1],shape_points[2],shape_points[3]))
elseif#shape_points>3 then
for i,p1 in ipairs(shape_points)do
local p2=shape_points[(i%#shape_points)+1]
local p3=shape_points[(i+1)%#shape_points+1]
local triangle=TRIANGLE:New(p1,p2,p3)
local is_ear=true
if not is_clockwise(p1,p2,p3)then
is_ear=false
else
for _,point in ipairs(shape_points)do
if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
is_ear=false
break
end
end
end
if is_ear then
local is_valid_triangle=true
for _,point in ipairs(points)do
if point~=p1 and point~=p2 and point~=p3 and triangle:ContainsPoint(point)then
is_valid_triangle=false
break
end
end
if is_valid_triangle then
table.insert(triangles,triangle)
local remaining_points={}
for j,point in ipairs(shape_points)do
if point~=p2 then
table.insert(remaining_points,point)
end
end
divide_recursively(remaining_points)
break
end
end
end
end
end
points=ensure_clockwise(points)
divide_recursively(points)
return triangles
end
function POLYGON:CovarianceMatrix()
local cx,cy=self:GetCentroid()
local covXX,covYY,covXY=0,0,0
for _,p in ipairs(self.points)do
covXX=covXX+(p.x-cx)^2
covYY=covYY+(p.y-cy)^2
covXY=covXY+(p.x-cx)*(p.y-cy)
end
covXX=covXX/(#self.points-1)
covYY=covYY/(#self.points-1)
covXY=covXY/(#self.points-1)
return covXX,covYY,covXY
end
function POLYGON:Direction()
local covXX,covYY,covXY=self:CovarianceMatrix()
local theta=0.5*math.atan2(2*covXY,covXX-covYY)
return math.cos(theta),math.sin(theta)
end
function POLYGON:GetRandomVec2()
local weights={}
for _,triangle in pairs(self.Triangles)do
weights[triangle]=triangle.SurfaceArea/self.SurfaceArea
end
local random_weight=math.random()
local accumulated_weight=0
for triangle,weight in pairs(weights)do
accumulated_weight=accumulated_weight+weight
if accumulated_weight>=random_weight then
return triangle:GetRandomVec2()
end
end
end
function POLYGON:GetRandomNonWeightedVec2()
return self.Triangles[math.random(1,#self.Triangles)]:GetRandomVec2()
end
function POLYGON:ContainsPoint(point,polygon_points)
local x=point.x
local y=point.y
polygon_points=polygon_points or self.Points
local counter=0
local num_points=#polygon_points
for current_index=1,num_points do
local next_index=(current_index%num_points)+1
local current_x,current_y=polygon_points[current_index].x,polygon_points[current_index].y
local next_x,next_y=polygon_points[next_index].x,polygon_points[next_index].y
if((current_y>y)~=(next_y>y))and(x<(next_x-current_x)*(y-current_y)/(next_y-current_y)+current_x)then
counter=counter+1
end
end
return counter%2==1
end
function POLYGON:Draw(include_inner_triangles)
include_inner_triangles=include_inner_triangles or false
for i=1,#self.Coords do
local c1=self.Coords[i]
local c2=self.Coords[i%#self.Coords+1]
table.add(self.OutlineMarkIDs,c1:LineToAll(c2))
end
if include_inner_triangles then
for _,triangle in ipairs(self.Triangles)do
triangle:Draw()
end
end
end
function POLYGON:RemoveDraw()
for _,triangle in pairs(self.Triangles)do
triangle:RemoveDraw()
end
for _,mark_id in pairs(self.OutlineMarkIDs)do
UTILS.RemoveMark(mark_id)
end
end
function POLYGON:__CalculateSurfaceArea()
local area=0
for _,triangle in pairs(self.Triangles)do
area=area+triangle.SurfaceArea
end
return area
end
TRIANGLE={
ClassName="TRIANGLE",
Points={},
Coords={},
SurfaceArea=0
}
function TRIANGLE:New(p1,p2,p3)
local self=BASE:Inherit(self,SHAPE_BASE:New())
self.Points={p1,p2,p3}
local center_x=(p1.x+p2.x+p3.x)/3
local center_y=(p1.y+p2.y+p3.y)/3
self.CenterVec2={x=center_x,y=center_y}
for _,pt in pairs({p1,p2,p3})do
table.add(self.Coords,COORDINATE:NewFromVec2(pt))
end
self.SurfaceArea=math.abs((p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y))*0.5
self.MarkIDs={}
return self
end
function TRIANGLE:ContainsPoint(pt,points)
points=points or self.Points
local function sign(p1,p2,p3)
return(p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y)
end
local d1=sign(pt,self.Points[1],self.Points[2])
local d2=sign(pt,self.Points[2],self.Points[3])
local d3=sign(pt,self.Points[3],self.Points[1])
local has_neg=(d1<0)or(d2<0)or(d3<0)
local has_pos=(d1>0)or(d2>0)or(d3>0)
return not(has_neg and has_pos)
end
function TRIANGLE:GetRandomVec2(points)
points=points or self.Points
local pt={math.random(),math.random()}
table.sort(pt)
local s=pt[1]
local t=pt[2]-pt[1]
local u=1-pt[2]
return{x=s*points[1].x+t*points[2].x+u*points[3].x,
y=s*points[1].y+t*points[2].y+u*points[3].y}
end
function TRIANGLE:Draw()
for i=1,#self.Coords do
local c1=self.Coords[i]
local c2=self.Coords[i%#self.Coords+1]
table.add(self.MarkIDs,c1:LineToAll(c2))
end
end
function TRIANGLE:RemoveDraw()
for _,mark_id in pairs(self.MarkIDs)do
UTILS.RemoveMark(mark_id)
end
end
do
USERSOUND={
ClassName="USERSOUND",
}
function USERSOUND:New(UserSoundFileName)
local self=BASE:Inherit(self,BASE:New())
self.UserSoundFileName=UserSoundFileName
return self
end
function USERSOUND:SetFileName(UserSoundFileName)
self.UserSoundFileName=UserSoundFileName
return self
end
function USERSOUND:ToAll()
trigger.action.outSound(self.UserSoundFileName)
return self
end
function USERSOUND:ToCoalition(Coalition)
trigger.action.outSoundForCoalition(Coalition,self.UserSoundFileName)
return self
end
function USERSOUND:ToCountry(Country)
trigger.action.outSoundForCountry(Country,self.UserSoundFileName)
return self
end
function USERSOUND:ToGroup(Group,Delay)
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil,USERSOUND.ToGroup,{self,Group},Delay)
else
trigger.action.outSoundForGroup(Group:GetID(),self.UserSoundFileName)
end
return self
end
function USERSOUND:ToUnit(Unit,Delay)
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil,USERSOUND.ToUnit,{self,Unit},Delay)
else
trigger.action.outSoundForUnit(Unit:GetID(),self.UserSoundFileName)
end
return self
end
function USERSOUND:ToClient(Client,Delay)
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil,USERSOUND.ToClient,{self,Client},Delay)
else
trigger.action.outSoundForUnit(Client:GetID(),self.UserSoundFileName)
end
return self
end
end
do
SOUNDBASE={
ClassName="SOUNDBASE",
}
function SOUNDBASE:New()
local self=BASE:Inherit(self,BASE:New())
return self
end
function SOUNDBASE:GetSpeechTime(length,speed,isGoogle)
local maxRateRatio=3
speed=speed or 1.0
isGoogle=isGoogle or false
local speedFactor=1.0
if isGoogle then
speedFactor=speed
else
if speed~=0 then
speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1
end
if speed<0 then
speedFactor=1/speedFactor
end
end
local wpm=math.ceil(100*speedFactor)
local cps=math.floor((wpm*5)/60)
if type(length)=="string"then
length=string.len(length)
end
return math.ceil(length/cps)
end
end
do
SOUNDFILE={
ClassName="SOUNDFILE",
filename=nil,
path="l10n/DEFAULT/",
duration=3,
subtitle=nil,
subduration=0,
useSRS=false,
}
function SOUNDFILE:New(FileName,Path,Duration,UseSrs)
local self=BASE:Inherit(self,BASE:New())
self:F({FileName,Path,Duration,UseSrs})
self:SetFileName(FileName)
self:SetPlayWithSRS(UseSrs or false)
self:SetPath(Path)
self:SetDuration(Duration)
return self
end
function SOUNDFILE:SetPath(Path)
self:F({Path})
if not Path then
if self.useSRS then
self.path=lfs.tempdir().."Mission\\l10n\\DEFAULT"
else
self.path="l10n/DEFAULT/"
end
else
self.path=Path
end
local nmax=1000;local n=1
while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
self.path=self.path:sub(1,#self.path-1)
n=n+1
end
self.path=self.path.."/"
self:T("self.path="..self.path)
return self
end
function SOUNDFILE:GetPath()
local path=self.path or"l10n/DEFAULT/"
return path
end
function SOUNDFILE:SetFileName(FileName)
self.filename=FileName or"Hello World.mp3"
return self
end
function SOUNDFILE:GetFileName()
return self.filename
end
function SOUNDFILE:SetDuration(Duration)
if Duration and type(Duration)=="string"then
Duration=tonumber(Duration)
end
self.duration=Duration or 3
return self
end
function SOUNDFILE:GetDuration()
return self.duration or 3
end
function SOUNDFILE:GetName()
local path=self:GetPath()
local filename=self:GetFileName()
local name=string.format("%s%s",path,filename)
return name
end
function SOUNDFILE:SetPlayWithSRS(Switch)
self:F({Switch})
if Switch==true or Switch==nil then
self.useSRS=true
else
self.useSRS=false
end
self:T("self.useSRS="..tostring(self.useSRS))
return self
end
end
do
SOUNDTEXT={
ClassName="SOUNDTEXT",
}
function SOUNDTEXT:New(Text,Duration)
local self=BASE:Inherit(self,BASE:New())
self:SetText(Text)
self:SetDuration(Duration or MSRS.getSpeechTime(Text))
self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec",self.text,self.duration))
return self
end
function SOUNDTEXT:SetText(Text)
self.text=Text or"Hello World!"
return self
end
function SOUNDTEXT:SetDuration(Duration)
self.duration=Duration or 3
return self
end
function SOUNDTEXT:SetGender(Gender)
self.gender=Gender or"female"
return self
end
function SOUNDTEXT:SetCulture(Culture)
self.culture=Culture or"en-GB"
return self
end
function SOUNDTEXT:SetVoice(VoiceName)
self.voice=VoiceName
return self
end
end
RADIO={
ClassName="RADIO",
FileName="",
Frequency=0,
Modulation=radio.modulation.AM,
Subtitle="",
SubtitleDuration=0,
Power=100,
Loop=false,
alias=nil,
moduhasbeenset=false,
}
function RADIO:New(Positionable)
local self=BASE:Inherit(self,BASE:New())
self:F(Positionable)
if Positionable:GetPointVec2()then
self.Positionable=Positionable
return self
end
self:E({error="The passed positionable is invalid, no RADIO created!",positionable=Positionable})
return nil
end
function RADIO:SetAlias(alias)
self.alias=tostring(alias)
return self
end
function RADIO:GetAlias()
return tostring(self.alias)
end
function RADIO:SetFileName(FileName)
self:F2(FileName)
if type(FileName)=="string"then
if FileName:find(".ogg")or FileName:find(".wav")then
if not FileName:find("l10n/DEFAULT/")then
FileName="l10n/DEFAULT/"..FileName
end
self.FileName=FileName
return self
end
end
self:E({"File name invalid. Maybe something wrong with the extension?",FileName})
return self
end
function RADIO:SetFrequency(Frequency)
self:F2(Frequency)
if type(Frequency)=="number"then
self.Frequency=Frequency
self.HertzFrequency=Frequency*1000000
if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
local commandSetFrequency={
id="SetFrequency",
params={
frequency=self.HertzFrequency,
modulation=self.Modulation,
}
}
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end
return self
end
self:E({"Frequency is not a number. Frequency unchanged.",Frequency})
return self
end
function RADIO:SetModulation(Modulation)
self:F2(Modulation)
if type(Modulation)=="number"then
if Modulation==radio.modulation.AM or Modulation==radio.modulation.FM then
self.Modulation=Modulation
if self.moduhasbeenset==false and Modulation==radio.modulation.FM then
self:SetFrequency(self.Frequency)
end
self.moduhasbeenset=true
return self
end
end
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.",self.Modulation})
return self
end
function RADIO:SetPower(Power)
self:F2(Power)
if type(Power)=="number"then
self.Power=math.floor(math.abs(Power))
else
self:E({"Power is invalid. Power unchanged.",self.Power})
end
return self
end
function RADIO:SetLoop(Loop)
self:F2(Loop)
if type(Loop)=="boolean"then
self.Loop=Loop
return self
end
self:E({"Loop is invalid. Loop unchanged.",self.Loop})
return self
end
function RADIO:SetSubtitle(Subtitle,SubtitleDuration)
self:F2({Subtitle,SubtitleDuration})
if type(Subtitle)=="string"then
self.Subtitle=Subtitle
else
self.Subtitle=""
self:E({"Subtitle is invalid. Subtitle reset.",self.Subtitle})
end
if type(SubtitleDuration)=="number"then
self.SubtitleDuration=SubtitleDuration
else
self.SubtitleDuration=0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.",self.SubtitleDuration})
end
return self
end
function RADIO:NewGenericTransmission(FileName,Frequency,Modulation,Power,Loop)
self:F({FileName,Frequency,Modulation,Power})
self:SetFileName(FileName)
if Frequency then self:SetFrequency(Frequency)end
if Modulation then self:SetModulation(Modulation)end
if Power then self:SetPower(Power)end
if Loop then self:SetLoop(Loop)end
return self
end
function RADIO:NewUnitTransmission(FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop)
self:F({FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop})
self:SetFileName(FileName)
if Modulation then
self:SetModulation(Modulation)
end
if Frequency then
self:SetFrequency(Frequency)
end
if Subtitle then
self:SetSubtitle(Subtitle,SubtitleDuration or 0)
end
if Loop then
self:SetLoop(Loop)
end
return self
end
function RADIO:Broadcast(viatrigger)
self:F({viatrigger=viatrigger})
if(self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP")and(not viatrigger)then
self:T("Broadcasting from a UNIT or a GROUP")
local commandTransmitMessage={
id="TransmitMessage",
params={
file=self.FileName,
duration=self.SubtitleDuration,
subtitle=self.Subtitle,
loop=self.Loop,
}}
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else
self:T("Broadcasting from a POSITIONABLE")
trigger.action.radioTransmission(self.FileName,self.Positionable:GetPositionVec3(),self.Modulation,self.Loop,self.Frequency,self.Power,tostring(self.ID))
end
return self
end
function RADIO:StopBroadcast()
self:F()
if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then
local commandStopTransmission={id="StopTransmission",params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
trigger.action.stopRadioTransmission(tostring(self.ID))
end
return self
end
RADIOQUEUE={
ClassName="RADIOQUEUE",
Debugmode=nil,
lid=nil,
frequency=nil,
modulation=nil,
scheduler=nil,
RQid=nil,
queue={},
alias=nil,
dt=nil,
delay=nil,
Tlast=nil,
sendercoord=nil,
sendername=nil,
senderinit=nil,
power=nil,
numbers={},
checking=nil,
schedonce=false,
}
function RADIOQUEUE:New(frequency,modulation,alias)
local self=BASE:Inherit(self,BASE:New())
self.alias=alias or"My Radio"
self.lid=string.format("RADIOQUEUE %s | ",self.alias)
if frequency==nil then
self:E(self.lid.."ERROR: No frequency specified as first parameter!")
return nil
end
self.frequency=frequency*1000000
self.modulation=modulation or radio.modulation.AM
self:SetRadioPower()
self.scheduler=SCHEDULER:New()
self.scheduler:NoTrace()
return self
end
function RADIOQUEUE:Start(delay,dt)
self.delay=delay or 1
self.dt=dt or 0.01
self:I(self.lid..string.format("Starting RADIOQUEUE %s on Frequency %.2f MHz [modulation=%d] in %.1f seconds (dt=%.3f sec)",self.alias,self.frequency/1000000,self.modulation,self.delay,self.dt))
if self.schedonce then
self:_CheckRadioQueueDelayed(self.delay)
else
self.RQid=self.scheduler:Schedule(nil,RADIOQUEUE._CheckRadioQueue,{self},self.delay,self.dt)
end
return self
end
function RADIOQUEUE:Stop()
self:I(self.lid.."Stopping RADIOQUEUE.")
self.scheduler:Stop(self.RQid)
self.queue={}
return self
end
function RADIOQUEUE:SetSenderCoordinate(coordinate)
self.sendercoord=coordinate
return self
end
function RADIOQUEUE:SetSenderUnitName(name)
self.sendername=name
return self
end
function RADIOQUEUE:SetRadioPower(power)
self.power=power or 100
return self
end
function RADIOQUEUE:SetSRS(PathToSRS,Port)
local path=PathToSRS or MSRS.path
local port=Port or MSRS.port
self.msrs=MSRS:New(path,self.frequency/1000000,self.modulation)
self.msrs:SetPort(port)
return self
end
function RADIOQUEUE:SetDigit(digit,filename,duration,path,subtitle,subduration)
local transmission={}
transmission.filename=filename
transmission.duration=duration
transmission.path=path or"l10n/DEFAULT/"
transmission.subtitle=nil
transmission.subduration=nil
if type(digit)=="number"then
digit=tostring(digit)
end
self.numbers[digit]=transmission
return self
end
function RADIOQUEUE:AddTransmission(transmission)
self:F({transmission=transmission})
transmission.isplaying=false
transmission.Tstarted=nil
table.insert(self.queue,transmission)
if self.schedonce and not self.checking then
self:_CheckRadioQueueDelayed()
end
return self
end
function RADIOQUEUE:NewTransmission(filename,duration,path,tstart,interval,subtitle,subduration)
if not filename then
self:E(self.lid.."ERROR: No filename specified.")
return nil
end
if type(filename)~="string"then
self:E(self.lid.."ERROR: Filename specified is NOT a string.")
return nil
end
if not duration then
self:E(self.lid.."ERROR: No duration of transmission specified.")
return nil
end
if type(duration)~="number"then
self:E(self.lid..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={}
transmission.filename=filename
transmission.duration=duration
transmission.path=path or"l10n/DEFAULT/"
transmission.Tplay=tstart or timer.getAbsTime()
transmission.subtitle=subtitle
transmission.interval=interval or 0
if transmission.subtitle then
transmission.subduration=subduration or 5
else
transmission.subduration=nil
end
self:AddTransmission(transmission)
return transmission
end
function RADIOQUEUE:AddSoundFile(soundfile,tstart,interval)
local transmission=self:NewTransmission(soundfile:GetFileName(),soundfile.duration,soundfile:GetPath(),tstart,interval,soundfile.subtitle,soundfile.subduration)
transmission.soundfile=soundfile
return self
end
function RADIOQUEUE:AddSoundText(soundtext,tstart,interval)
local transmission=self:NewTransmission("SoundText.ogg",soundtext.duration,nil,tstart,interval,soundtext.subtitle,soundtext.subduration)
transmission.soundtext=soundtext
return self
end
function RADIOQUEUE:Number2Transmission(number,delay,interval)
local numbers=UTILS.GetCharacters(number)
local wait=0
for i=1,#numbers do
local n=numbers[i]
local transmission=UTILS.DeepCopy(self.numbers[n])
transmission.Tplay=timer.getAbsTime()+(delay or 0)
if interval and i==1 then
transmission.interval=interval
end
self:AddTransmission(transmission)
wait=wait+transmission.duration
end
return wait
end
function RADIOQUEUE:Broadcast(transmission)
if((transmission.soundfile and transmission.soundfile.useSRS)or transmission.soundtext)and self.msrs then
self:_BroadcastSRS(transmission)
return
end
local sender=self:_GetRadioSender()
local filename=string.format("%s%s",transmission.path,transmission.filename)
if sender then
self:T(self.lid..string.format("Broadcasting from aircraft %s | sender init: %s",sender:GetName(),tostring(self.senderinit)))
self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName()))
local commandFrequency={
id="SetFrequency",
params={
frequency=self.frequency,
modulation=self.modulation,
}}
sender:SetCommand(commandFrequency)
self.senderinit=true
local subtitle=nil
local duration=nil
if transmission.subtitle and transmission.subduration and transmission.subduration>0 then
subtitle=transmission.subtitle
duration=transmission.subduration
end
local commandTransmit={
id="TransmitMessage",
params={
file=filename,
duration=duration,
subtitle=subtitle,
loop=false,
}}
sender:SetCommand(commandTransmit)
if self.Debugmode then
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"")
MESSAGE:New(text,2,"RADIOQUEUE "..self.alias):ToAll():ToLog()
end
else
self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission()"))
local vec3=nil
if self.sendername then
vec3=self:_GetRadioSenderCoord()
end
if self.sendercoord and not vec3 then
vec3=self.sendercoord:GetVec3()
end
if vec3 then
self:T("Sending")
self:T({filename=filename,vec3=vec3,modulation=self.modulation,frequency=self.frequency,power=self.power})
trigger.action.radioTransmission(filename,vec3,self.modulation,false,self.frequency,self.power)
if self.Debugmode then
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"")
MESSAGE:New(string.format(text,filename,transmission.duration,transmission.subtitle or""),5,"RADIOQUEUE "..self.alias):ToAll():ToLog()
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
function RADIOQUEUE:_BroadcastSRS(transmission)
if transmission.soundfile and transmission.soundfile.useSRS then
self.msrs:PlaySoundFile(transmission.soundfile)
elseif transmission.soundtext then
self.msrs:PlaySoundText(transmission.soundtext)
end
end
function RADIOQUEUE:_CheckRadioQueueDelayed(delay)
self.checking=true
self:ScheduleOnce(delay or self.dt,RADIOQUEUE._CheckRadioQueue,self)
end
function RADIOQUEUE:_CheckRadioQueue()
self:T("_CheckRadioQueue")
if#self.queue==0 then
self.checking=false
return
end
local time=timer.getAbsTime()
local playing=false
local next=nil
local remove=nil
for i,_transmission in ipairs(self.queue)do
local transmission=_transmission
if time>=transmission.Tplay then
if transmission.isplaying then
if time>=transmission.Tstarted+transmission.duration then
transmission.isplaying=false
remove=i
self.Tlast=time
else
playing=true
end
else
local Tlast=self.Tlast
if transmission.interval==nil then
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
if next or Tlast then
break
end
end
else
end
end
if next~=nil and not playing then
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
end
if remove then
table.remove(self.queue,remove)
end
if self.schedonce then
self:_CheckRadioQueueDelayed()
end
end
function RADIOQUEUE:_GetRadioSender()
local sender=nil
if self.sendername then
sender=UNIT:FindByName(self.sendername)
if sender and sender:IsAlive()and(sender:IsAir()or sender:IsGround())then
return sender
end
end
return nil
end
function RADIOQUEUE:_GetRadioSenderCoord()
local vec3=nil
if self.sendername then
local sender=UNIT:FindByName(self.sendername)
if sender and sender:IsAlive()then
return sender:GetVec3()
end
local sender=STATIC:FindByName(self.sendername,false)
if sender then
return sender:GetVec3()
end
end
return nil
end
RADIOSPEECH={
ClassName="RADIOSPEECH",
Vocabulary={
EN={},
DE={},
RU={},
}
}
RADIOSPEECH.Vocabulary.EN={
["1"]={"1",0.25},
["2"]={"2",0.25},
["3"]={"3",0.30},
["4"]={"4",0.35},
["5"]={"5",0.35},
["6"]={"6",0.42},
["7"]={"7",0.38},
["8"]={"8",0.20},
["9"]={"9",0.32},
["10"]={"10",0.35},
["11"]={"11",0.40},
["12"]={"12",0.42},
["13"]={"13",0.38},
["14"]={"14",0.42},
["15"]={"15",0.42},
["16"]={"16",0.52},
["17"]={"17",0.59},
["18"]={"18",0.40},
["19"]={"19",0.47},
["20"]={"20",0.38},
["30"]={"30",0.29},
["40"]={"40",0.35},
["50"]={"50",0.32},
["60"]={"60",0.44},
["70"]={"70",0.48},
["80"]={"80",0.26},
["90"]={"90",0.36},
["100"]={"100",0.55},
["200"]={"200",0.55},
["300"]={"300",0.61},
["400"]={"400",0.60},
["500"]={"500",0.61},
["600"]={"600",0.65},
["700"]={"700",0.70},
["800"]={"800",0.54},
["900"]={"900",0.60},
["1000"]={"1000",0.60},
["2000"]={"2000",0.61},
["3000"]={"3000",0.64},
["4000"]={"4000",0.62},
["5000"]={"5000",0.69},
["6000"]={"6000",0.69},
["7000"]={"7000",0.75},
["8000"]={"8000",0.59},
["9000"]={"9000",0.65},
["chevy"]={"chevy",0.35},
["colt"]={"colt",0.35},
["springfield"]={"springfield",0.65},
["dodge"]={"dodge",0.35},
["enfield"]={"enfield",0.5},
["ford"]={"ford",0.32},
["pontiac"]={"pontiac",0.55},
["uzi"]={"uzi",0.28},
["degrees"]={"degrees",0.5},
["kilometers"]={"kilometers",0.65},
["km"]={"kilometers",0.65},
["miles"]={"miles",0.45},
["meters"]={"meters",0.41},
["mi"]={"miles",0.45},
["feet"]={"feet",0.29},
["br"]={"br",1.1},
["bra"]={"bra",0.3},
["returning to base"]={"returning_to_base",0.85},
["on route to ground target"]={"on_route_to_ground_target",1.05},
["intercepting bogeys"]={"intercepting_bogeys",1.00},
["engaging ground target"]={"engaging_ground_target",1.20},
["engaging bogeys"]={"engaging_bogeys",0.81},
["wheels up"]={"wheels_up",0.42},
["landing at base"]={"landing at base",0.8},
["patrolling"]={"patrolling",0.55},
["for"]={"for",0.31},
["and"]={"and",0.31},
["at"]={"at",0.3},
["dot"]={"dot",0.26},
["defender"]={"defender",0.45},
}
RADIOSPEECH.Vocabulary.RU={
["1"]={"1",0.34},
["2"]={"2",0.30},
["3"]={"3",0.23},
["4"]={"4",0.51},
["5"]={"5",0.31},
["6"]={"6",0.44},
["7"]={"7",0.25},
["8"]={"8",0.43},
["9"]={"9",0.45},
["10"]={"10",0.53},
["11"]={"11",0.66},
["12"]={"12",0.70},
["13"]={"13",0.66},
["14"]={"14",0.80},
["15"]={"15",0.65},
["16"]={"16",0.75},
["17"]={"17",0.74},
["18"]={"18",0.85},
["19"]={"19",0.80},
["20"]={"20",0.58},
["30"]={"30",0.51},
["40"]={"40",0.51},
["50"]={"50",0.67},
["60"]={"60",0.76},
["70"]={"70",0.68},
["80"]={"80",0.84},
["90"]={"90",0.71},
["100"]={"100",0.35},
["200"]={"200",0.59},
["300"]={"300",0.53},
["400"]={"400",0.70},
["500"]={"500",0.50},
["600"]={"600",0.58},
["700"]={"700",0.64},
["800"]={"800",0.77},
["900"]={"900",0.75},
["1000"]={"1000",0.87},
["2000"]={"2000",0.83},
["3000"]={"3000",0.84},
["4000"]={"4000",1.00},
["5000"]={"5000",0.77},
["6000"]={"6000",0.90},
["7000"]={"7000",0.77},
["8000"]={"8000",0.92},
["9000"]={"9000",0.87},
["градусы"]={"degrees",0.5},
["километры"]={"kilometers",0.65},
["km"]={"kilometers",0.65},
["мили"]={"miles",0.45},
["mi"]={"miles",0.45},
["метров"]={"meters",0.41},
["m"]={"meters",0.41},
["ноги"]={"feet",0.37},
["br"]={"br",1.1},
["bra"]={"bra",0.3},
["возвращение на базу"]={"returning_to_base",1.40},
["на пути к наземной цели"]={"on_route_to_ground_target",1.45},
["перехват боги"]={"intercepting_bogeys",1.22},
["поражение наземной цели"]={"engaging_ground_target",1.53},
["привлечение болотных птиц"]={"engaging_bogeys",1.68},
["колёса вверх..."]={"wheels_up",0.92},
["посадка на базу"]={"landing at base",1.04},
["патрулирование"]={"patrolling",0.96},
["для"]={"for",0.27},
["и"]={"and",0.17},
["на сайте"]={"at",0.19},
["точка"]={"dot",0.51},
["защитник"]={"defender",0.45},
}
function RADIOSPEECH:New(frequency,modulation)
local self=BASE:Inherit(self,RADIOQUEUE:New(frequency,modulation))
self.Language="EN"
self:BuildTree()
return self
end
function RADIOSPEECH:SetLanguage(Langauge)
self.Language=Langauge
end
function RADIOSPEECH:AddSentenceToSpeech(RemainingSentence,Speech,Sentence,Data)
self:I({RemainingSentence,Speech,Sentence,Data})
local Token,RemainingSentence=RemainingSentence:match("^ *([^ ]+)(.*)")
self:I({Token=Token,RemainingSentence=RemainingSentence})
if Token then
if not Speech[Token]then
Speech[Token]={}
if RemainingSentence and RemainingSentence~=""then
Speech[Token].Next={}
self:AddSentenceToSpeech(RemainingSentence,Speech[Token].Next,Sentence,Data)
else
Speech[Token].Sentence=Sentence
Speech[Token].Data=Data
end
end
end
end
function RADIOSPEECH:BuildTree()
self.Speech={}
for Language,Sentences in pairs(self.Vocabulary)do
self:I({Language=Language,Sentences=Sentences})
self.Speech[Language]={}
for Sentence,Data in pairs(Sentences)do
self:I({Sentence=Sentence,Data=Data})
self:AddSentenceToSpeech(Sentence,self.Speech[Language],Sentence,Data)
end
end
self:I({Speech=self.Speech})
return self
end
function RADIOSPEECH:SpeakWords(Sentence,Speech,Language)
local OriginalSentence=Sentence
local Word,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)")
self:I({Word=Word,Speech=Speech[Word],RemainderSentence=RemainderSentence})
if Word then
if Word~=""and tonumber(Word)==nil then
Word=Word:lower()
if Speech[Word]then
if Speech[Word].Next==nil then
self:I({Sentence=Speech[Word].Sentence,Data=Speech[Word].Data})
self:NewTransmission(Speech[Word].Data[1]..".wav",Speech[Word].Data[2],Language.."/")
else
if RemainderSentence and RemainderSentence~=""then
return self:SpeakWords(RemainderSentence,Speech[Word].Next,Language)
end
end
end
return RemainderSentence
end
return OriginalSentence
else
return""
end
end
function RADIOSPEECH:SpeakDigits(Sentence,Speech,Langauge)
local OriginalSentence=Sentence
local Digits,RemainderSentence=Sentence:match("^[., ]*([^ .,]+)(.*)")
self:I({Digits=Digits,Speech=Speech[Digits],RemainderSentence=RemainderSentence})
if Digits then
if Digits~=""and tonumber(Digits)~=nil then
local Number=tonumber(Digits)
local Multiple=nil
while Number>=0 do
if Number>1000 then
Multiple=math.floor(Number/1000)*1000
elseif Number>100 then
Multiple=math.floor(Number/100)*100
elseif Number>20 then
Multiple=math.floor(Number/10)*10
elseif Number>=0 then
Multiple=Number
end
Sentence=tostring(Multiple)
if Speech[Sentence]then
self:I({Speech=Speech[Sentence].Sentence,Data=Speech[Sentence].Data})
self:NewTransmission(Speech[Sentence].Data[1]..".wav",Speech[Sentence].Data[2],Langauge.."/")
end
Number=Number-Multiple
Number=(Number==0)and-1 or Number
end
return RemainderSentence
end
return OriginalSentence
else
return""
end
end
function RADIOSPEECH:Speak(Sentence,Language)
self:I({Sentence,Language})
local Language=Language or"EN"
self:I({Language=Language})
local Speech=self.Speech[Language]
self:I({Speech=Speech,Language=Language})
self:NewTransmission("_In.wav",0.52,Language.."/")
repeat
Sentence=self:SpeakWords(Sentence,Speech,Language)
self:I({Sentence=Sentence})
Sentence=self:SpeakDigits(Sentence,Speech,Language)
self:I({Sentence=Sentence})
until not Sentence or Sentence==""
self:NewTransmission("_Out.wav",0.28,Language.."/")
end
MSRS={
ClassName="MSRS",
lid=nil,
port=5002,
name="MSRS",
backend="srsexe",
frequencies={},
modulations={},
coalition=0,
gender="female",
culture=nil,
voice=nil,
volume=1,
speed=1,
coordinate=nil,
provider="win",
Label="ROBOT",
ConfigFileName="Moose_MSRS.lua",
ConfigFilePath="Config\\",
ConfigLoaded=false,
poptions={},
UsePowerShell=false,
}
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",
},
MicrosoftGRPC={
["Hazel"]="Hazel",
["George"]="George",
["Susan"]="Susan",
["David"]="David",
["Zira"]="Zira",
["Mark"]="Mark",
["James"]="James",
["Catherine"]="Catherine",
["Richard"]="Richard",
["Linda"]="Linda",
["Ravi"]="Ravi",
["Heera"]="Heera",
["Sean"]="Sean",
["en_GB_Hazel"]="Hazel",
["en_GB_George"]="George",
["en_GB_Susan"]="Susan",
["en_US_David"]="David",
["en_US_Zira"]="Zira",
["en_US_Mark"]="Mark",
["en_AU_James"]="James",
["en_AU_Catherine"]="Catherine",
["en_CA_Richard"]="Richard",
["en_CA_Linda"]="Linda",
["en_IN_Ravi"]="Ravi",
["en_IN_Heera"]="Heera",
["en_IR_Sean"]="Sean",
},
Google={
Standard={
["en_AU_Standard_A"]='en-AU-Standard-A',
["en_AU_Standard_B"]='en-AU-Standard-B',
["en_AU_Standard_C"]='en-AU-Standard-C',
["en_AU_Standard_D"]='en-AU-Standard-D',
["en_IN_Standard_A"]='en-IN-Standard-A',
["en_IN_Standard_B"]='en-IN-Standard-B',
["en_IN_Standard_C"]='en-IN-Standard-C',
["en_IN_Standard_D"]='en-IN-Standard-D',
["en_GB_Standard_A"]='en-GB-Standard-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_GB_Standard_N"]='en-GB-Standard-N',
["en_US_Standard_A"]='en-US-Standard-A',
["en_US_Standard_B"]='en-US-Standard-B',
["en_US_Standard_C"]='en-US-Standard-C',
["en_US_Standard_D"]='en-US-Standard-D',
["en_US_Standard_E"]='en-US-Standard-E',
["en_US_Standard_F"]='en-US-Standard-F',
["en_US_Standard_G"]='en-US-Standard-G',
["en_US_Standard_H"]='en-US-Standard-H',
["en_US_Standard_I"]='en-US-Standard-I',
["en_US_Standard_J"]='en-US-Standard-J',
["fr_FR_Standard_A"]="fr-FR-Standard-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-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",
["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',
["en_AU_Wavenet_B"]='en-AU-Wavenet-B',
["en_AU_Wavenet_C"]='en-AU-Wavenet-C',
["en_AU_Wavenet_D"]='en-AU-Wavenet-D',
["en_IN_Wavenet_A"]='en-IN-Wavenet-A',
["en_IN_Wavenet_B"]='en-IN-Wavenet-B',
["en_IN_Wavenet_C"]='en-IN-Wavenet-C',
["en_IN_Wavenet_D"]='en-IN-Wavenet-D',
["en_GB_Wavenet_A"]='en-GB-Wavenet-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_GB_Wavenet_O"]='en-GB-Wavenet-O',
["en_GB_Wavenet_N"]='en-GB-Wavenet-N',
["en_US_Wavenet_A"]='en-US-Wavenet-N',
["en_US_Wavenet_B"]='en-US-Wavenet-B',
["en_US_Wavenet_C"]='en-US-Wavenet-C',
["en_US_Wavenet_D"]='en-US-Wavenet-D',
["en_US_Wavenet_E"]='en-US-Wavenet-E',
["en_US_Wavenet_F"]='en-US-Wavenet-F',
["en_US_Wavenet_G"]='en-US-Wavenet-G',
["en_US_Wavenet_H"]='en-US-Wavenet-H',
["en_US_Wavenet_I"]='en-US-Wavenet-I',
["en_US_Wavenet_J"]='en-US-Wavenet-J',
["fr_FR_Wavenet_A"]="fr-FR-Wavenet-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-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",
["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",
},
},
}
MSRS.Backend={
SRSEXE="srsexe",
GRPC="grpc",
}
MSRS.Provider={
WINDOWS="win",
GOOGLE="gcloud",
AZURE="azure",
AMAZON="aws",
}
function MSRS.uuid()
local random=math.random
local template='yxxx-xxxxxxxxxxxx'
return string.gsub(template,'[xy]',function(c)
local v=(c=='x')and random(0,0xf)or random(8,0xb)
return string.format('%x',v)
end)
end
function MSRS:New(Path,Frequency,Modulation,Backend)
local self=BASE:Inherit(self,BASE:New())
self:F({Path,Frequency,Modulation,Backend})
Frequency=Frequency or 143
Modulation=Modulation or radio.modulation.AM
self.lid=string.format("%s-%s | ","unknown",self.version)
if not self.ConfigLoaded then
self:SetPath(Path)
self:SetPort()
self:SetFrequencies(Frequency)
self:SetModulations(Modulation)
self:SetGender()
self:SetCoalition()
self:SetLabel()
self:SetVolume()
self:SetBackend(Backend)
else
if Path then
self:SetPath(Path)
end
if Frequency then
self:SetFrequencies(Frequency)
end
if Modulation then
self:SetModulations(Modulation)
end
if Backend then
self:SetBackend(Backend)
end
end
self.lid=string.format("%s-%s | ",self.name,self.version)
if not io or not os then
self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!")
end
return self
end
function MSRS:SetBackend(Backend)
self:F({Backend=Backend})
Backend=Backend or MSRS.Backend.SRSEXE
local function Checker(back)
local ok=false
for _,_backend in pairs(MSRS.Backend)do
if tostring(back)==_backend then ok=true end
end
return ok
end
if Checker(Backend)then
self.backend=Backend
else
MESSAGE:New("ERROR: Backend "..tostring(Backend).." is not supported!",30,"MSRS",true):ToLog():ToAll()
end
return self
end
function MSRS:SetBackendGRPC()
self:F()
self:SetBackend(MSRS.Backend.GRPC)
return self
end
function MSRS:SetBackendSRSEXE()
self:F()
self:SetBackend(MSRS.Backend.SRSEXE)
return self
end
function MSRS.SetDefaultBackend(Backend)
MSRS.backend=Backend or MSRS.Backend.SRSEXE
end
function MSRS.SetDefaultBackendGRPC()
MSRS.backend=MSRS.Backend.GRPC
end
function MSRS:GetBackend()
return self.backend
end
function MSRS:SetPath(Path)
self:F({Path=Path})
self.path=Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
local n=1;local nmax=1000
while(self.path:sub(-1)=="/"or self.path:sub(-1)==[[\]])and n<=nmax do
self.path=self.path:sub(1,#self.path-1)
n=n+1
end
self:F(string.format("SRS path=%s",self:GetPath()))
return self
end
function MSRS:GetPath()
return self.path
end
function MSRS:SetVolume(Volume)
self:F({Volume=Volume})
local volume=Volume or 1
if volume>1 then volume=1 elseif volume<0 then volume=0 end
self.volume=volume
return self
end
function MSRS:GetVolume()
return self.volume
end
function MSRS:SetLabel(Label)
self:F({Label=Label})
self.Label=Label or"ROBOT"
return self
end
function MSRS:GetLabel()
return self.Label
end
function MSRS:SetPort(Port)
self:F({Port=Port})
self.port=Port or 5002
self:T(string.format("SRS port=%s",self:GetPort()))
return self
end
function MSRS:GetPort()
return self.port
end
function MSRS:SetCoalition(Coalition)
self:F({Coalition=Coalition})
self.coalition=Coalition or 0
return self
end
function MSRS:GetCoalition()
return self.coalition
end
function MSRS:SetFrequencies(Frequencies)
self:F(Frequencies)
self.frequencies=UTILS.EnsureTable(Frequencies,false)
return self
end
function MSRS:AddFrequencies(Frequencies)
self:F(Frequencies)
for _,_freq in pairs(UTILS.EnsureTable(Frequencies,false))do
self:T(self.lid..string.format("Adding frequency %s",tostring(_freq)))
table.insert(self.frequencies,_freq)
end
return self
end
function MSRS:GetFrequencies()
return self.frequencies
end
function MSRS:SetModulations(Modulations)
self:F(Modulations)
self.modulations=UTILS.EnsureTable(Modulations,false)
self:T(self.lid.."Modulations:")
self:T(self.modulations)
return self
end
function MSRS:AddModulations(Modulations)
self:F(Modulations)
for _,_mod in pairs(UTILS.EnsureTable(Modulations,false))do
table.insert(self.modulations,_mod)
end
return self
end
function MSRS:GetModulations()
return self.modulations
end
function MSRS:SetGender(Gender)
self:F({Gender=Gender})
Gender=Gender or"female"
self.gender=Gender:lower()
self:T("Setting gender to "..tostring(self.gender))
return self
end
function MSRS:SetCulture(Culture)
self:F({Culture=Culture})
self.culture=Culture
return self
end
function MSRS:SetVoice(Voice)
self:F({Voice=Voice})
self.voice=Voice
return self
end
function MSRS:SetVoiceProvider(Voice,Provider)
self:F({Voice=Voice,Provider=Provider})
self.poptions=self.poptions or{}
self.poptions[Provider or self:GetProvider()].voice=Voice
return self
end
function MSRS:SetVoiceWindows(Voice)
self:F({Voice=Voice})
self:SetVoiceProvider(Voice or"Microsoft Hazel Desktop",MSRS.Provider.WINDOWS)
return self
end
function MSRS:SetVoiceGoogle(Voice)
self:F({Voice=Voice})
self:SetVoiceProvider(Voice or MSRS.Voices.Google.Standard.en_GB_Standard_A,MSRS.Provider.GOOGLE)
return self
end
function MSRS:SetVoiceAzure(Voice)
self:F({Voice=Voice})
self:SetVoiceProvider(Voice or"en-US-AriaNeural",MSRS.Provider.AZURE)
return self
end
function MSRS:SetVoiceAmazon(Voice)
self:F({Voice=Voice})
self:SetVoiceProvider(Voice or"Brian",MSRS.Provider.AMAZON)
return self
end
function MSRS:GetVoice(Provider)
Provider=Provider or self.provider
if Provider and self.poptions[Provider]and self.poptions[Provider].voice then
return self.poptions[Provider].voice
else
return self.voice
end
end
function MSRS:SetCoordinate(Coordinate)
self:F(Coordinate)
self.coordinate=Coordinate
return self
end
function MSRS:SetGoogle(PathToCredentials)
self:F({PathToCredentials=PathToCredentials})
if PathToCredentials then
self.provider=MSRS.Provider.GOOGLE
self:SetProviderOptionsGoogle(PathToCredentials,PathToCredentials)
end
return self
end
function MSRS:SetGoogleAPIKey(APIKey)
self:F({APIKey=APIKey})
if APIKey then
self.provider=MSRS.Provider.GOOGLE
if self.poptions[MSRS.Provider.GOOGLE]then
self.poptions[MSRS.Provider.GOOGLE].key=APIKey
else
self:SetProviderOptionsGoogle(nil,APIKey)
end
end
return self
end
function MSRS:SetProvider(Provider)
BASE:F({Provider=Provider})
if self then
self.provider=Provider or MSRS.Provider.WINDOWS
return self
else
MSRS.provider=Provider or MSRS.Provider.WINDOWS
end
return
end
function MSRS:GetProvider()
return self.provider or MSRS.Provider.WINDOWS
end
function MSRS:SetProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region)
BASE:F({Provider,CredentialsFile,AccessKey,SecretKey,Region})
local option=MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region)
if self then
self.poptions=self.poptions or{}
self.poptions[Provider]=option
else
MSRS.poptions=MSRS.poptions or{}
MSRS.poptions[Provider]=option
end
return option
end
function MSRS._CreateProviderOptions(Provider,CredentialsFile,AccessKey,SecretKey,Region)
BASE:F({Provider,CredentialsFile,AccessKey,SecretKey,Region})
local option={}
option.provider=Provider
option.credentials=CredentialsFile
option.key=AccessKey
option.secret=SecretKey
option.region=Region
return option
end
function MSRS:SetProviderOptionsGoogle(CredentialsFile,AccessKey)
self:F({CredentialsFile,AccessKey})
self:SetProviderOptions(MSRS.Provider.GOOGLE,CredentialsFile,AccessKey)
return self
end
function MSRS:SetProviderOptionsAmazon(AccessKey,SecretKey,Region)
self:F({AccessKey,SecretKey,Region})
self:SetProviderOptions(MSRS.Provider.AMAZON,nil,AccessKey,SecretKey,Region)
return self
end
function MSRS:SetProviderOptionsAzure(AccessKey,Region)
self:F({AccessKey,Region})
self:SetProviderOptions(MSRS.Provider.AZURE,nil,AccessKey,nil,Region)
return self
end
function MSRS:GetProviderOptions(Provider)
return self.poptions[Provider or self.provider]or{}
end
function MSRS:SetTTSProviderGoogle()
self:F()
self:SetProvider(MSRS.Provider.GOOGLE)
return self
end
function MSRS:SetTTSProviderMicrosoft()
self:F()
self:SetProvider(MSRS.Provider.WINDOWS)
return self
end
function MSRS:SetTTSProviderAzure()
self:F()
self:SetProvider(MSRS.Provider.AZURE)
return self
end
function MSRS:SetTTSProviderAmazon()
self:F()
self:SetProvider(MSRS.Provider.AMAZON)
return self
end
function MSRS:Help()
self:F()
local path=self:GetPath()
local exe="DCS-SR-ExternalAudio.exe"
local filename=os.getenv('TMP').."\\MSRS-help-"..MSRS.uuid()..".txt"
local command=string.format("%s/%s --help > %s",path,exe,filename)
os.execute(command)
local f=assert(io.open(filename,"rb"))
local data=f:read("*all")
f:close()
env.info("SRS help output:")
env.info("======================================================================")
env.info(data)
env.info("======================================================================")
return self
end
function MSRS:PlaySoundFile(Soundfile,Delay)
self:F({Soundfile,Delay})
local soundfile=Soundfile:GetName()
local exists=UTILS.FileExists(soundfile)
if not exists then
self:E("ERROR: MSRS sound file does not exist! File="..soundfile)
return self
end
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlaySoundFile,self,Soundfile,0)
else
local command=self:_GetCommand()
command=command..' --file="'..tostring(soundfile)..'"'
command=string.gsub(command,"--ssml","-h")
self:_ExecCommand(command)
end
return self
end
function MSRS:PlaySoundText(SoundText,Delay)
self:F({SoundText,Delay})
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlaySoundText,self,SoundText,0)
else
if self.backend==MSRS.Backend.GRPC then
self:_DCSgRPCtts(SoundText.text,nil,SoundText.gender,SoundText.culture,SoundText.voice,SoundText.volume,SoundText.label,SoundText.coordinate)
else
local command=self:_GetCommand(nil,nil,nil,SoundText.gender,SoundText.voice,SoundText.culture,SoundText.volume,SoundText.speed)
command=command..string.format(" --text=\"%s\"",tostring(SoundText.text))
self:_ExecCommand(command)
end
end
return self
end
function MSRS:PlayText(Text,Delay,Coordinate)
self:F({Text,Delay,Coordinate})
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlayText,self,Text,nil,Coordinate)
else
if self.backend==MSRS.Backend.GRPC then
self:T(self.lid.."Transmitting")
self:_DCSgRPCtts(Text,nil,nil,nil,nil,nil,nil,Coordinate)
else
self:PlayTextExt(Text,Delay,nil,nil,nil,nil,nil,nil,nil,Coordinate)
end
end
return self
end
function MSRS:PlayTextExt(Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate)
self:T({Text,Delay,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate})
if Delay and Delay>0 then
self:ScheduleOnce(Delay,self.PlayTextExt,self,Text,0,Frequencies,Modulations,Gender,Culture,Voice,Volume,Label,Coordinate)
else
Frequencies=Frequencies or self:GetFrequencies()
Modulations=Modulations or self:GetModulations()
if self.backend==MSRS.Backend.SRSEXE then
local command=self:_GetCommand(UTILS.EnsureTable(Frequencies,false),UTILS.EnsureTable(Modulations,false),nil,Gender,Voice,Culture,Volume,nil,nil,Label,Coordinate)
command=command..string.format(" --text=\"%s\"",tostring(Text))
self:_ExecCommand(command)
elseif self.backend==MSRS.Backend.GRPC then
self:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate)
end
end
return self
end
function MSRS:PlayTextFile(TextFile,Delay)
self:F({TextFile,Delay})
if Delay and Delay>0 then
self:ScheduleOnce(Delay,MSRS.PlayTextFile,self,TextFile,0)
else
local exists=UTILS.FileExists(TextFile)
if not exists then
self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile))
return self
end
local command=self:_GetCommand()
command=command..string.format(" --textFile=\"%s\"",tostring(TextFile))
self:T(string.format("MSRS TextFile command=%s",command))
local l=string.len(command)
self:T(string.format("Command length=%d",l))
self:_ExecCommand(command)
end
return self
end
function MSRS:_GetLatLongAlt(Coordinate)
self:F({Coordinate=Coordinate})
local lat=0.0
local lon=0.0
local alt=0.0
if Coordinate then
lat,lon,alt=coord.LOtoLL(Coordinate)
end
return lat,lon,math.floor(alt)
end
function MSRS:_GetCommand(freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate)
self:F({freqs,modus,coal,gender,voice,culture,volume,speed,port,label,coordinate})
local path=self:GetPath()
local exe="DCS-SR-ExternalAudio.exe"
local fullPath=string.format("%s\\%s",path,exe)
freqs=table.concat(freqs or self.frequencies,",")
modus=table.concat(modus or self.modulations,",")
coal=coal or self.coalition
gender=gender or self.gender
voice=voice or self:GetVoice(self.provider)or self.voice
culture=culture or self.culture
volume=volume or self.volume
speed=speed or self.speed
port=port or self.port
label=label or self.Label
coordinate=coordinate or self.coordinate
modus=modus:gsub("0","AM")
modus=modus:gsub("1","FM")
local 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 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 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: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 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'))
script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n'))
script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n',filename))
script:write(string.format('Set WinScriptHost = Nothing'))
script:close()
self:T("MSRS vbs file created to start batch="..filenvbs)
local runvbs=string.format('cscript.exe //Nologo //B "%s"',filenvbs)
self:T("MSRS execute VBS command="..runvbs)
res=os.execute(runvbs)
timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
timer.scheduleFunction(os.remove,filenvbs,timer.getTime()+1)
self:T("MSRS vbs and batch file removed")
elseif 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)
res=os.execute(command)
timer.scheduleFunction(os.remove,filename,timer.getTime()+1)
end
return res
end
function MSRS:_DCSgRPCtts(Text,Frequencies,Gender,Culture,Voice,Volume,Label,Coordinate)
self: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()
options.plaintext=Text
options.srsClientName=Label or self.Label
if self.coordinate then
options.position={}
options.position.lat,options.position.lon,options.position.alt=self:_GetLatLongAlt(self.coordinate)
end
options.coalition=UTILS.GetCoalitionName(self.coalition):lower()
local provider=self.provider or MSRS.Provider.WINDOWS
options.provider={}
options.provider[provider]=self:GetProviderOptions(provider)
Voice=Voice or self:GetVoice(self.provider)or self.voice
if Voice then
options.provider[provider].voice=Voice
else
local preTag,genderProp,langProp,postTag='','','',''
local gender=""
if self.gender then
gender=string.format(' gender=\"%s\"',self.gender)
end
local language=""
if self.culture then
language=string.format(' language=\"%s\"',self.culture)
end
if self.gender or self.culture then
ssml=string.format("<voice%s%s>%s</voice>",gender,language,Text)
end
end
for _,freq in pairs(Frequencies)do
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
function MSRS:LoadConfigFile(Path,Filename)
if lfs==nil then
env.info("*****Note - lfs and os need to be desanitized for MSRS to work!")
return false
end
local path=Path or lfs.writedir()..MSRS.ConfigFilePath
local file=Filename or MSRS.ConfigFileName or"Moose_MSRS.lua"
local pathandfile=path..file
local filexsists=UTILS.FileExists(pathandfile)
if filexsists and not MSRS.ConfigLoaded then
env.info("FF reading config file")
assert(loadfile(path..file))()
if MSRS_Config then
local Self=self or MSRS
Self.path=MSRS_Config.Path or"C:\\Program Files\\DCS-SimpleRadio-Standalone"
Self.port=MSRS_Config.Port or 5002
Self.backend=MSRS_Config.Backend or MSRS.Backend.SRSEXE
Self.frequencies=MSRS_Config.Frequency or{127,243}
Self.modulations=MSRS_Config.Modulation or{0,0}
Self.coalition=MSRS_Config.Coalition or 0
if MSRS_Config.Coordinate then
Self.coordinate=COORDINATE:New(MSRS_Config.Coordinate[1],MSRS_Config.Coordinate[2],MSRS_Config.Coordinate[3])
end
Self.culture=MSRS_Config.Culture or"en-GB"
Self.gender=MSRS_Config.Gender or"male"
Self.Label=MSRS_Config.Label or"MSRS"
Self.voice=MSRS_Config.Voice
Self.provider=MSRS_Config.Provider or MSRS.Provider.WINDOWS
for _,provider in pairs(MSRS.Provider)do
if MSRS_Config[provider]then
Self.poptions[provider]=MSRS_Config[provider]
end
end
Self.ConfigLoaded=true
end
env.info("MSRS - Successfully loaded default configuration from disk!",false)
end
if not filexsists then
env.info("MSRS - Cannot find default configuration file!",false)
return false
end
return true
end
function MSRS.getSpeechTime(length,speed,isGoogle)
local maxRateRatio=3
speed=speed or 1.0
isGoogle=isGoogle or false
local speedFactor=1.0
if isGoogle then
speedFactor=speed
else
if speed~=0 then
speedFactor=math.abs(speed)*(maxRateRatio-1)/10+1
end
if speed<0 then
speedFactor=1/speedFactor
end
end
local wpm=math.ceil(100*speedFactor)
local cps=math.floor((wpm*5)/60)
if type(length)=="string"then
length=string.len(length)
end
return length/cps
end
MSRSQUEUE={
ClassName="MSRSQUEUE",
Debugmode=nil,
lid=nil,
queue={},
alias=nil,
dt=nil,
Tlast=nil,
checking=nil,
}
function MSRSQUEUE:New(alias)
local self=BASE:Inherit(self,BASE:New())
self.alias=alias or"My Radio"
self.dt=1.0
self.lid=string.format("MSRSQUEUE %s | ",self.alias)
return self
end
function MSRSQUEUE:Clear()
self:T(self.lid.."Clearing MSRSQUEUE")
self.queue={}
return self
end
function MSRSQUEUE:AddTransmission(transmission)
transmission.isplaying=false
transmission.Tstarted=nil
table.insert(self.queue,transmission)
if not self.checking then
self:_CheckRadioQueue()
end
return self
end
function MSRSQUEUE:SetTransmitOnlyWithPlayers(Switch)
self.TransmitOnlyWithPlayers=Switch
if Switch==false or Switch==nil then
if self.PlayerSet then
self.PlayerSet:FilterStop()
end
self.PlayerSet=nil
else
self.PlayerSet=SET_CLIENT:New():FilterStart()
end
return self
end
function MSRSQUEUE:NewTransmission(text,duration,msrs,tstart,interval,subgroups,subtitle,subduration,frequency,modulation,gender,culture,voice,volume,label,coordinate)
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
end
end
if not text then
self:E(self.lid.."ERROR: No text specified.")
return nil
end
if type(text)~="string"then
self:E(self.lid.."ERROR: Text specified is NOT a string.")
return nil
end
local transmission={}
transmission.text=text
transmission.duration=duration or MSRS.getSpeechTime(text)
transmission.msrs=msrs
transmission.Tplay=tstart or timer.getAbsTime()
transmission.subtitle=subtitle
transmission.interval=interval or 0
transmission.frequency=frequency or msrs.frequencies
transmission.modulation=modulation or msrs.modulations
transmission.subgroups=subgroups
if transmission.subtitle then
transmission.subduration=subduration or transmission.duration
else
transmission.subduration=0
end
transmission.gender=gender or msrs.gender
transmission.culture=culture or msrs.culture
transmission.voice=voice or msrs.voice
transmission.volume=volume or msrs.volume
transmission.label=label or msrs.Label
transmission.coordinate=coordinate or msrs.coordinate
self:AddTransmission(transmission)
return transmission
end
function MSRSQUEUE:Broadcast(transmission)
self:T(self.lid.."Broadcast")
if transmission.frequency then
transmission.msrs:PlayTextExt(transmission.text,nil,transmission.frequency,transmission.modulation,transmission.gender,transmission.culture,transmission.voice,transmission.volume,transmission.label,transmission.coordinate)
else
transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate)
end
local function texttogroup(gid)
trigger.action.outTextForGroup(gid,transmission.subtitle,transmission.subduration,true)
end
if transmission.subgroups and#transmission.subgroups>0 then
for _,_group in pairs(transmission.subgroups)do
local group=_group
if group and group:IsAlive()then
local gid=group:GetID()
self:ScheduleOnce(4,texttogroup,gid)
end
end
end
end
function MSRSQUEUE:CalcTransmisstionDuration()
local Tnow=timer.getAbsTime()
local T=0
for _,_transmission in pairs(self.queue)do
local transmission=_transmission
if transmission.isplaying then
local dt=Tnow-transmission.Tstarted
T=T+transmission.duration-dt
else
T=T+transmission.duration
end
end
return T
end
function MSRSQUEUE:_CheckRadioQueue(delay)
local N=#self.queue
self:T2(self.lid..string.format("Check radio queue %s: delay=%.3f sec, N=%d, checking=%s",self.alias,delay or 0,N,tostring(self.checking)))
if delay and delay>0 then
self:ScheduleOnce(delay,MSRSQUEUE._CheckRadioQueue,self)
self.checking=true
else
if N==0 then
self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias))
self.checking=false
return
end
local time=timer.getAbsTime()
self.checking=true
local dt=self.dt
local playing=false
local next=nil
local remove=nil
for i,_transmission in ipairs(self.queue)do
local transmission=_transmission
if time>=transmission.Tplay then
if transmission.isplaying then
if time>=transmission.Tstarted+transmission.duration then
transmission.isplaying=false
remove=i
self.Tlast=time
else
playing=true
dt=transmission.duration-(time-transmission.Tstarted)
end
else
local Tlast=self.Tlast
if transmission.interval==nil then
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
if next or Tlast then
break
end
end
else
end
end
if next~=nil and not playing then
self:T(self.lid..string.format("Broadcasting text=\"%s\" at T=%.3f",next.text,time))
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
dt=next.duration
end
if remove then
table.remove(self.queue,remove)
N=N-1
if#self.queue==0 then
self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking",self.alias))
self.checking=false
return
end
end
self:_CheckRadioQueue(dt)
end
end
MSRS.LoadConfigFile()
_EVENTDISPATCHER=EVENT:New()
_SCHEDULEDISPATCHER=SCHEDULEDISPATCHER:New()
_DATABASE=DATABASE:New()
_SETTINGS=SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn()
_DATABASE:_RegisterCargos()
_DATABASE:_RegisterZones()
_DATABASE:_RegisterAirbases()
BASE:I("Checking de-sanitization of os, io and lfs:")
local __na=false
if os then
BASE:I("- os available")
else
BASE:I("- os NOT available! Some functions may not work.")
__na=true
end
if io then
BASE:I("- io available")
else
BASE:I("- io NOT available! Some functions may not work.")
__na=true
end
if lfs then
BASE:I("- lfs available")
else
BASE:I("- lfs NOT available! Some functions may not work.")
__na=true
end
if __na then
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
end
BASE.ServerName="Unknown"
if lfs and loadfile then
local serverfile=lfs.writedir()..'Config/serverSettings.lua'
if UTILS.FileExists(serverfile)then
loadfile(serverfile)()
if cfg and cfg.name then
BASE.ServerName=cfg.name
end
end
BASE.ServerName=BASE.ServerName or"Unknown"
BASE:I("Server Name: "..tostring(BASE.ServerName))
end
BASE:TraceOnOff(false)
env.info('*** MOOSE INCLUDE END *** ')
