diff --git a/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/MOP-100 - MARKEROPS_BASE - Basic Demo.lua b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/MOP-100 - MARKEROPS_BASE - Basic Demo.lua new file mode 100644 index 0000000000..0ee97708dc --- /dev/null +++ b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/MOP-100 - MARKEROPS_BASE - Basic Demo.lua @@ -0,0 +1,86 @@ +------------------------------------------------------------------------- +-- MOP-100 - MARKEROPS_BASE - Basic Demo +------------------------------------------------------------------------- +-- Documentation +-- +-- MARKEROPS_BASE: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.MarkerOps_Base.html +-- +------------------------------------------------------------------------- +-- On the F10, call a tanker to start from the carrier. It will fly to +-- an initial zone. Set a marker on the F10 map with keyword "TankerDemo". +-- The Tanker will fly there. Set a marker on the F10 map with keywords +-- "TankerDemo RTB". The tanke will RTB to the carrier. +------------------------------------------------------------------------- +-- Date: May 2021 +------------------------------------------------------------------------- + +-- globals +mytanker = nil +tankergroup = nil +TankerAuftrag = nil + +function menucalltanker() + + if not mytanker then + -- new MARKEROPS_BASE object + mytanker = MARKEROPS_BASE:New("TankerDemo",{"RTB"}) -- Core.MarkerOps_Base#MARKEROPS_BASE + -- start FlightGroup + tankergroup = FLIGHTGROUP:New("Tanker") + tankergroup:SetHomebase(AIRBASE:FindByName("Truman")) + tankergroup:SetDefaultRadio(245,"AM",false) + tankergroup:SetDespawnAfterLanding() + tankergroup:SwitchTACAN(45, "TKR", 1, "X") + tankergroup:SetDefaultCallsign(CALLSIGN.Tanker.Texaco,1) + -- Mission + local InitialHold = ZONE:New("Initial Hold"):GetCoordinate() + TankerAuftrag = AUFTRAG:NewTANKER(InitialHold,18000,UTILS.KnotsToAltKIAS(220,18000),90,20,0) + TankerAuftrag:SetMissionRange(500) + tankergroup:AddMission(TankerAuftrag) + else + local status = tankergroup:GetState() + local m = MESSAGE:New(string.format("Tanker %s ops in status: %s", mytanker.Tag, status),10,"Info",true):ToAll() + end + + -- Handler function + local function Handler(Keywords,Coord) + + local MustRTB = false + for _,_word in pairs (Keywords) do + if string.lower(_word) == "rtb" then + MustRTB = true + end + end + + -- cancel current Auftrag + TankerAuftrag:Cancel() + + -- check if we need to RTB + if MustRTB then + tankergroup:RTB(AIRBASE:FindByName("Truman")) + else + -- no, fly to coordinate of marker + local auftrag = AUFTRAG:NewTANKER(Coord,18000,UTILS.KnotsToAltKIAS(220,18000),90,20,0) + auftrag:SetMissionRange(500) + tankergroup:AddMission(auftrag) + TankerAuftrag = auftrag + end + end + + -- Event functions + function mytanker:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord) + local m = MESSAGE:New(string.format("Tanker %s Mark Added.", self.Tag),10,"Info",true):ToAll() + Handler(Keywords,Coord) + end + + function mytanker:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord) + local m = MESSAGE:New(string.format("Tanker %s Mark Changed.", self.Tag),10,"Info",true):ToAll() + Handler(Keywords,Coord) + end + + function mytanker:OnAfterMarkDeleted(From,Event,To) + local m = MESSAGE:New(string.format("Tanker %s Mark Deleted.", self.Tag),10,"Info",true):ToAll() + end +end + +MenuTop = MENU_COALITION:New( coalition.side.BLUE,"Call Tanker") +MenuTanker = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Start Tanker",MenuTop,menucalltanker) diff --git a/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/MOP-100 - MARKEROPS_BASE - Basic Demo.miz b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/MOP-100 - MARKEROPS_BASE - Basic Demo.miz new file mode 100644 index 0000000000..6f651e3786 Binary files /dev/null and b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/MOP-100 - MARKEROPS_BASE - Basic Demo.miz differ diff --git a/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/_unpacked/l10n/DEFAULT/MarkerOps_Base_Demo.lua b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/_unpacked/l10n/DEFAULT/MarkerOps_Base_Demo.lua new file mode 100644 index 0000000000..0ee97708dc --- /dev/null +++ b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/_unpacked/l10n/DEFAULT/MarkerOps_Base_Demo.lua @@ -0,0 +1,86 @@ +------------------------------------------------------------------------- +-- MOP-100 - MARKEROPS_BASE - Basic Demo +------------------------------------------------------------------------- +-- Documentation +-- +-- MARKEROPS_BASE: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.MarkerOps_Base.html +-- +------------------------------------------------------------------------- +-- On the F10, call a tanker to start from the carrier. It will fly to +-- an initial zone. Set a marker on the F10 map with keyword "TankerDemo". +-- The Tanker will fly there. Set a marker on the F10 map with keywords +-- "TankerDemo RTB". The tanke will RTB to the carrier. +------------------------------------------------------------------------- +-- Date: May 2021 +------------------------------------------------------------------------- + +-- globals +mytanker = nil +tankergroup = nil +TankerAuftrag = nil + +function menucalltanker() + + if not mytanker then + -- new MARKEROPS_BASE object + mytanker = MARKEROPS_BASE:New("TankerDemo",{"RTB"}) -- Core.MarkerOps_Base#MARKEROPS_BASE + -- start FlightGroup + tankergroup = FLIGHTGROUP:New("Tanker") + tankergroup:SetHomebase(AIRBASE:FindByName("Truman")) + tankergroup:SetDefaultRadio(245,"AM",false) + tankergroup:SetDespawnAfterLanding() + tankergroup:SwitchTACAN(45, "TKR", 1, "X") + tankergroup:SetDefaultCallsign(CALLSIGN.Tanker.Texaco,1) + -- Mission + local InitialHold = ZONE:New("Initial Hold"):GetCoordinate() + TankerAuftrag = AUFTRAG:NewTANKER(InitialHold,18000,UTILS.KnotsToAltKIAS(220,18000),90,20,0) + TankerAuftrag:SetMissionRange(500) + tankergroup:AddMission(TankerAuftrag) + else + local status = tankergroup:GetState() + local m = MESSAGE:New(string.format("Tanker %s ops in status: %s", mytanker.Tag, status),10,"Info",true):ToAll() + end + + -- Handler function + local function Handler(Keywords,Coord) + + local MustRTB = false + for _,_word in pairs (Keywords) do + if string.lower(_word) == "rtb" then + MustRTB = true + end + end + + -- cancel current Auftrag + TankerAuftrag:Cancel() + + -- check if we need to RTB + if MustRTB then + tankergroup:RTB(AIRBASE:FindByName("Truman")) + else + -- no, fly to coordinate of marker + local auftrag = AUFTRAG:NewTANKER(Coord,18000,UTILS.KnotsToAltKIAS(220,18000),90,20,0) + auftrag:SetMissionRange(500) + tankergroup:AddMission(auftrag) + TankerAuftrag = auftrag + end + end + + -- Event functions + function mytanker:OnAfterMarkAdded(From,Event,To,Text,Keywords,Coord) + local m = MESSAGE:New(string.format("Tanker %s Mark Added.", self.Tag),10,"Info",true):ToAll() + Handler(Keywords,Coord) + end + + function mytanker:OnAfterMarkChanged(From,Event,To,Text,Keywords,Coord) + local m = MESSAGE:New(string.format("Tanker %s Mark Changed.", self.Tag),10,"Info",true):ToAll() + Handler(Keywords,Coord) + end + + function mytanker:OnAfterMarkDeleted(From,Event,To) + local m = MESSAGE:New(string.format("Tanker %s Mark Deleted.", self.Tag),10,"Info",true):ToAll() + end +end + +MenuTop = MENU_COALITION:New( coalition.side.BLUE,"Call Tanker") +MenuTanker = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Start Tanker",MenuTop,menucalltanker) diff --git a/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/_unpacked/l10n/DEFAULT/Moose_dev_small.lua b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/_unpacked/l10n/DEFAULT/Moose_dev_small.lua new file mode 100644 index 0000000000..71ade2a43b --- /dev/null +++ b/MOP - MarkerOps_Base/MOP-100 - MARKEROPS_BASE/_unpacked/l10n/DEFAULT/Moose_dev_small.lua @@ -0,0 +1,78101 @@ +env.info('*** MOOSE GITHUB Commit Hash ID: 2021-05-06T07:30:06.0000000Z-9eba6d607c4f2c10c460c7bf9c71d88affa5ff6c ***') +env.info('*** MOOSE STATIC INCLUDE START *** ') +ENUMS={} +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, +Auto=3221225470, +AutoDCS=1073741822, +AnyAG=2956984318, +AnyAA=264241152, +AnyUnguided=2952822768, +AnyGuided=268402702, +} +ENUMS.MissionTask={ +NOTHING="Nothing", +AFAC="AFAC", +ANTISHIPSTRIKE="Antiship Strike", +AWACS="AWACS", +CAP="CAP", +CAS="CAS", +ESCORT="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', +} +env.setErrorMessageBoxEnabled(false) +routines={} +routines.majorVersion=3 +routines.minorVersion=3 +routines.build=22 +routines.utils={} +routines.utils.round=function(number,decimals) +local power=10^decimals +return math.floor(number*power)/power +end +routines.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 +routines.utils.oneLineSerialize=function(tbl) +lookup_table={} +local function _Serialize(tbl) +if type(tbl)=='table'then +if lookup_table[tbl]then +return lookup_table[object] +end +local tbl_str={} +lookup_table[tbl]=tbl_str +tbl_str[#tbl_str+1]='{' +for ind,val in pairs(tbl)do +local ind_str={} +if type(ind)=="number"then +ind_str[#ind_str+1]='[' +ind_str[#ind_str+1]=tostring(ind) +ind_str[#ind_str+1]=']=' +else +ind_str[#ind_str+1]='[' +ind_str[#ind_str+1]=routines.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]=routines.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 +else +end +end +tbl_str[#tbl_str+1]='}' +return table.concat(tbl_str) +else +if type(tbl)=='string'then +return tbl +else +return tostring(tbl) +end +end +end +local objectreturn=_Serialize(tbl) +return objectreturn +end +routines.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)=='table')or(type(s)=='userdata'))then +return tostring(s) +elseif type(s)=='string'then +s=string.format('%s',s:gsub("%%","%%%%")) +return s +end +end +end +routines.utils.toDegree=function(angle) +return angle*180/math.pi +end +routines.utils.toRadian=function(angle) +return angle*math.pi/180 +end +routines.utils.metersToNM=function(meters) +return meters/1852 +end +routines.utils.metersToFeet=function(meters) +return meters/0.3048 +end +routines.utils.NMToMeters=function(NM) +return NM*1852 +end +routines.utils.feetToMeters=function(feet) +return feet*0.3048 +end +routines.utils.mpsToKnots=function(mps) +return mps*3600/1852 +end +routines.utils.mpsToKmph=function(mps) +return mps*3.6 +end +routines.utils.knotsToMps=function(knots) +return knots*1852/3600 +end +routines.utils.kmphToMps=function(kmph) +return kmph/3.6 +end +function routines.utils.makeVec2(Vec3) +if Vec3.z then +return{x=Vec3.x,y=Vec3.z} +else +return{x=Vec3.x,y=Vec3.y} +end +end +function routines.utils.makeVec3(Vec2,y) +if not Vec2.z then +if not y then +y=0 +end +return{x=Vec2.x,y=y,z=Vec2.y} +else +return{x=Vec2.x,y=Vec2.y,z=Vec2.z} +end +end +function routines.utils.makeVec3GL(Vec2,offset) +local adj=offset or 0 +if not Vec2.z then +return{x=Vec2.x,y=(land.getHeight(Vec2)+adj),z=Vec2.y} +else +return{x=Vec2.x,y=(land.getHeight({x=Vec2.x,y=Vec2.z})+adj),z=Vec2.z} +end +end +routines.utils.zoneToVec3=function(zone) +local new={} +if type(zone)=='table'and zone.point then +new.x=zone.point.x +new.y=zone.point.y +new.z=zone.point.z +return new +elseif type(zone)=='string'then +zone=trigger.misc.getZone(zone) +if zone then +new.x=zone.point.x +new.y=zone.point.y +new.z=zone.point.z +return new +end +end +end +function routines.utils.getDir(vec,point) +local dir=math.atan2(vec.z,vec.x) +dir=dir+routines.getNorthCorrection(point) +if dir<0 then +dir=dir+2*math.pi +end +return dir +end +function routines.utils.get2DDist(point1,point2) +point1=routines.utils.makeVec3(point1) +point2=routines.utils.makeVec3(point2) +return routines.vec.mag({x=point1.x-point2.x,y=0,z=point1.z-point2.z}) +end +function routines.utils.get3DDist(point1,point2) +return routines.vec.mag({x=point1.x-point2.x,y=point1.y-point2.y,z=point1.z-point2.z}) +end +routines.vec={} +routines.vec.add=function(vec1,vec2) +return{x=vec1.x+vec2.x,y=vec1.y+vec2.y,z=vec1.z+vec2.z} +end +routines.vec.sub=function(vec1,vec2) +return{x=vec1.x-vec2.x,y=vec1.y-vec2.y,z=vec1.z-vec2.z} +end +routines.vec.scalarMult=function(vec,mult) +return{x=vec.x*mult,y=vec.y*mult,z=vec.z*mult} +end +routines.vec.scalar_mult=routines.vec.scalarMult +routines.vec.dp=function(vec1,vec2) +return vec1.x*vec2.x+vec1.y*vec2.y+vec1.z*vec2.z +end +routines.vec.cp=function(vec1,vec2) +return{x=vec1.y*vec2.z-vec1.z*vec2.y,y=vec1.z*vec2.x-vec1.x*vec2.z,z=vec1.x*vec2.y-vec1.y*vec2.x} +end +routines.vec.mag=function(vec) +return(vec.x^2+vec.y^2+vec.z^2)^0.5 +end +routines.vec.getUnitVec=function(vec) +local mag=routines.vec.mag(vec) +return{x=vec.x/mag,y=vec.y/mag,z=vec.z/mag} +end +routines.vec.rotateVec2=function(vec2,theta) +return{x=vec2.x*math.cos(theta)-vec2.y*math.sin(theta),y=vec2.x*math.sin(theta)+vec2.y*math.cos(theta)} +end +routines.tostringMGRS=function(MGRS,acc) +if acc==0 then +return MGRS.UTMZone..' '..MGRS.MGRSDigraph +else +return MGRS.UTMZone..' '..MGRS.MGRSDigraph..' '..string.format('%0'..acc..'d',routines.utils.round(MGRS.Easting/(10^(5-acc)),0)) +..' '..string.format('%0'..acc..'d',routines.utils.round(MGRS.Northing/(10^(5-acc)),0)) +end +end +routines.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=routines.utils.round((oldLatMin-latMin)*60,acc) +local oldLonMin=lonMin +lonMin=math.floor(lonMin) +local lonSec=routines.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 +if acc<=0 then +secFrmtStr='%02d' +else +local width=3+acc +secFrmtStr='%0'..width..'.'..acc..'f' +end +return string.format('%02d',latDeg)..' '..string.format('%02d',latMin)..'\' '..string.format(secFrmtStr,latSec)..'"'..latHemi..' ' +..string.format('%02d',lonDeg)..' '..string.format('%02d',lonMin)..'\' '..string.format(secFrmtStr,lonSec)..'"'..lonHemi +else +latMin=routines.utils.round(latMin,acc) +lonMin=routines.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('%02d',latDeg)..' '..string.format(minFrmtStr,latMin)..'\''..latHemi..' ' +..string.format('%02d',lonDeg)..' '..string.format(minFrmtStr,lonMin)..'\''..lonHemi +end +end +routines.tostringBR=function(az,dist,alt,metric) +az=routines.utils.round(routines.utils.toDegree(az),0) +if metric then +dist=routines.utils.round(dist/1000,2) +else +dist=routines.utils.round(routines.utils.metersToNM(dist),2) +end +local s=string.format('%03d',az)..' for '..dist +if alt then +if metric then +s=s..' at '..routines.utils.round(alt,0) +else +s=s..' at '..routines.utils.round(routines.utils.metersToFeet(alt),0) +end +end +return s +end +routines.getNorthCorrection=function(point) +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 +do +local idNum=0 +routines.addEventHandler=function(f) +local handler={} +idNum=idNum+1 +handler.id=idNum +handler.f=f +handler.onEvent=function(self,event) +self.f(event) +end +world.addEventHandler(handler) +end +routines.removeEventHandler=function(id) +for key,handler in pairs(world.eventHandlers)do +if handler.id and handler.id==id then +world.eventHandlers[key]=nil +return true +end +end +return false +end +end +function routines.getRandPointInCircle(point,radius,innerRadius) +local theta=2*math.pi*math.random() +local rad=math.random()+math.random() +if rad>1 then +rad=2-rad +end +local radMult +if innerRadius and innerRadius<=radius then +radMult=(radius-innerRadius)*rad+innerRadius +else +radMult=radius*rad +end +if not point.z then +point.z=point.y +end +local rndCoord +if radius>0 then +rndCoord={x=math.cos(theta)*radMult+point.x,y=math.sin(theta)*radMult+point.z} +else +rndCoord={x=point.x,y=point.z} +end +return rndCoord +end +routines.goRoute=function(group,path) +local misTask={ +id='Mission', +params={ +route={ +points=routines.utils.deepCopy(path), +}, +}, +} +if type(group)=='string'then +group=Group.getByName(group) +end +local groupCon=group:getController() +if groupCon then +groupCon:setTask(misTask) +return true +end +Controller.setTask(groupCon,misTask) +return false +end +routines.ground={} +routines.fixedWing={} +routines.heli={} +routines.ground.buildWP=function(point,overRideForm,overRideSpeed) +local wp={} +wp.x=point.x +if point.z then +wp.y=point.z +else +wp.y=point.y +end +local form,speed +if point.speed and not overRideSpeed then +wp.speed=point.speed +elseif type(overRideSpeed)=='number'then +wp.speed=overRideSpeed +else +wp.speed=routines.utils.kmphToMps(20) +end +if point.form and not overRideForm then +form=point.form +else +form=overRideForm +end +if not form then +wp.action='Cone' +else +form=string.lower(form) +if form=='off_road'or form=='off road'then +wp.action='Off Road' +elseif form=='on_road'or form=='on road'then +wp.action='On Road' +elseif form=='rank'or form=='line_abrest'or form=='line abrest'or form=='lineabrest'then +wp.action='Rank' +elseif form=='cone'then +wp.action='Cone' +elseif form=='diamond'then +wp.action='Diamond' +elseif form=='vee'then +wp.action='Vee' +elseif form=='echelon_left'or form=='echelon left'or form=='echelonl'then +wp.action='EchelonL' +elseif form=='echelon_right'or form=='echelon right'or form=='echelonr'then +wp.action='EchelonR' +else +wp.action='Cone' +end +end +wp.type='Turning Point' +return wp +end +routines.fixedWing.buildWP=function(point,WPtype,speed,alt,altType) +local wp={} +wp.x=point.x +if point.z then +wp.y=point.z +else +wp.y=point.y +end +if alt and type(alt)=='number'then +wp.alt=alt +else +wp.alt=2000 +end +if altType then +altType=string.lower(altType) +if altType=='radio'or'agl'then +wp.alt_type='RADIO' +elseif altType=='baro'or'asl'then +wp.alt_type='BARO' +end +else +wp.alt_type='RADIO' +end +if point.speed then +speed=point.speed +end +if point.type then +WPtype=point.type +end +if not speed then +wp.speed=routines.utils.kmphToMps(500) +else +wp.speed=speed +end +if not WPtype then +wp.action='Turning Point' +else +WPtype=string.lower(WPtype) +if WPtype=='flyover'or WPtype=='fly over'or WPtype=='fly_over'then +wp.action='Fly Over Point' +elseif WPtype=='turningpoint'or WPtype=='turning point'or WPtype=='turning_point'then +wp.action='Turning Point' +else +wp.action='Turning Point' +end +end +wp.type='Turning Point' +return wp +end +routines.heli.buildWP=function(point,WPtype,speed,alt,altType) +local wp={} +wp.x=point.x +if point.z then +wp.y=point.z +else +wp.y=point.y +end +if alt and type(alt)=='number'then +wp.alt=alt +else +wp.alt=500 +end +if altType then +altType=string.lower(altType) +if altType=='radio'or'agl'then +wp.alt_type='RADIO' +elseif altType=='baro'or'asl'then +wp.alt_type='BARO' +end +else +wp.alt_type='RADIO' +end +if point.speed then +speed=point.speed +end +if point.type then +WPtype=point.type +end +if not speed then +wp.speed=routines.utils.kmphToMps(200) +else +wp.speed=speed +end +if not WPtype then +wp.action='Turning Point' +else +WPtype=string.lower(WPtype) +if WPtype=='flyover'or WPtype=='fly over'or WPtype=='fly_over'then +wp.action='Fly Over Point' +elseif WPtype=='turningpoint'or WPtype=='turning point'or WPtype=='turning_point'then +wp.action='Turning Point' +else +wp.action='Turning Point' +end +end +wp.type='Turning Point' +return wp +end +routines.groupToRandomPoint=function(vars) +local group=vars.group +local point=vars.point +local radius=vars.radius or 0 +local innerRadius=vars.innerRadius +local form=vars.form or'Cone' +local heading=vars.heading or math.random()*2*math.pi +local headingDegrees=vars.headingDegrees +local speed=vars.speed or routines.utils.kmphToMps(20) +local useRoads +if not vars.disableRoads then +useRoads=true +else +useRoads=false +end +local path={} +if headingDegrees then +heading=headingDegrees*math.pi/180 +end +if heading>=2*math.pi then +heading=heading-2*math.pi +end +local rndCoord=routines.getRandPointInCircle(point,radius,innerRadius) +local offset={} +local posStart=routines.getLeadPos(group) +offset.x=routines.utils.round(math.sin(heading-(math.pi/2))*50+rndCoord.x,3) +offset.z=routines.utils.round(math.cos(heading+(math.pi/2))*50+rndCoord.y,3) +path[#path+1]=routines.ground.buildWP(posStart,form,speed) +if useRoads==true and((point.x-posStart.x)^2+(point.z-posStart.z)^2)^0.5>radius*1.3 then +path[#path+1]=routines.ground.buildWP({['x']=posStart.x+11,['z']=posStart.z+11},'off_road',speed) +path[#path+1]=routines.ground.buildWP(posStart,'on_road',speed) +path[#path+1]=routines.ground.buildWP(offset,'on_road',speed) +else +path[#path+1]=routines.ground.buildWP({['x']=posStart.x+25,['z']=posStart.z+25},form,speed) +end +path[#path+1]=routines.ground.buildWP(offset,form,speed) +path[#path+1]=routines.ground.buildWP(rndCoord,form,speed) +routines.goRoute(group,path) +return +end +routines.groupRandomDistSelf=function(gpData,dist,form,heading,speed) +local pos=routines.getLeadPos(gpData) +local fakeZone={} +fakeZone.radius=dist or math.random(300,1000) +fakeZone.point={x=pos.x,y,pos.y,z=pos.z} +routines.groupToRandomZone(gpData,fakeZone,form,heading,speed) +return +end +routines.groupToRandomZone=function(gpData,zone,form,heading,speed) +if type(gpData)=='string'then +gpData=Group.getByName(gpData) +end +if type(zone)=='string'then +zone=trigger.misc.getZone(zone) +elseif type(zone)=='table'and not zone.radius then +zone=trigger.misc.getZone(zone[math.random(1,#zone)]) +end +if speed then +speed=routines.utils.kmphToMps(speed) +end +local vars={} +vars.group=gpData +vars.radius=zone.radius +vars.form=form +vars.headingDegrees=heading +vars.speed=speed +vars.point=routines.utils.zoneToVec3(zone) +routines.groupToRandomPoint(vars) +return +end +routines.isTerrainValid=function(coord,terrainTypes) +if coord.z then +coord.y=coord.z +end +local typeConverted={} +if type(terrainTypes)=='string'then +for constId,constData in pairs(land.SurfaceType)do +if string.lower(constId)==string.lower(terrainTypes)or string.lower(constData)==string.lower(terrainTypes)then +table.insert(typeConverted,constId) +end +end +elseif type(terrainTypes)=='table'then +for typeId,typeData in pairs(terrainTypes)do +for constId,constData in pairs(land.SurfaceType)do +if string.lower(constId)==string.lower(typeData)or string.lower(constData)==string.lower(typeId)then +table.insert(typeConverted,constId) +end +end +end +end +for validIndex,validData in pairs(typeConverted)do +if land.getSurfaceType(coord)==land.SurfaceType[validData]then +return true +end +end +return false +end +routines.groupToPoint=function(gpData,point,form,heading,speed,useRoads) +if type(point)=='string'then +point=trigger.misc.getZone(point) +end +if speed then +speed=routines.utils.kmphToMps(speed) +end +local vars={} +vars.group=gpData +vars.form=form +vars.headingDegrees=heading +vars.speed=speed +vars.disableRoads=useRoads +vars.point=routines.utils.zoneToVec3(point) +routines.groupToRandomPoint(vars) +return +end +routines.getLeadPos=function(group) +if type(group)=='string'then +group=Group.getByName(group) +end +local units=group:getUnits() +local leader=units[1] +if not leader then +local lowestInd=math.huge +for ind,unit in pairs(units)do +if ind0 then +local maxPos=-math.huge +local maxPosInd +for i=1,#unitPosTbl do +local rotatedVec2=routines.vec.rotateVec2(routines.utils.makeVec2(unitPosTbl[i]),heading) +if(not maxPos)or maxPos=1.0 then +CurrentZoneID=routines.IsUnitInZones(CargoUnit,LandingZones) +if CurrentZoneID then +break +end +end +end +end +return CurrentZoneID +end +function routines.IsUnitInZones(TransportUnit,LandingZones) +local TransportZoneResult=nil +local TransportZonePos=nil +local TransportZone=nil +if TransportUnit then +local TransportUnitPos=TransportUnit:getPosition().p +if type(LandingZones)=="table"then +for LandingZoneID,LandingZoneName in pairs(LandingZones)do +TransportZone=trigger.misc.getZone(LandingZoneName) +if TransportZone then +TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z} +if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then +TransportZoneResult=LandingZoneID +break +end +end +end +else +TransportZone=trigger.misc.getZone(LandingZones) +TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z} +if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then +TransportZoneResult=1 +end +end +if TransportZoneResult then +else +end +return TransportZoneResult +else +return nil +end +end +function routines.IsUnitNearZonesRadius(TransportUnit,LandingZones,ZoneRadius) +local TransportZoneResult=nil +local TransportZonePos=nil +local TransportZone=nil +if TransportUnit then +local TransportUnitPos=TransportUnit:getPosition().p +if type(LandingZones)=="table"then +for LandingZoneID,LandingZoneName in pairs(LandingZones)do +TransportZone=trigger.misc.getZone(LandingZoneName) +if TransportZone then +TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z} +if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=ZoneRadius)then +TransportZoneResult=LandingZoneID +break +end +end +end +else +TransportZone=trigger.misc.getZone(LandingZones) +TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z} +if(((TransportUnitPos.x-TransportZonePos.x)^2+(TransportUnitPos.z-TransportZonePos.z)^2)^0.5<=ZoneRadius)then +TransportZoneResult=1 +end +end +if TransportZoneResult then +else +end +return TransportZoneResult +else +return nil +end +end +function routines.IsStaticInZones(TransportStatic,LandingZones) +local TransportZoneResult=nil +local TransportZonePos=nil +local TransportZone=nil +local TransportStaticPos=TransportStatic:getPosition().p +if type(LandingZones)=="table"then +for LandingZoneID,LandingZoneName in pairs(LandingZones)do +TransportZone=trigger.misc.getZone(LandingZoneName) +if TransportZone then +TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z} +if(((TransportStaticPos.x-TransportZonePos.x)^2+(TransportStaticPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then +TransportZoneResult=LandingZoneID +break +end +end +end +else +TransportZone=trigger.misc.getZone(LandingZones) +TransportZonePos={radius=TransportZone.radius,x=TransportZone.point.x,y=TransportZone.point.y,z=TransportZone.point.z} +if(((TransportStaticPos.x-TransportZonePos.x)^2+(TransportStaticPos.z-TransportZonePos.z)^2)^0.5<=TransportZonePos.radius)then +TransportZoneResult=1 +end +end +return TransportZoneResult +end +function routines.IsUnitInRadius(CargoUnit,ReferencePosition,Radius) +local Valid=true +local CargoPos=CargoUnit:getPosition().p +local ReferenceP=ReferencePosition.p +if(((CargoPos.x-ReferenceP.x)^2+(CargoPos.z-ReferenceP.z)^2)^0.5<=Radius)then +else +Valid=false +end +return Valid +end +function routines.IsPartOfGroupInRadius(CargoGroup,ReferencePosition,Radius) +local Valid=true +Valid=routines.ValidateGroup(CargoGroup,"CargoGroup",Valid) +local CargoUnits=CargoGroup:getUnits() +for CargoUnitId,CargoUnit in pairs(CargoUnits)do +local CargoUnitPos=CargoUnit:getPosition().p +local ReferenceP=ReferencePosition.p +if(((CargoUnitPos.x-ReferenceP.x)^2+(CargoUnitPos.z-ReferenceP.z)^2)^0.5<=Radius)then +else +Valid=false +break +end +end +return Valid +end +function routines.ValidateString(Variable,VariableName,Valid) +if type(Variable)=="string"then +if Variable==""then +error("routines.ValidateString: error: "..VariableName.." must be filled out!") +Valid=false +end +else +error("routines.ValidateString: error: "..VariableName.." is not a string.") +Valid=false +end +return Valid +end +function routines.ValidateNumber(Variable,VariableName,Valid) +if type(Variable)=="number"then +else +error("routines.ValidateNumber: error: "..VariableName.." is not a number.") +Valid=false +end +return Valid +end +function routines.ValidateGroup(Variable,VariableName,Valid) +if Variable==nil then +error("routines.ValidateGroup: error: "..VariableName.." is a nil value!") +Valid=false +end +return Valid +end +function routines.ValidateZone(LandingZones,VariableName,Valid) +if LandingZones==nil then +error("routines.ValidateGroup: error: "..VariableName.." is a nil value!") +Valid=false +end +if type(LandingZones)=="table"then +for LandingZoneID,LandingZoneName in pairs(LandingZones)do +if trigger.misc.getZone(LandingZoneName)==nil then +error("routines.ValidateGroup: error: Zone "..LandingZoneName.." does not exist!") +Valid=false +break +end +end +else +if trigger.misc.getZone(LandingZones)==nil then +error("routines.ValidateGroup: error: Zone "..LandingZones.." does not exist!") +Valid=false +end +end +return Valid +end +function routines.ValidateEnumeration(Variable,VariableName,Enum,Valid) +local ValidVariable=false +for EnumId,EnumData in pairs(Enum)do +if Variable==EnumData then +ValidVariable=true +break +end +end +if ValidVariable then +else +error('TransportValidateEnum: " .. VariableName .. " is not a valid type.'..Variable) +Valid=false +end +return Valid +end +function routines.getGroupRoute(groupIdent,task) +local gpId=groupIdent +if type(groupIdent)=='string'and not tonumber(groupIdent)then +gpId=_DATABASE.Templates.Groups[groupIdent].groupId +end +for coa_name,coa_data in pairs(env.mission.coalition)do +if(coa_name=='red'or coa_name=='blue')and type(coa_data)=='table'then +if coa_data.country then +for cntry_id,cntry_data in pairs(coa_data.country)do +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"then +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,group_data in pairs(obj_type_data.group)do +if group_data and group_data.groupId==gpId then +if group_data.route and group_data.route.points and#group_data.route.points>0 then +local points={} +for point_num,point in pairs(group_data.route.points)do +local routeData={} +if env.mission.version>7 then +routeData.name=env.getValueDictByKey(point.name) +else +routeData.name=point.name +end +if not point.point then +routeData.x=point.x +routeData.y=point.y +else +routeData.point=point.point +end +routeData.form=point.action +routeData.speed=point.speed +routeData.alt=point.alt +routeData.alt_type=point.alt_type +routeData.airdromeId=point.airdromeId +routeData.helipadId=point.helipadId +routeData.type=point.type +routeData.action=point.action +if task then +routeData.task=point.task +end +points[point_num]=routeData +end +return points +end +return +end +end +end +end +end +end +end +end +end +end +routines.ground.patrolRoute=function(vars) +local tempRoute={} +local useRoute={} +local gpData=vars.gpData +if type(gpData)=='string'then +gpData=Group.getByName(gpData) +end +local useGroupRoute +if not vars.useGroupRoute then +useGroupRoute=vars.gpData +else +useGroupRoute=vars.useGroupRoute +end +local routeProvided=false +if not vars.route then +if useGroupRoute then +tempRoute=routines.getGroupRoute(useGroupRoute) +end +else +useRoute=vars.route +local posStart=routines.getLeadPos(gpData) +useRoute[1]=routines.ground.buildWP(posStart,useRoute[1].action,useRoute[1].speed) +routeProvided=true +end +local overRideSpeed=vars.speed or'default' +local pType=vars.pType +local offRoadForm=vars.offRoadForm or'default' +local onRoadForm=vars.onRoadForm or'default' +if routeProvided==false and#tempRoute>0 then +local posStart=routines.getLeadPos(gpData) +useRoute[#useRoute+1]=routines.ground.buildWP(posStart,offRoadForm,overRideSpeed) +for i=1,#tempRoute do +local tempForm=tempRoute[i].action +local tempSpeed=tempRoute[i].speed +if offRoadForm=='default'then +tempForm=tempRoute[i].action +end +if onRoadForm=='default'then +onRoadForm='On Road' +end +if(string.lower(tempRoute[i].action)=='on road'or string.lower(tempRoute[i].action)=='onroad'or string.lower(tempRoute[i].action)=='on_road')then +tempForm=onRoadForm +else +tempForm=offRoadForm +end +if type(overRideSpeed)=='number'then +tempSpeed=overRideSpeed +end +useRoute[#useRoute+1]=routines.ground.buildWP(tempRoute[i],tempForm,tempSpeed) +end +if pType and string.lower(pType)=='doubleback'then +local curRoute=routines.utils.deepCopy(useRoute) +for i=#curRoute,2,-1 do +useRoute[#useRoute+1]=routines.ground.buildWP(curRoute[i],curRoute[i].action,curRoute[i].speed) +end +end +useRoute[1].action=useRoute[#useRoute].action +end +local cTask3={} +local newPatrol={} +newPatrol.route=useRoute +newPatrol.gpData=gpData:getName() +cTask3[#cTask3+1]='routines.ground.patrolRoute(' +cTask3[#cTask3+1]=routines.utils.oneLineSerialize(newPatrol) +cTask3[#cTask3+1]=')' +cTask3=table.concat(cTask3) +local tempTask={ +id='WrappedAction', +params={ +action={ +id='Script', +params={ +command=cTask3, +}, +}, +}, +} +useRoute[#useRoute].task=tempTask +routines.goRoute(gpData,useRoute) +return +end +routines.ground.patrol=function(gpData,pType,form,speed) +local vars={} +if type(gpData)=='table'and gpData:getName()then +gpData=gpData:getName() +end +vars.useGroupRoute=gpData +vars.gpData=gpData +vars.pType=pType +vars.offRoadForm=form +vars.speed=speed +routines.ground.patrolRoute(vars) +return +end +function routines.GetUnitHeight(CheckUnit) +local UnitPoint=CheckUnit:getPoint() +local UnitPosition={x=UnitPoint.x,y=UnitPoint.z} +local UnitHeight=UnitPoint.y +local LandHeight=land.getHeight(UnitPosition) +return UnitHeight-LandHeight +end +Su34Status={status={}} +boardMsgRed={statusMsg=""} +boardMsgAll={timeMsg=""} +SpawnSettings={} +Su34MenuPath={} +Su34Menus=0 +function Su34AttackCarlVinson(groupName) +local groupSu34=Group.getByName(groupName) +local controllerSu34=groupSu34.getController(groupSu34) +local groupCarlVinson=Group.getByName("US Carl Vinson #001") +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE) +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) +if groupCarlVinson~=nil then +controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupCarlVinson:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}}) +end +Su34Status.status[groupName]=1 +MessageToRed(string.format('%s: ',groupName)..'Attacking carrier Carl Vinson. ',10,'RedStatus'..groupName) +end +function Su34AttackWest(groupName) +local groupSu34=Group.getByName(groupName) +local controllerSu34=groupSu34.getController(groupSu34) +local groupShipWest1=Group.getByName("US Ship West #001") +local groupShipWest2=Group.getByName("US Ship West #002") +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE) +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) +if groupShipWest1~=nil then +controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipWest1:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}}) +end +if groupShipWest2~=nil then +controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipWest2:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=true}}) +end +Su34Status.status[groupName]=2 +MessageToRed(string.format('%s: ',groupName)..'Attacking invading ships in the west. ',10,'RedStatus'..groupName) +end +function Su34AttackNorth(groupName) +local groupSu34=Group.getByName(groupName) +local controllerSu34=groupSu34.getController(groupSu34) +local groupShipNorth1=Group.getByName("US Ship North #001") +local groupShipNorth2=Group.getByName("US Ship North #002") +local groupShipNorth3=Group.getByName("US Ship North #003") +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE) +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) +if groupShipNorth1~=nil then +controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth1:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}}) +end +if groupShipNorth2~=nil then +controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth2:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}}) +end +if groupShipNorth3~=nil then +controllerSu34.pushTask(controllerSu34,{id='AttackGroup',params={groupId=groupShipNorth3:getID(),expend=AI.Task.WeaponExpend.ALL,attackQtyLimit=false}}) +end +Su34Status.status[groupName]=3 +MessageToRed(string.format('%s: ',groupName)..'Attacking invading ships in the north. ',10,'RedStatus'..groupName) +end +function Su34Orbit(groupName) +local groupSu34=Group.getByName(groupName) +local controllerSu34=groupSu34:getController() +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD) +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) +controllerSu34:pushTask({id='ControlledTask',params={task={id='Orbit',params={pattern=AI.Task.OrbitPattern.RACE_TRACK}},stopCondition={duration=600}}}) +Su34Status.status[groupName]=4 +MessageToRed(string.format('%s: ',groupName)..'In orbit and awaiting further instructions. ',10,'RedStatus'..groupName) +end +function Su34TakeOff(groupName) +local groupSu34=Group.getByName(groupName) +local controllerSu34=groupSu34:getController() +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD) +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE) +Su34Status.status[groupName]=8 +MessageToRed(string.format('%s: ',groupName)..'Take-Off. ',10,'RedStatus'..groupName) +end +function Su34Hold(groupName) +local groupSu34=Group.getByName(groupName) +local controllerSu34=groupSu34:getController() +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD) +controllerSu34.setOption(controllerSu34,AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE) +Su34Status.status[groupName]=5 +MessageToRed(string.format('%s: ',groupName)..'Holding Weapons. ',10,'RedStatus'..groupName) +end +function Su34RTB(groupName) +Su34Status.status[groupName]=6 +MessageToRed(string.format('%s: ',groupName)..'Return to Krasnodar. ',10,'RedStatus'..groupName) +end +function Su34Destroyed(groupName) +Su34Status.status[groupName]=7 +MessageToRed(string.format('%s: ',groupName)..'Destroyed. ',30,'RedStatus'..groupName) +end +function GroupAlive(groupName) +local groupTest=Group.getByName(groupName) +local groupExists=false +if groupTest then +groupExists=groupTest:isExist() +end +return groupExists +end +function Su34IsDead() +end +function Su34OverviewStatus() +local msg="" +local currentStatus=0 +local Exists=false +for groupName,currentStatus in pairs(Su34Status.status)do +env.info(('Su34 Overview Status: GroupName = '..groupName)) +Alive=GroupAlive(groupName) +if Alive then +if currentStatus==1 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Attacking carrier Carl Vinson. " +elseif currentStatus==2 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Attacking supporting ships in the west. " +elseif currentStatus==3 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Attacking invading ships in the north. " +elseif currentStatus==4 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."In orbit and awaiting further instructions. " +elseif currentStatus==5 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Holding Weapons. " +elseif currentStatus==6 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Return to Krasnodar. " +elseif currentStatus==7 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Destroyed. " +elseif currentStatus==8 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Take-Off. " +end +else +if currentStatus==7 then +msg=msg..string.format("%s: ",groupName) +msg=msg.."Destroyed. " +else +Su34Destroyed(groupName) +end +end +end +boardMsgRed.statusMsg=msg +end +function UpdateBoardMsg() +Su34OverviewStatus() +MessageToRed(boardMsgRed.statusMsg,15,'RedStatus') +end +function MusicReset(flg) +trigger.action.setUserFlag(95,flg) +end +function PlaneActivate(groupNameFormat,flg) +local groupName=groupNameFormat..string.format("#%03d",trigger.misc.getUserFlag(flg)) +trigger.action.activateGroup(Group.getByName(groupName)) +end +function Su34Menu(groupName) +local groupSu34=Group.getByName(groupName) +if Su34Status.status[groupName]==1 or +Su34Status.status[groupName]==2 or +Su34Status.status[groupName]==3 or +Su34Status.status[groupName]==4 or +Su34Status.status[groupName]==5 then +if Su34MenuPath[groupName]==nil then +if planeMenuPath==nil then +planeMenuPath=missionCommands.addSubMenuForCoalition( +coalition.side.RED, +"SU-34 anti-ship flights", +nil +) +end +Su34MenuPath[groupName]=missionCommands.addSubMenuForCoalition( +coalition.side.RED, +"Flight "..groupName, +planeMenuPath +) +missionCommands.addCommandForCoalition( +coalition.side.RED, +"Attack carrier Carl Vinson", +Su34MenuPath[groupName], +Su34AttackCarlVinson, +groupName +) +missionCommands.addCommandForCoalition( +coalition.side.RED, +"Attack ships in the west", +Su34MenuPath[groupName], +Su34AttackWest, +groupName +) +missionCommands.addCommandForCoalition( +coalition.side.RED, +"Attack ships in the north", +Su34MenuPath[groupName], +Su34AttackNorth, +groupName +) +missionCommands.addCommandForCoalition( +coalition.side.RED, +"Hold position and await instructions", +Su34MenuPath[groupName], +Su34Orbit, +groupName +) +missionCommands.addCommandForCoalition( +coalition.side.RED, +"Report status", +Su34MenuPath[groupName], +Su34OverviewStatus +) +end +else +if Su34MenuPath[groupName]then +missionCommands.removeItemForCoalition(coalition.side.RED,Su34MenuPath[groupName]) +end +end +end +function ChooseInfantry(TeleportPrefixTable,TeleportMax) +TeleportPrefixTableCount=#TeleportPrefixTable +TeleportPrefixTableIndex=math.random(1,TeleportPrefixTableCount) +local TeleportFound=false +local TeleportLoop=true +local Index=TeleportPrefixTableIndex +local TeleportPrefix='' +while TeleportLoop do +TeleportPrefix=TeleportPrefixTable[Index] +if SpawnSettings[TeleportPrefix]then +if SpawnSettings[TeleportPrefix]['SpawnCount']-10 then +local PlayerFound=false +local MusicStart=0 +local MusicTime=0 +for SndQueueIdx,SndQueue in pairs(_MusicTable.Queue)do +if SndQueue.PlayerName==PlayerName then +PlayerFound=true +MusicStart=SndQueue.Start +MusicTime=_MusicTable.Files[SndQueue.Ref].Time +break +end +end +if PlayerFound then +if MusicStart+MusicTime<=timer.getTime()then +MusicOut=true +end +else +MusicOut=true +end +end +if MusicOut then +else +end +return MusicOut +end +function MusicScheduler() +if _MusicTable['Queue']~=nil and _MusicTable.FileCnt>0 then +for SndQueueIdx,SndQueue in pairs(_MusicTable.Queue)do +if SndQueue.Continue then +if MusicCanStart(SndQueue.PlayerName)then +MusicToPlayer('',SndQueue.PlayerName,true) +end +end +end +end +end +env.info(('Init: Scripts Loaded v1.1')) +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", +} +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, +}, +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, +}, +} +UTILS={ +_MarkID=1 +} +UTILS.IsInstanceOf=function(object,className) +if not type(className)=='string'then +if type(className)=='table'and className.IsInstanceOf~=nil then +className=className.ClassName +else +local err_str='className parameter should be a string; parameter received: '..type(className) +return false +end +end +if type(object)=='table'and object.IsInstanceOf~=nil then +return object:IsInstanceOf(className) +else +local basicDataTypes={'string','number','function','boolean','nil','table'} +for _,basicDataType in ipairs(basicDataTypes)do +if className==basicDataType then +return type(object)==basicDataType +end +end +end +return false +end +UTILS.DeepCopy=function(object) +local lookup_table={} +local function _copy(object) +if type(object)~="table"then +return object +elseif lookup_table[object]then +return lookup_table[object] +end +local new_table={} +lookup_table[object]=new_table +for index,value in pairs(object)do +new_table[_copy(index)]=_copy(value) +end +return setmetatable(new_table,getmetatable(object)) +end +local objectreturn=_copy(object) +return objectreturn +end +UTILS.OneLineSerialize=function(tbl) +lookup_table={} +local function _Serialize(tbl) +if type(tbl)=='table'then +if lookup_table[tbl]then +return lookup_table[object] +end +local tbl_str={} +lookup_table[tbl]=tbl_str +tbl_str[#tbl_str+1]='{' +for ind,val in pairs(tbl)do +local ind_str={} +if type(ind)=="number"then +ind_str[#ind_str+1]='[' +ind_str[#ind_str+1]=tostring(ind) +ind_str[#ind_str+1]=']=' +else +ind_str[#ind_str+1]='[' +ind_str[#ind_str+1]=routines.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]=routines.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 '..routines.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 +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)=='table')or(type(s)=='userdata'))then +return tostring(s) +elseif type(s)=='string'then +s=string.format('%q',s) +return s +end +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) +return knots/1.94384 +end +UTILS.CelciusToFarenheit=function(Celcius) +return Celcius*9/5+32 +end +UTILS.hPa2inHg=function(hPa) +return hPa*0.0295299830714 +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.tostringMGRS=function(MGRS,acc) +if acc==0 then +return MGRS.UTMZone..' '..MGRS.MGRSDigraph +else +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.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.SecondsToClock(seconds,short) +if seconds==nil then +return nil +end +local seconds=tonumber(seconds) +local _seconds=seconds%(60*60*24) +if seconds<0 then +return nil +else +local hours=string.format("%02.f",math.floor(_seconds/3600)) +local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60))) +local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60)) +local days=string.format("%d",seconds/(60*60*24)) +local clock=hours..":"..mins..":"..secs.."+"..days +if short then +if hours=="00"then +clock=hours..":"..mins..":"..secs +else +clock=hours..":"..mins..":"..secs +end +end +return clock +end +end +function UTILS.SecondsOfToday() +local time=timer.getAbsTime() +local clock=UTILS.SecondsToClock(time,true) +return UTILS.ClockToSeconds(clock) +end +function UTILS.SecondsToMidnight() +return 24*60*60-UTILS.SecondsOfToday() +end +function UTILS.ClockToSeconds(clock) +if clock==nil then +return nil +end +local seconds=0 +local dsplit=UTILS.Split(clock,"+") +if#dsplit>1 then +seconds=seconds+tonumber(dsplit[2])*60*60*24 +end +local tsplit=UTILS.Split(dsplit[1],":") +local i=1 +for _,time in ipairs(tsplit)do +if i==1 then +seconds=seconds+tonumber(time)*60*60 +elseif i==2 then +seconds=seconds+tonumber(time)*60 +elseif i==3 then +seconds=seconds+tonumber(time) +end +i=i+1 +end +return seconds +end +function UTILS.DisplayMissionTime(duration) +duration=duration or 5 +local Tnow=timer.getAbsTime() +local mission_time=Tnow-timer.getTime0() +local mission_time_minutes=mission_time/60 +local mission_time_seconds=mission_time%60 +local local_time=UTILS.SecondsToClock(Tnow) +local text=string.format("Time: %s - %02d:%02d",local_time,mission_time_minutes,mission_time_seconds) +MESSAGE:New(text,duration):ToAll() +end +function UTILS.ReplaceIllegalCharacters(Text,ReplaceBy) +ReplaceBy=ReplaceBy or"_" +local text=Text:gsub("[<>|/?*:\\]",ReplaceBy) +return text +end +function UTILS.RandomGaussian(x0,sigma,xmin,xmax,imax) +sigma=sigma or 10 +imax=imax or 100 +local r +local gotit=false +local i=0 +while not gotit do +local x1=math.random() +local x2=math.random() +r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0 +i=i+1 +if(r>=xmin and r<=xmax)or i>imax then +gotit=true +end +end +return r +end +function UTILS.Randomize(value,fac,lower,upper) +local min +if lower then +min=math.max(value-value*fac,lower) +else +min=value-value*fac +end +local max +if upper then +max=math.min(value+value*fac,upper) +else +max=value+value*fac +end +local r=math.random(min,max) +return r +end +function UTILS.VecDot(a,b) +return a.x*b.x+a.y*b.y+a.z*b.z +end +function UTILS.VecNorm(a) +return math.sqrt(UTILS.VecDot(a,a)) +end +function UTILS.VecDist2D(a,b) +local c={x=b.x-a.x,y=b.y-a.y} +local d=math.sqrt(c.x*c.x+c.y*c.y) +return d +end +function UTILS.VecDist3D(a,b) +local c={x=b.x-a.x,y=b.y-a.y,z=b.z-a.z} +local 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.VecAdd(a,b) +return{x=a.x+b.x,y=a.y+b.y,z=a.z+b.z} +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.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.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.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.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.GetDate() +local date,year,month,day=UTILS.GetDCSMissionDate() +local time=timer.getAbsTime() +local clock=UTILS.SecondsToClock(time,false) +local x=tonumber(UTILS.Split(clock,"+")[2]) +local day=day+x +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 +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.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.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 +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 +else +BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0",tostring(theatre))) +return 0 +end +end +function UTILS.GetDayOfYear(Year,Month,Day) +local floor=math.floor +local n1=floor(275*Month/9) +local n2=floor((Month+9)/12) +local n3=(1+floor((Year-4*floor(Year/4)+2)/3)) +return n1-(n2*n3)+Day-30 +end +function UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,Rising,Tlocal) +local zenith=90.83 +local latitude=Latitude +local longitude=Longitude +local rising=Rising +local n=DayOfYear +Tlocal=Tlocal or 0 +local rad=math.rad +local deg=math.deg +local floor=math.floor +local frac=function(n)return n-floor(n)end +local cos=function(d)return math.cos(rad(d))end +local acos=function(d)return deg(math.acos(d))end +local sin=function(d)return math.sin(rad(d))end +local asin=function(d)return deg(math.asin(d))end +local tan=function(d)return math.tan(rad(d))end +local atan=function(d)return deg(math.atan(d))end +local function fit_into_range(val,min,max) +local range=max-min +local count +if val=max then +count=floor((val-max)/range)+1 +return val-count*range +else +return val +end +end +local lng_hour=longitude/15 +local t +if rising then +t=n+((6-lng_hour)/24) +else +t=n+((18-lng_hour)/24) +end +local M=(0.9856*t)-3.289 +local L=fit_into_range(M+(1.916*sin(M))+(0.020*sin(2*M))+282.634,0,360) +local RA=fit_into_range(atan(0.91764*tan(L)),0,360) +local Lquadrant=floor(L/90)*90 +local RAquadrant=floor(RA/90)*90 +RA=RA+Lquadrant-RAquadrant +RA=RA/15 +local sinDec=0.39782*sin(L) +local cosDec=cos(asin(sinDec)) +local cosH=(cos(zenith)-(sinDec*sin(latitude)))/(cosDec*cos(latitude)) +if rising and cosH>1 then +return"N/R" +elseif cosH<-1 then +return"N/S" +end +local H +if rising then +H=360-acos(cosH) +else +H=acos(cosH) +end +H=H/15 +local T=H+RA-(0.06571*t)-6.622 +local UT=fit_into_range(T-lng_hour+Tlocal,0,24) +return floor(UT)*60*60+frac(UT)*60*60 +end +function UTILS.GetSunrise(Day,Month,Year,Latitude,Longitude,Tlocal) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tlocal) +end +function UTILS.GetSunset(Day,Month,Year,Latitude,Longitude,Tlocal) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +return UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tlocal) +end +function UTILS.GetOSTime() +if os then +return os.clock() +end +return nil +end +function UTILS.ShuffleTable(t) +if t==nil or type(t)~="table"then +BASE:I("Error in ShuffleTable: Missing or wrong tyåe 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 +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 desanitized!") +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) +else +debug.sethook() +local runTimeGame=timer.getTime()-PROFILER.TstartGame +local runTimeOS=os.clock()-PROFILER.TstartOS +PROFILER.showInfo(runTimeGame,runTimeOS) +end +end +function PROFILER.eventHandler:onEvent(event) +if event.id==world.event.S_EVENT_MISSION_END then +PROFILER.Stop() +end +end +function PROFILER.hook(event) +local f=debug.getinfo(2,"f").func +if event=='call'then +if PROFILER.Counters[f]==nil then +PROFILER.Counters[f]=1 +PROFILER.dInfo[f]=debug.getinfo(2,"Sn") +if PROFILER.fTimeTotal[f]==nil then +PROFILER.fTimeTotal[f]=0 +end +else +PROFILER.Counters[f]=PROFILER.Counters[f]+1 +end +if PROFILER.fTime[f]==nil then +PROFILER.fTime[f]=os.clock() +end +elseif(event=='return')then +if PROFILER.fTime[f]~=nil then +PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f]) +PROFILER.fTime[f]=nil +end +end +end +function PROFILER.getData(func) +local n=PROFILER.dInfo[func] +if n.what=="C"then +return n.name,"?","?",PROFILER.fTimeTotal[func] +end +return n.name,n.short_src,n.linedefined,PROFILER.fTimeTotal[func] +end +function PROFILER._flog(f,txt) +f:write(txt.."\r\n") +end +function PROFILER.showTable(data,f,runTimeGame) +for i=1,#data do +local t=data[i] +local cps=t.count/runTimeGame +local threshCPS=cps>=PROFILER.ThreshCPS +local threshTot=t.tm>=PROFILER.ThreshTtot +if threshCPS and threshTot then +local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line)) +PROFILER._flog(f,text) +end +end +end +function PROFILER.printCSV(data,runTimeGame) +local file=PROFILER.getfilename("csv") +local g=io.open(file,'w') +local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," +g:write(text.."\r\n") +for i=1,#data do +local t=data[i] +local cps=t.count/runTimeGame +local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,",t.func,t.count,cps,t.tm,t.tm/runTimeGame*100,t.tm/t.count,tostring(t.src),tostring(t.line)) +g:write(txt.."\r\n") +end +g:close() +end +function PROFILER.getfilename(ext) +local dir=lfs.writedir()..[[Logs\]] +ext=ext or PROFILER.fileNameSuffix +local file=dir..PROFILER.fileNamePrefix.."."..ext +if not UTILS.FileExists(file)then +return file +end +for i=1,999 do +local file=string.format("%s%s-%03d.%s",dir,PROFILER.fileNamePrefix,i,ext) +if not UTILS.FileExists(file)then +return file +end +end +end +function PROFILER.showInfo(runTimeGame,runTimeOS) +local file=PROFILER.getfilename(PROFILER.fileNameSuffix) +local f=io.open(file,'w') +local Ttot=0 +local Calls=0 +local t={} +local tcopy=nil +local tserialize=nil +local tforgen=nil +local tpairs=nil +for func,count in pairs(PROFILER.Counters)do +local s,src,line,tm=PROFILER.getData(func) +if PROFILER.logUnknown==true then +if s==nil then s=""end +end +if s~=nil then +local T= +{func=s, +src=src, +line=line, +count=count, +tm=tm, +} +if s=="_copy"then +if tcopy==nil then +tcopy=T +else +tcopy.count=tcopy.count+T.count +tcopy.tm=tcopy.tm+T.tm +end +elseif s=="_Serialize"then +if tserialize==nil then +tserialize=T +else +tserialize.count=tserialize.count+T.count +tserialize.tm=tserialize.tm+T.tm +end +elseif s=="(for generator)"then +if tforgen==nil then +tforgen=T +else +tforgen.count=tforgen.count+T.count +tforgen.tm=tforgen.tm+T.tm +end +elseif s=="pairs"then +if tpairs==nil then +tpairs=T +else +tpairs.count=tpairs.count+T.count +tpairs.tm=tpairs.tm+T.tm +end +else +table.insert(t,T) +end +Ttot=Ttot+tm +Calls=Calls+count +end +end +if tcopy then +table.insert(t,tcopy) +end +if tserialize then +table.insert(t,tserialize) +end +if tforgen then +table.insert(t,tforgen) +end +if tpairs then +table.insert(t,tpairs) +end +env.info('############################ Profiler Stopped ############################') +env.info(string.format("* Runtime Game : %s = %d sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame)) +env.info(string.format("* Runtime Real : %s = %d sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS)) +env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100)) +env.info(string.format("* Total functions : %d",#t)) +env.info(string.format("* Total func calls : %d",Calls)) +env.info(string.format("* Writing to file : \"%s\"",file)) +env.info(string.format("* Writing to file : \"%s\"",PROFILER.getfilename("csv"))) +env.info("##############################################################################") +table.sort(t,function(a,b)return a.tm>b.tm end) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER._flog(f,"-------------------------") +PROFILER._flog(f,"---- Profiler Report ----") +PROFILER._flog(f,"-------------------------") +PROFILER._flog(f,"") +PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec",UTILS.SecondsToClock(runTimeGame,true),runTimeGame)) +PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec",UTILS.SecondsToClock(runTimeOS,true),runTimeOS)) +PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)",UTILS.SecondsToClock(Ttot,true),Ttot,Ttot/runTimeGame*100)) +PROFILER._flog(f,"") +PROFILER._flog(f,string.format("* Total functions = %d",#t)) +PROFILER._flog(f,string.format("* Total func calls = %d",Calls)) +PROFILER._flog(f,"") +PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec",PROFILER.ThreshCPS)) +PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec",PROFILER.ThreshTtot)) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER.showTable(t,f,runTimeGame) +table.sort(t,function(a,b)return a.tm/a.count>b.tm/b.count end) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER._flog(f,"--------------------------------------") +PROFILER._flog(f,"---- Data Sorted by Time per Call ----") +PROFILER._flog(f,"--------------------------------------") +PROFILER._flog(f,"") +PROFILER.showTable(t,f,runTimeGame) +table.sort(t,function(a,b)return a.count>b.count end) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"") +PROFILER._flog(f,"------------------------------------") +PROFILER._flog(f,"---- Data Sorted by Total Calls ----") +PROFILER._flog(f,"------------------------------------") +PROFILER._flog(f,"") +PROFILER.showTable(t,f,runTimeGame) +PROFILER._flog(f,"") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +PROFILER._flog(f,"************************************************************************************************************************") +f:close() +PROFILER.printCSV(t,runTimeGame) +end +local _TraceOnOff=true +local _TraceLevel=1 +local _TraceAll=false +local _TraceClass={} +local _TraceClassMethod={} +local _ClassID=0 +BASE={ +ClassName="BASE", +ClassID=0, +Events={}, +States={}, +Debug=debug, +Scheduler=nil, +} +BASE.__={} +BASE._={ +Schedules={} +} +FORMATION={ +Cone="Cone", +Vee="Vee" +} +function BASE:New() +local self=routines.utils.deepCopy(self) +_ClassID=_ClassID+1 +self.ClassID=_ClassID +return self +end +function BASE:Inherit(Child,Parent) +local Child=routines.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) +self:F({EventTime,Initiator}) +local Event={ +id=world.event.S_EVENT_CRASH, +time=EventTime, +initiator=Initiator, +} +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) +self:F({EventTime,Initiator}) +local Event={ +id=world.event.S_EVENT_DEAD, +time=EventTime, +initiator=Initiator, +} +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: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,...) +self:F2({Start}) +self:T3({...}) +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=_SCHEDULEDISPATCHER:AddSchedule( +self, +SchedulerFunction, +{...}, +Start, +nil, +nil, +nil +) +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( +self, +SchedulerFunction, +{...}, +Start, +Repeat, +RandomizeFactor, +Stop, +4 +) +self._.Schedules[#self._.Schedules+1]=ScheduleID +return self._.Schedules[#self._.Schedules] +end +function BASE:ScheduleStop(SchedulerFunction) +self:F3({"ScheduleStop:"}) +if self.Scheduler then +_SCHEDULEDISPATCHER:Stop(self.Scheduler,self._.Schedules[SchedulerFunction]) +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: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:_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,routines.utils.oneLineSerialize(Arguments))) +end +end +end +function BASE:F(Arguments) +if BASE.Debug and _TraceOnOff then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=1 then +self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:F2(Arguments) +if BASE.Debug and _TraceOnOff then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=2 then +self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:F3(Arguments) +if BASE.Debug and _TraceOnOff then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=3 then +self:_F(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:_T(Arguments,DebugInfoCurrentParam,DebugInfoFromParam) +if BASE.Debug and(_TraceAll==true)or(_TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName])then +local DebugInfoCurrent=DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo(3,"l") +local Function="function" +if DebugInfoCurrent.name then +Function=DebugInfoCurrent.name +end +if _TraceAll==true or _TraceClass[self.ClassName]or _TraceClassMethod[self.ClassName].Method[Function]then +local LineCurrent=0 +if DebugInfoCurrent.currentline then +LineCurrent=DebugInfoCurrent.currentline +end +local LineFrom=0 +if DebugInfoFrom then +LineFrom=DebugInfoFrom.currentline +end +env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s",LineCurrent,LineFrom,"T",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(Arguments))) +end +end +end +function BASE:T(Arguments) +if BASE.Debug and _TraceOnOff then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=1 then +self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:T2(Arguments) +if BASE.Debug and _TraceOnOff then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=2 then +self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:T3(Arguments) +if BASE.Debug and _TraceOnOff then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +if _TraceLevel>=3 then +self:_T(Arguments,DebugInfoCurrent,DebugInfoFrom) +end +end +end +function BASE:E(Arguments) +if BASE.Debug then +local DebugInfoCurrent=BASE.Debug.getinfo(2,"nl") +local DebugInfoFrom=BASE.Debug.getinfo(3,"l") +local Function="function" +if DebugInfoCurrent.name then +Function=DebugInfoCurrent.name +end +local LineCurrent=DebugInfoCurrent.currentline +local LineFrom=-1 +if DebugInfoFrom then +LineFrom=DebugInfoFrom.currentline +end +env.info(string.format("%6d(%6d)/%1s:%30s%05d.%s(%s)",LineCurrent,LineFrom,"E",self.ClassName,self.ClassID,Function,routines.utils.oneLineSerialize(Arguments))) +else +env.info(string.format("%1s:%30s%05d(%s)","E",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(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,routines.utils.oneLineSerialize(Arguments))) +else +env.info(string.format("%1s:%30s%05d(%s)","I",self.ClassName,self.ClassID,routines.utils.oneLineSerialize(Arguments))) +end +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 +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 +end +REPORT={ +ClassName="REPORT", +Title="", +} +function REPORT:New(Title) +local self=BASE:Inherit(self,BASE:New()) +self.Report={} +self:SetTitle(Title or"") +self:SetIndent(3) +return self +end +function REPORT:HasText() +return#self.Report>0 +end +function REPORT:SetIndent(Indent) +self.Indent=Indent +return self +end +function REPORT:Add(Text) +self.Report[#self.Report+1]=Text +return self +end +function REPORT:AddIndent(Text,Separator) +self.Report[#self.Report+1]=((Separator and Separator..string.rep(" ",self.Indent-1))or string.rep(" ",self.Indent))..Text:gsub("\n","\n"..string.rep(" ",self.Indent)) +return self +end +function REPORT:Text(Delimiter) +Delimiter=Delimiter or"\n" +local ReportText=(self.Title~=""and self.Title..Delimiter or self.Title)..table.concat(self.Report,Delimiter)or"" +return ReportText +end +function REPORT:SetTitle(Title) +self.Title=Title +return self +end +function REPORT:GetCount() +return#self.Report +end +SCHEDULER={ +ClassName="SCHEDULER", +Schedules={}, +MasterObject=nil, +ShowTrace=nil, +} +function SCHEDULER:New(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop) +local self=BASE:Inherit(self,BASE:New()) +self:F2({Start,Repeat,RandomizeFactor,Stop}) +local ScheduleID=nil +self.MasterObject=MasterObject +self.ShowTrace=false +if SchedulerFunction then +ScheduleID=self:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,3) +end +return self,ScheduleID +end +function SCHEDULER:Schedule(MasterObject,SchedulerFunction,SchedulerArguments,Start,Repeat,RandomizeFactor,Stop,TraceLevel,Fsm) +self:F2({Start,Repeat,RandomizeFactor,Stop}) +self:T3({SchedulerArguments}) +local ObjectName="-" +if MasterObject and MasterObject.ClassName and MasterObject.ClassID then +ObjectName=MasterObject.ClassName..MasterObject.ClassID +end +self:F3({"Schedule :",ObjectName,tostring(MasterObject),Start,Repeat,RandomizeFactor,Stop}) +self.MasterObject=MasterObject +local ScheduleID=_SCHEDULEDISPATCHER:AddSchedule( +self, +SchedulerFunction, +SchedulerArguments, +Start, +Repeat, +RandomizeFactor, +Stop, +TraceLevel or 3, +Fsm +) +self.Schedules[#self.Schedules+1]=ScheduleID +return ScheduleID +end +function SCHEDULER:Start(ScheduleID) +self:F3({ScheduleID}) +self:T(string.format("Starting scheduler ID=%s",tostring(ScheduleID))) +_SCHEDULEDISPATCHER:Start(self,ScheduleID) +end +function SCHEDULER:Stop(ScheduleID) +self:F3({ScheduleID}) +self:T(string.format("Stopping scheduler ID=%s",tostring(ScheduleID))) +_SCHEDULEDISPATCHER:Stop(self,ScheduleID) +end +function SCHEDULER:Remove(ScheduleID) +self:F3({ScheduleID}) +self:T(string.format("Removing scheduler ID=%s",tostring(ScheduleID))) +_SCHEDULEDISPATCHER:RemoveSchedule(self,ScheduleID) +end +function SCHEDULER:Clear() +self:F3() +self:T(string.format("Clearing scheduler")) +_SCHEDULEDISPATCHER:Clear(self) +end +function SCHEDULER:ShowTrace() +_SCHEDULEDISPATCHER:ShowTrace(self) +end +function SCHEDULER:NoTrace() +_SCHEDULEDISPATCHER:NoTrace(self) +end +SCHEDULEDISPATCHER={ +ClassName="SCHEDULEDISPATCHER", +CallID=0, +PersistentSchedulers={}, +ObjectSchedulers={}, +Schedule=nil, +} +function SCHEDULEDISPATCHER:New() +local self=BASE:Inherit(self,BASE:New()) +self:F3() +return self +end +function SCHEDULEDISPATCHER:AddSchedule(Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm) +self:F2({Scheduler,ScheduleFunction,ScheduleArguments,Start,Repeat,Randomize,Stop,TraceLevel,Fsm}) +self.CallID=self.CallID+1 +local CallID=self.CallID.."#"..(Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID()or"")or"" +self:T2(string.format("Adding schedule #%d CallID=%s",self.CallID,CallID)) +self.PersistentSchedulers=self.PersistentSchedulers or{} +self.ObjectSchedulers=self.ObjectSchedulers or setmetatable({},{__mode="v"}) +if Scheduler.MasterObject then +self.ObjectSchedulers[CallID]=Scheduler +self:F3({CallID=CallID,ObjectScheduler=tostring(self.ObjectSchedulers[CallID]),MasterObject=tostring(Scheduler.MasterObject)}) +else +self.PersistentSchedulers[CallID]=Scheduler +self:F3({CallID=CallID,PersistentScheduler=self.PersistentSchedulers[CallID]}) +end +self.Schedule=self.Schedule or setmetatable({},{__mode="k"}) +self.Schedule[Scheduler]=self.Schedule[Scheduler]or{} +self.Schedule[Scheduler][CallID]={} +self.Schedule[Scheduler][CallID].Function=ScheduleFunction +self.Schedule[Scheduler][CallID].Arguments=ScheduleArguments +self.Schedule[Scheduler][CallID].StartTime=timer.getTime()+(Start or 0) +self.Schedule[Scheduler][CallID].Start=Start+0.001 +self.Schedule[Scheduler][CallID].Repeat=Repeat or 0 +self.Schedule[Scheduler][CallID].Randomize=Randomize or 0 +self.Schedule[Scheduler][CallID].Stop=Stop +local Info={} +if debug then +TraceLevel=TraceLevel or 2 +Info=debug.getinfo(TraceLevel,"nlS") +local name_fsm=debug.getinfo(TraceLevel-1,"n").name +if name_fsm then +Info.name=name_fsm +end +end +self:T3(self.Schedule[Scheduler][CallID]) +self.Schedule[Scheduler][CallID].CallHandler=function(Params) +local CallID=Params.CallID +local Info=Params.Info or{} +local Source=Info.source or"?" +local Line=Info.currentline or"?" +local Name=Info.name or"?" +local ErrorHandler=function(errmsg) +env.info("Error in timer function: "..errmsg) +if BASE.Debug~=nil then +env.info(BASE.Debug.traceback()) +end +return errmsg +end +local Scheduler=self.ObjectSchedulers[CallID] +if not Scheduler then +Scheduler=self.PersistentSchedulers[CallID] +end +if Scheduler then +local MasterObject=tostring(Scheduler.MasterObject) +local Schedule=self.Schedule[Scheduler][CallID] +local SchedulerObject=Scheduler.MasterObject +local ShowTrace=Scheduler.ShowTrace +local ScheduleFunction=Schedule.Function +local ScheduleArguments=Schedule.Arguments or{} +local Start=Schedule.Start +local Repeat=Schedule.Repeat or 0 +local Randomize=Schedule.Randomize or 0 +local Stop=Schedule.Stop or 0 +local ScheduleID=Schedule.ScheduleID +local Prefix=(Repeat==0)and"--->"or"+++>" +local Status,Result +if SchedulerObject then +local function Timer() +if ShowTrace then +SchedulerObject:T(Prefix..Name..":"..Line.." ("..Source..")") +end +return ScheduleFunction(SchedulerObject,unpack(ScheduleArguments)) +end +Status,Result=xpcall(Timer,ErrorHandler) +else +local function Timer() +if ShowTrace then +self:T(Prefix..Name..":"..Line.." ("..Source..")") +end +return ScheduleFunction(unpack(ScheduleArguments)) +end +Status,Result=xpcall(Timer,ErrorHandler) +end +local CurrentTime=timer.getTime() +local StartTime=Schedule.StartTime +self:F3({CallID=CallID,ScheduleID=ScheduleID,Master=MasterObject,CurrentTime=CurrentTime,StartTime=StartTime,Start=Start,Repeat=Repeat,Randomize=Randomize,Stop=Stop}) +if Status and((Result==nil)or(Result and Result~=false))then +if Repeat~=0 and((Stop==0)or(Stop~=0 and CurrentTime<=StartTime+Stop))then +local ScheduleTime=CurrentTime+Repeat+math.random(-(Randomize*Repeat/2),(Randomize*Repeat/2))+0.0001 +return ScheduleTime +else +self:Stop(Scheduler,CallID) +end +else +self:Stop(Scheduler,CallID) +end +else +self:I("<<<>"..Name..":"..Line.." ("..Source..")") +end +return nil +end +self:Start(Scheduler,CallID,Info) +return CallID +end +function SCHEDULEDISPATCHER:RemoveSchedule(Scheduler,CallID) +self:F2({Remove=CallID,Scheduler=Scheduler}) +if CallID then +self:Stop(Scheduler,CallID) +self.Schedule[Scheduler][CallID]=nil +end +end +function SCHEDULEDISPATCHER:Start(Scheduler,CallID,Info) +self:F2({Start=CallID,Scheduler=Scheduler}) +if CallID then +local Schedule=self.Schedule[Scheduler][CallID] +if not Schedule.ScheduleID then +local Tnow=timer.getTime() +Schedule.StartTime=Tnow +Schedule.ScheduleID=timer.scheduleFunction(Schedule.CallHandler,{CallID=CallID,Info=Info},Tnow+Schedule.Start) +self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s",tostring(CallID),tostring(Schedule.ScheduleID))) +end +else +for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do +self:Start(Scheduler,CallID,Info) +end +end +end +function SCHEDULEDISPATCHER:Stop(Scheduler,CallID) +self:F2({Stop=CallID,Scheduler=Scheduler}) +if CallID then +local Schedule=self.Schedule[Scheduler][CallID] +if Schedule.ScheduleID then +self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s",tostring(CallID),tostring(Schedule.ScheduleID))) +timer.removeFunction(Schedule.ScheduleID) +Schedule.ScheduleID=nil +else +self:T(string.format("Error no ScheduleID for CallID=%s",tostring(CallID))) +end +else +for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do +self:Stop(Scheduler,CallID) +end +end +end +function SCHEDULEDISPATCHER:Clear(Scheduler) +self:F2({Scheduler=Scheduler}) +for CallID,Schedule in pairs(self.Schedule[Scheduler]or{})do +self:Stop(Scheduler,CallID) +end +end +function SCHEDULEDISPATCHER:ShowTrace(Scheduler) +self:F2({Scheduler=Scheduler}) +Scheduler.ShowTrace=true +end +function SCHEDULEDISPATCHER:NoTrace(Scheduler) +self:F2({Scheduler=Scheduler}) +Scheduler.ShowTrace=false +end +EVENT={ +ClassName="EVENT", +ClassID=0, +MissionEnd=false, +} +world.event.S_EVENT_NEW_CARGO=world.event.S_EVENT_MAX+1000 +world.event.S_EVENT_DELETE_CARGO=world.event.S_EVENT_MAX+1001 +world.event.S_EVENT_NEW_ZONE=world.event.S_EVENT_MAX+1002 +world.event.S_EVENT_DELETE_ZONE=world.event.S_EVENT_MAX+1003 +world.event.S_EVENT_NEW_ZONE_GOAL=world.event.S_EVENT_MAX+1004 +world.event.S_EVENT_DELETE_ZONE_GOAL=world.event.S_EVENT_MAX+1005 +world.event.S_EVENT_REMOVE_UNIT=world.event.S_EVENT_MAX+1006 +world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT=world.event.S_EVENT_MAX+1007 +EVENTS={ +Shot=world.event.S_EVENT_SHOT, +Hit=world.event.S_EVENT_HIT, +Takeoff=world.event.S_EVENT_TAKEOFF, +Land=world.event.S_EVENT_LAND, +Crash=world.event.S_EVENT_CRASH, +Ejection=world.event.S_EVENT_EJECTION, +Refueling=world.event.S_EVENT_REFUELING, +Dead=world.event.S_EVENT_DEAD, +PilotDead=world.event.S_EVENT_PILOT_DEAD, +BaseCaptured=world.event.S_EVENT_BASE_CAPTURED, +MissionStart=world.event.S_EVENT_MISSION_START, +MissionEnd=world.event.S_EVENT_MISSION_END, +TookControl=world.event.S_EVENT_TOOK_CONTROL, +RefuelingStop=world.event.S_EVENT_REFUELING_STOP, +Birth=world.event.S_EVENT_BIRTH, +HumanFailure=world.event.S_EVENT_HUMAN_FAILURE, +EngineStartup=world.event.S_EVENT_ENGINE_STARTUP, +EngineShutdown=world.event.S_EVENT_ENGINE_SHUTDOWN, +PlayerEnterUnit=world.event.S_EVENT_PLAYER_ENTER_UNIT, +PlayerLeaveUnit=world.event.S_EVENT_PLAYER_LEAVE_UNIT, +PlayerComment=world.event.S_EVENT_PLAYER_COMMENT, +ShootingStart=world.event.S_EVENT_SHOOTING_START, +ShootingEnd=world.event.S_EVENT_SHOOTING_END, +MarkAdded=world.event.S_EVENT_MARK_ADDED, +MarkChange=world.event.S_EVENT_MARK_CHANGE, +MarkRemoved=world.event.S_EVENT_MARK_REMOVED, +NewCargo=world.event.S_EVENT_NEW_CARGO, +DeleteCargo=world.event.S_EVENT_DELETE_CARGO, +NewZone=world.event.S_EVENT_NEW_ZONE, +DeleteZone=world.event.S_EVENT_DELETE_ZONE, +NewZoneGoal=world.event.S_EVENT_NEW_ZONE_GOAL, +DeleteZoneGoal=world.event.S_EVENT_DELETE_ZONE_GOAL, +RemoveUnit=world.event.S_EVENT_REMOVE_UNIT, +PlayerEnterAircraft=world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT, +DetailedFailure=world.event.S_EVENT_DETAILED_FAILURE or-1, +Kill=world.event.S_EVENT_KILL or-1, +Score=world.event.S_EVENT_SCORE or-1, +UnitLost=world.event.S_EVENT_UNIT_LOST or-1, +LandingAfterEjection=world.event.S_EVENT_LANDING_AFTER_EJECTION or-1, +ParatrooperLanding=world.event.S_EVENT_PARATROOPER_LENDING or-1, +DiscardChairAfterEjection=world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or-1, +WeaponAdd=world.event.S_EVENT_WEAPON_ADD or-1, +TriggerZone=world.event.S_EVENT_TRIGGER_ZONE or-1, +LandingQualityMark=world.event.S_EVENT_LANDING_QUALITY_MARK or-1, +BDA=world.event.S_EVENT_BDA or-1, +} +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" +}, +} +function EVENT:New() +local self=BASE:Inherit(self,BASE:New()) +self.EventHandler=world.addEventHandler(self) +return self +end +function EVENT:Init(EventID,EventClass) +self:F3({_EVENTMETA[EventID].Text,EventClass}) +if not self.Events[EventID]then +self.Events[EventID]={} +end +local EventPriority=EventClass:GetEventPriority() +if not self.Events[EventID][EventPriority]then +self.Events[EventID][EventPriority]=setmetatable({},{__mode="k"}) +end +if not self.Events[EventID][EventPriority][EventClass]then +self.Events[EventID][EventPriority][EventClass]={} +end +return self.Events[EventID][EventPriority][EventClass] +end +function EVENT:RemoveEvent(EventClass,EventID) +self:F2({"Removing subscription for class: ",EventClass:GetClassNameAndID()}) +local EventPriority=EventClass:GetEventPriority() +self.Events=self.Events or{} +self.Events[EventID]=self.Events[EventID]or{} +self.Events[EventID][EventPriority]=self.Events[EventID][EventPriority]or{} +self.Events[EventID][EventPriority][EventClass]=nil +return self +end +function EVENT:Reset(EventObject) +self:F({"Resetting subscriptions for class: ",EventObject:GetClassNameAndID()}) +local EventPriority=EventObject:GetEventPriority() +for EventID,EventData in pairs(self.Events)do +if self.EventsDead then +if self.EventsDead[EventID]then +if self.EventsDead[EventID][EventPriority]then +if self.EventsDead[EventID][EventPriority][EventObject]then +self.Events[EventID][EventPriority][EventObject]=self.EventsDead[EventID][EventPriority][EventObject] +end +end +end +end +end +end +function EVENT:RemoveAll(EventClass) +local EventClassName=EventClass:GetClassNameAndID() +local EventPriority=EventClass:GetEventPriority() +for EventID,EventData in pairs(self.Events)do +self.Events[EventID][EventPriority][EventClass]=nil +end +return self +end +function EVENT:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EventID) +self:F2(EventTemplate.name) +for EventUnitID,EventUnit in pairs(EventTemplate.units)do +self:OnEventForUnit(EventUnit.name,EventFunction,EventClass,EventID) +end +return self +end +function EVENT:OnEventGeneric(EventFunction,EventClass,EventID) +self:F2({EventID,EventClass,EventFunction}) +local EventData=self:Init(EventID,EventClass) +EventData.EventFunction=EventFunction +return self +end +function EVENT:OnEventForUnit(UnitName,EventFunction,EventClass,EventID) +self:F2(UnitName) +local EventData=self:Init(EventID,EventClass) +EventData.EventUnit=true +EventData.EventFunction=EventFunction +return self +end +function EVENT:OnEventForGroup(GroupName,EventFunction,EventClass,EventID,...) +local Event=self:Init(EventID,EventClass) +Event.EventGroup=true +Event.EventFunction=EventFunction +Event.Params=arg +return self +end +do +function EVENT:OnBirthForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Birth) +return self +end +end +do +function EVENT:OnCrashForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Crash) +return self +end +end +do +function EVENT:OnDeadForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Dead) +return self +end +end +do +function EVENT:OnLandForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Land) +return self +end +end +do +function EVENT:OnTakeOffForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.Takeoff) +return self +end +end +do +function EVENT:OnEngineShutDownForTemplate(EventTemplate,EventFunction,EventClass) +self:F2(EventTemplate.name) +self:OnEventForTemplate(EventTemplate,EventFunction,EventClass,EVENTS.EngineShutdown) +return self +end +end +do +function EVENT:CreateEventNewCargo(Cargo) +self:F({Cargo}) +local Event={ +id=EVENTS.NewCargo, +time=timer.getTime(), +cargo=Cargo, +} +world.onEvent(Event) +end +function EVENT:CreateEventDeleteCargo(Cargo) +self:F({Cargo}) +local Event={ +id=EVENTS.DeleteCargo, +time=timer.getTime(), +cargo=Cargo, +} +world.onEvent(Event) +end +function EVENT:CreateEventNewZone(Zone) +self:F({Zone}) +local Event={ +id=EVENTS.NewZone, +time=timer.getTime(), +zone=Zone, +} +world.onEvent(Event) +end +function EVENT:CreateEventDeleteZone(Zone) +self:F({Zone}) +local Event={ +id=EVENTS.DeleteZone, +time=timer.getTime(), +zone=Zone, +} +world.onEvent(Event) +end +function EVENT:CreateEventNewZoneGoal(ZoneGoal) +self:F({ZoneGoal}) +local Event={ +id=EVENTS.NewZoneGoal, +time=timer.getTime(), +ZoneGoal=ZoneGoal, +} +world.onEvent(Event) +end +function EVENT:CreateEventDeleteZoneGoal(ZoneGoal) +self:F({ZoneGoal}) +local Event={ +id=EVENTS.DeleteZoneGoal, +time=timer.getTime(), +ZoneGoal=ZoneGoal, +} +world.onEvent(Event) +end +function EVENT:CreateEventPlayerEnterUnit(PlayerUnit) +self:F({PlayerUnit}) +local Event={ +id=EVENTS.PlayerEnterUnit, +time=timer.getTime(), +initiator=PlayerUnit:GetDCSObject() +} +world.onEvent(Event) +end +function EVENT:CreateEventPlayerEnterAircraft(PlayerUnit) +self:F({PlayerUnit}) +local Event={ +id=EVENTS.PlayerEnterAircraft, +time=timer.getTime(), +initiator=PlayerUnit:GetDCSObject() +} +world.onEvent(Event) +end +end +function EVENT:onEvent(Event) +local ErrorHandler=function(errmsg) +env.info("Error in SCHEDULER function:"..errmsg) +if BASE.Debug~=nil then +env.info(debug.traceback()) +end +return errmsg +end +local EventMeta=_EVENTMETA[Event.id] +if EventMeta then +if self and +self.Events and +self.Events[Event.id]and +self.MissionEnd==false and +(Event.initiator~=nil or(Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit))then +if Event.id and Event.id==EVENTS.MissionEnd then +self.MissionEnd=true +end +if Event.initiator then +Event.IniObjectCategory=Event.initiator:getCategory() +if 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="" +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() +Event.IniCoalition=Event.IniDCSUnit:getCoalition() +Event.IniTypeName=Event.IniDCSUnit:getTypeName() +Event.IniCategory=Event.IniDCSUnit:getDesc().category +end +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 +end +if Event.IniObjectCategory==Object.Category.CARGO then +Event.IniDCSUnit=Event.initiator +Event.IniDCSUnitName=Event.IniDCSUnit:getName() +Event.IniUnitName=Event.IniDCSUnitName +Event.IniUnit=CARGO:FindByName(Event.IniDCSUnitName) +Event.IniCoalition=Event.IniDCSUnit:getCoalition() +Event.IniCategory=Event.IniDCSUnit:getDesc().category +Event.IniTypeName=Event.IniDCSUnit:getTypeName() +end +if Event.IniObjectCategory==Object.Category.SCENERY then +Event.IniDCSUnit=Event.initiator +Event.IniDCSUnitName=Event.IniDCSUnit:getName() +Event.IniUnitName=Event.IniDCSUnitName +Event.IniUnit=SCENERY:Register(Event.IniDCSUnitName,Event.initiator) +Event.IniCategory=Event.IniDCSUnit:getDesc().category +Event.IniTypeName=Event.initiator:isExist()and Event.IniDCSUnit:getTypeName()or"SCENERY" +end +if 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() +end +end +if Event.target then +Event.TgtObjectCategory=Event.target:getCategory() +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() +Event.TgtCoalition=Event.TgtDCSUnit:getCoalition() +Event.TgtCategory=Event.TgtDCSUnit:getDesc().category +Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() +end +if Event.TgtObjectCategory==Object.Category.STATIC then +BASE:T({StaticTgtEvent=Event.id}) +Event.TgtDCSUnit=Event.target +if Event.target:isExist()and Event.id~=33 then +Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() +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() +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 +end +if Event.TgtObjectCategory==Object.Category.SCENERY then +Event.TgtDCSUnit=Event.target +Event.TgtDCSUnitName=Event.TgtDCSUnit:getName() +Event.TgtUnitName=Event.TgtDCSUnitName +Event.TgtUnit=SCENERY:Register(Event.TgtDCSUnitName,Event.target) +Event.TgtCategory=Event.TgtDCSUnit:getDesc().category +Event.TgtTypeName=Event.TgtDCSUnit:getTypeName() +end +end +if Event.weapon then +Event.Weapon=Event.weapon +Event.WeaponName=Event.Weapon:getTypeName() +Event.WeaponUNIT=CLIENT:Find(Event.Weapon,'',true) +Event.WeaponPlayerName=Event.WeaponUNIT and Event.Weapon:getPlayerName() +Event.WeaponCoalition=Event.WeaponUNIT and Event.Weapon:getCoalition() +Event.WeaponCategory=Event.WeaponUNIT and Event.Weapon:getDesc().category +Event.WeaponTypeName=Event.WeaponUNIT and Event.Weapon:getTypeName() +end +if Event.place then +if Event.id==EVENTS.LandingAfterEjection then +else +Event.Place=AIRBASE:Find(Event.place) +Event.PlaceName=Event.Place:GetName() +end +end +if Event.idx then +Event.MarkID=Event.idx +Event.MarkVec3=Event.pos +Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) +Event.MarkText=Event.text +Event.MarkCoalition=Event.coalition +Event.MarkGroupID=Event.groupID +end +if Event.cargo then +Event.Cargo=Event.cargo +Event.CargoName=Event.cargo.Name +end +if Event.zone then +Event.Zone=Event.zone +Event.ZoneName=Event.zone.ZoneName +end +local PriorityOrder=EventMeta.Order +local PriorityBegin=PriorityOrder==-1 and 5 or 1 +local PriorityEnd=PriorityOrder==-1 and 1 or 5 +if Event.IniObjectCategory~=Object.Category.STATIC then +self:F({EventMeta.Text,Event,Event.IniDCSUnitName,Event.TgtDCSUnitName,PriorityOrder}) +end +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=GROUP:FindByName(Event.IniDCSGroupName) +Event.TgtGroup=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 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 +if Event.IniObjectCategory~=3 then +self:F({"Calling EventFunction for UNIT ",EventClass:GetClassNameAndID(),", Unit ",Event.IniUnitName,EventPriority}) +end +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 +if Event.IniObjectCategory~=3 then +self:F({"Calling "..EventMeta.Event.." for Class ",EventClass:GetClassNameAndID(),EventPriority}) +end +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 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 +if Event.IniObjectCategory~=3 then +self:F({"Calling EventFunction for GROUP ",EventClass:GetClassNameAndID(),", Unit ",Event.IniUnitName,EventPriority}) +end +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 +if Event.IniObjectCategory~=3 then +self:F({"Calling "..EventMeta.Event.." for GROUP ",EventClass:GetClassNameAndID(),EventPriority}) +end +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 +if Event.IniObjectCategory~=3 then +self:F2({"Calling EventFunction for Class ",EventClass:GetClassNameAndID(),EventPriority}) +end +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 +if Event.IniObjectCategory~=3 then +self:F2({"Calling "..EventMeta.Event.." for Class ",EventClass:GetClassNameAndID(),EventPriority}) +end +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() +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: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:IsMetric()) +end +function SETTINGS:SetLL_Accuracy(LL_Accuracy) +self.LL_Accuracy=LL_Accuracy +end +function SETTINGS:GetLL_DDM_Accuracy() +return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy() +end +function SETTINGS:SetMGRS_Accuracy(MGRS_Accuracy) +self.MGRS_Accuracy=MGRS_Accuracy +end +function SETTINGS:GetMGRS_Accuracy() +return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy() +end +function SETTINGS:SetMessageTime(MessageType,MessageTime) +self.MessageTypeTimings=self.MessageTypeTimings or{} +self.MessageTypeTimings[MessageType]=MessageTime +end +function SETTINGS:GetMessageTime(MessageType) +return(self.MessageTypeTimings and self.MessageTypeTimings[MessageType])or _SETTINGS:GetMessageTime(MessageType) +end +function SETTINGS:SetA2G_LL_DMS() +self.A2GSystem="LL DMS" +end +function SETTINGS:SetA2G_LL_DDM() +self.A2GSystem="LL DDM" +end +function SETTINGS:IsA2G_LL_DMS() +return(self.A2GSystem and self.A2GSystem=="LL DMS")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS()) +end +function SETTINGS:IsA2G_LL_DDM() +return(self.A2GSystem and self.A2GSystem=="LL DDM")or(not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM()) +end +function SETTINGS:SetA2G_MGRS() +self.A2GSystem="MGRS" +end +function SETTINGS:IsA2G_MGRS() +return(self.A2GSystem and self.A2GSystem=="MGRS")or(not self.A2GSystem and _SETTINGS:IsA2G_MGRS()) +end +function SETTINGS:SetA2G_BR() +self.A2GSystem="BR" +end +function SETTINGS:IsA2G_BR() +return(self.A2GSystem and self.A2GSystem=="BR")or(not self.A2GSystem and _SETTINGS:IsA2G_BR()) +end +function SETTINGS:SetA2A_BRAA() +self.A2ASystem="BRAA" +end +function SETTINGS:IsA2A_BRAA() +return(self.A2ASystem and self.A2ASystem=="BRAA")or(not self.A2ASystem and _SETTINGS:IsA2A_BRAA()) +end +function SETTINGS:SetA2A_BULLS() +self.A2ASystem="BULLS" +end +function SETTINGS:IsA2A_BULLS() +return(self.A2ASystem and self.A2ASystem=="BULLS")or(not self.A2ASystem and _SETTINGS:IsA2A_BULLS()) +end +function SETTINGS:SetA2A_LL_DMS() +self.A2ASystem="LL DMS" +end +function SETTINGS:SetA2A_LL_DDM() +self.A2ASystem="LL DDM" +end +function SETTINGS:IsA2A_LL_DMS() +return(self.A2ASystem and self.A2ASystem=="LL DMS")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS()) +end +function SETTINGS:IsA2A_LL_DDM() +return(self.A2ASystem and self.A2ASystem=="LL DDM")or(not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM()) +end +function SETTINGS:SetA2A_MGRS() +self.A2ASystem="MGRS" +end +function SETTINGS:IsA2A_MGRS() +return(self.A2ASystem and self.A2ASystem=="MGRS")or(not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) +end +function SETTINGS:SetSystemMenu(MenuGroup,RootMenu) +local MenuText="System Settings" +local MenuTime=timer.getTime() +local SettingsMenu=MENU_GROUP:New(MenuGroup,MenuText,RootMenu):SetTime(MenuTime) +local text="A2G Coordinate System" +if _SETTINGS.MenuShort then +text="A2G Coordinates" +end +local A2GCoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +if not self:IsA2G_LL_DMS()then +local text="Lat/Lon Degree Min Sec (LL DMS)" +if _SETTINGS.MenuShort then +text="LL DMS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime) +end +if not self:IsA2G_LL_DDM()then +local text="Lat/Lon Degree Dec Min (LL DDM)" +if _SETTINGS.MenuShort then +text="LL DDM" +end +MENU_GROUP_COMMAND:New(MenuGroup,"Lat/Lon Degree Dec Min (LL DDM)",A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime) +end +if self:IsA2G_LL_DDM()then +local text1="LL DDM Accuracy 1" +local text2="LL DDM Accuracy 2" +local text3="LL DDM Accuracy 3" +if _SETTINGS.MenuShort then +text1="LL DDM" +end +MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 1",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 2",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL DDM Accuracy 3",A2GCoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +end +if not self:IsA2G_BR()then +local text="Bearing, Range (BR)" +if _SETTINGS.MenuShort then +text="BR" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"BR"):SetTime(MenuTime) +end +if not self:IsA2G_MGRS()then +local text="Military Grid (MGRS)" +if _SETTINGS.MenuShort then +text="MGRS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2GCoordinateMenu,self.A2GMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime) +end +if self:IsA2G_MGRS()then +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2GCoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime) +end +local text="A2A Coordinate System" +if _SETTINGS.MenuShort then +text="A2A Coordinates" +end +local A2ACoordinateMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +if not self:IsA2A_LL_DMS()then +local text="Lat/Lon Degree Min Sec (LL DMS)" +if _SETTINGS.MenuShort then +text="LL DMS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DMS"):SetTime(MenuTime) +end +if not self:IsA2A_LL_DDM()then +local text="Lat/Lon Degree Dec Min (LL DDM)" +if _SETTINGS.MenuShort then +text="LL DDM" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"LL DDM"):SetTime(MenuTime) +end +if self:IsA2A_LL_DDM()or self:IsA2A_LL_DMS()then +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 0",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,0):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 1",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 2",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"LL Accuracy 3",A2ACoordinateMenu,self.MenuLL_DDM_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +end +if not self:IsA2A_BULLS()then +local text="Bullseye (BULLS)" +if _SETTINGS.MenuShort then +text="Bulls" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BULLS"):SetTime(MenuTime) +end +if not self:IsA2A_BRAA()then +local text="Bearing Range Altitude Aspect (BRAA)" +if _SETTINGS.MenuShort then +text="BRAA" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"BRAA"):SetTime(MenuTime) +end +if not self:IsA2A_MGRS()then +local text="Military Grid (MGRS)" +if _SETTINGS.MenuShort then +text="MGRS" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,A2ACoordinateMenu,self.A2AMenuSystem,self,MenuGroup,RootMenu,"MGRS"):SetTime(MenuTime) +end +if self:IsA2A_MGRS()then +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 1",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,1):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 2",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,2):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 3",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,3):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 4",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,4):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"MGRS Accuracy 5",A2ACoordinateMenu,self.MenuMGRS_Accuracy,self,MenuGroup,RootMenu,5):SetTime(MenuTime) +end +local text="Measures and Weights System" +if _SETTINGS.MenuShort then +text="Unit System" +end +local MetricsMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +if self:IsMetric()then +local text="Imperial (Miles,Feet)" +if _SETTINGS.MenuShort then +text="Imperial" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,false):SetTime(MenuTime) +end +if self:IsImperial()then +local text="Metric (Kilometers,Meters)" +if _SETTINGS.MenuShort then +text="Metric" +end +MENU_GROUP_COMMAND:New(MenuGroup,text,MetricsMenu,self.MenuMWSystem,self,MenuGroup,RootMenu,true):SetTime(MenuTime) +end +local text="Messages and Reports" +if _SETTINGS.MenuShort then +text="Messages & Reports" +end +local MessagesMenu=MENU_GROUP:New(MenuGroup,text,SettingsMenu):SetTime(MenuTime) +local UpdateMessagesMenu=MENU_GROUP:New(MenuGroup,"Update Messages",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"Off",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,0):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,5):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,10):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",UpdateMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Update,60):SetTime(MenuTime) +local InformationMessagesMenu=MENU_GROUP:New(MenuGroup,"Information Messages",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"5 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,5):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"10 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,10):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",InformationMessagesMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Information,120):SetTime(MenuTime) +local BriefingReportsMenu=MENU_GROUP:New(MenuGroup,"Briefing Reports",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,120):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",BriefingReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Briefing,180):SetTime(MenuTime) +local OverviewReportsMenu=MENU_GROUP:New(MenuGroup,"Overview Reports",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,120):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",OverviewReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.Overview,180):SetTime(MenuTime) +local DetailedReportsMenu=MENU_GROUP:New(MenuGroup,"Detailed Reports",MessagesMenu):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"15 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,15):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"30 seconds",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,30):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"1 minute",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,60):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"2 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,120):SetTime(MenuTime) +MENU_GROUP_COMMAND:New(MenuGroup,"3 minutes",DetailedReportsMenu,self.MenuMessageTimingsSystem,self,MenuGroup,RootMenu,MESSAGE.Type.DetailedReportsMenu,180):SetTime(MenuTime) +SettingsMenu:Remove(MenuTime) +return self +end +function SETTINGS:SetPlayerMenuOn() +self.ShowPlayerMenu=true +end +function SETTINGS:SetPlayerMenuOff() +self.ShowPlayerMenu=false +end +function SETTINGS:SetPlayerMenu(PlayerUnit) +if _SETTINGS.ShowPlayerMenu==true then +local PlayerGroup=PlayerUnit:GetGroup() +local PlayerName=PlayerUnit:GetPlayerName() +local PlayerNames=PlayerGroup:GetPlayerNames() +local PlayerMenu=MENU_GROUP:New(PlayerGroup,'Settings "'..PlayerName..'"') +self.PlayerMenu=PlayerMenu +self:I(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) +BASE:E({self,PlayerUnit:GetName(),A2GSystem}) +self.A2GSystem=A2GSystem +MESSAGE:New(string.format("Settings: A2G format set to %s for player %s.",A2GSystem,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupA2ASystem(PlayerUnit,PlayerGroup,PlayerName,A2ASystem) +self.A2ASystem=A2ASystem +MESSAGE:New(string.format("Settings: A2A format set to %s for player %s.",A2ASystem,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupLL_DDM_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,LL_Accuracy) +self.LL_Accuracy=LL_Accuracy +MESSAGE:New(string.format("Settings: LL format accuracy set to %d decimal places for player %s.",LL_Accuracy,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupMGRS_AccuracySystem(PlayerUnit,PlayerGroup,PlayerName,MGRS_Accuracy) +self.MGRS_Accuracy=MGRS_Accuracy +MESSAGE:New(string.format("Settings: MGRS format accuracy set to %d for player %s.",MGRS_Accuracy,PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupMWSystem(PlayerUnit,PlayerGroup,PlayerName,MW) +self.Metric=MW +MESSAGE:New(string.format("Settings: Measurement format set to %s for player %s.",MW and"Metric"or"Imperial",PlayerName),5):ToGroup(PlayerGroup) +if _SETTINGS.MenuStatic==false then +self:RemovePlayerMenu(PlayerUnit) +self:SetPlayerMenu(PlayerUnit) +end +end +function SETTINGS:MenuGroupMessageTimingsSystem(PlayerUnit,PlayerGroup,PlayerName,MessageType,MessageTime) +self:SetMessageTime(MessageType,MessageTime) +MESSAGE:New(string.format("Settings: Default message time set for %s to %d.",MessageType,MessageTime),5):ToGroup(PlayerGroup) +end +end +function SETTINGS:SetEraWWII() +self.Era=SETTINGS.__Enum.Era.WWII +end +function SETTINGS:SetEraKorea() +self.Era=SETTINGS.__Enum.Era.Korea +end +function SETTINGS:SetEraCold() +self.Era=SETTINGS.__Enum.Era.Cold +end +function SETTINGS:SetEraModern() +self.Era=SETTINGS.__Enum.Era.Modern +end +end +MENU_INDEX={} +MENU_INDEX.MenuMission={} +MENU_INDEX.MenuMission.Menus={} +MENU_INDEX.Coalition={} +MENU_INDEX.Coalition[coalition.side.BLUE]={} +MENU_INDEX.Coalition[coalition.side.BLUE].Menus={} +MENU_INDEX.Coalition[coalition.side.RED]={} +MENU_INDEX.Coalition[coalition.side.RED].Menus={} +MENU_INDEX.Group={} +function MENU_INDEX:ParentPath(ParentMenu,MenuText) +local Path=ParentMenu and"@"..table.concat(ParentMenu.MenuPath or{},"@")or"" +if ParentMenu then +if ParentMenu:IsInstanceOf("MENU_GROUP")or ParentMenu:IsInstanceOf("MENU_GROUP_COMMAND")then +local GroupName=ParentMenu.Group:GetName() +if not self.Group[GroupName].Menus[Path]then +BASE:E({Path=Path,GroupName=GroupName}) +error("Parent path not found in menu index for group menu") +return nil +end +elseif ParentMenu:IsInstanceOf("MENU_COALITION")or ParentMenu:IsInstanceOf("MENU_COALITION_COMMAND")then +local Coalition=ParentMenu.Coalition +if not self.Coalition[Coalition].Menus[Path]then +BASE:E({Path=Path,Coalition=Coalition}) +error("Parent path not found in menu index for coalition menu") +return nil +end +elseif ParentMenu:IsInstanceOf("MENU_MISSION")or ParentMenu:IsInstanceOf("MENU_MISSION_COMMAND")then +if not self.MenuMission.Menus[Path]then +BASE:E({Path=Path}) +error("Parent path not found in menu index for mission menu") +return nil +end +end +end +Path=Path.."@"..MenuText +return Path +end +function MENU_INDEX:PrepareMission() +self.MenuMission.Menus=self.MenuMission.Menus or{} +end +function MENU_INDEX:PrepareCoalition(CoalitionSide) +self.Coalition[CoalitionSide]=self.Coalition[CoalitionSide]or{} +self.Coalition[CoalitionSide].Menus=self.Coalition[CoalitionSide].Menus or{} +end +function MENU_INDEX:PrepareGroup(Group) +if Group and Group:IsAlive()~=nil then +local GroupName=Group:GetName() +self.Group[GroupName]=self.Group[GroupName]or{} +self.Group[GroupName].Menus=self.Group[GroupName].Menus or{} +end +end +function MENU_INDEX:HasMissionMenu(Path) +return self.MenuMission.Menus[Path] +end +function MENU_INDEX:SetMissionMenu(Path,Menu) +self.MenuMission.Menus[Path]=Menu +end +function MENU_INDEX:ClearMissionMenu(Path) +self.MenuMission.Menus[Path]=nil +end +function MENU_INDEX:HasCoalitionMenu(Coalition,Path) +return self.Coalition[Coalition].Menus[Path] +end +function MENU_INDEX:SetCoalitionMenu(Coalition,Path,Menu) +self.Coalition[Coalition].Menus[Path]=Menu +end +function MENU_INDEX:ClearCoalitionMenu(Coalition,Path) +self.Coalition[Coalition].Menus[Path]=nil +end +function MENU_INDEX:HasGroupMenu(Group,Path) +if Group and Group:IsAlive()then +local MenuGroupName=Group:GetName() +return self.Group[MenuGroupName].Menus[Path] +end +return nil +end +function MENU_INDEX:SetGroupMenu(Group,Path,Menu) +local MenuGroupName=Group:GetName() +Group:F({MenuGroupName=MenuGroupName,Path=Path}) +self.Group[MenuGroupName].Menus[Path]=Menu +end +function MENU_INDEX:ClearGroupMenu(Group,Path) +local MenuGroupName=Group:GetName() +self.Group[MenuGroupName].Menus[Path]=nil +end +function MENU_INDEX:Refresh(Group) +for MenuID,Menu in pairs(self.MenuMission.Menus)do +Menu:Refresh() +end +for MenuID,Menu in pairs(self.Coalition[coalition.side.BLUE].Menus)do +Menu:Refresh() +end +for MenuID,Menu in pairs(self.Coalition[coalition.side.RED].Menus)do +Menu:Refresh() +end +local GroupName=Group:GetName() +for MenuID,Menu in pairs(self.Group[GroupName].Menus)do +Menu:Refresh() +end +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 +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 +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 +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 +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 +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 +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() +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 +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 +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, +} +function ZONE_BASE:New(ZoneName) +local self=BASE:Inherit(self,FSM:New()) +self:F(ZoneName) +self.ZoneName=ZoneName +return self +end +function ZONE_BASE:GetName() +self:F2() +return self.ZoneName +end +function ZONE_BASE:SetName(ZoneName) +self:F2() +self.ZoneName=ZoneName +end +function ZONE_BASE:IsVec2InZone(Vec2) +self:F2(Vec2) +return false +end +function ZONE_BASE:IsVec3InZone(Vec3) +local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) +return InZone +end +function ZONE_BASE:IsCoordinateInZone(Coordinate) +local InZone=self:IsVec2InZone(Coordinate:GetVec2()) +return InZone +end +function ZONE_BASE:IsPointVec2InZone(PointVec2) +local InZone=self:IsVec2InZone(PointVec2: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() +self:F2(self.ZoneName) +local Vec2=self:GetVec2() +local PointVec2=POINT_VEC2:NewFromVec2(Vec2) +self:T2({PointVec2}) +return PointVec2 +end +function ZONE_BASE:GetVec3(Height) +self:F2(self.ZoneName) +Height=Height or 0 +local Vec2=self:GetVec2() +local Vec3={x=Vec2.x,y=Height and Height or land.getHeight(self:GetVec2()),z=Vec2.y} +self:T2({Vec3}) +return Vec3 +end +function ZONE_BASE:GetPointVec3(Height) +self:F2(self.ZoneName) +local Vec3=self:GetVec3(Height) +local PointVec3=POINT_VEC3:NewFromVec3(Vec3) +self:T2({PointVec3}) +return PointVec3 +end +function ZONE_BASE:GetCoordinate(Height) +self:F2(self.ZoneName) +local Vec3=self:GetVec3(Height) +if self.Coordinate then +self.Coordinate.x=Vec3.x +self.Coordinate.y=Vec3.y +self.Coordinate.z=Vec3.z +else +self.Coordinate=COORDINATE:NewFromVec3(Vec3) +end +return self.Coordinate +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:BoundZone() +self:F2() +end +function ZONE_BASE:SmokeZone(SmokeColor) +self:F2(SmokeColor) +end +function ZONE_BASE:SetZoneProbability(ZoneProbability) +self:F({self:GetName(),ZoneProbability=ZoneProbability}) +self.ZoneProbability=ZoneProbability or 1 +return self +end +function ZONE_BASE:GetZoneProbability() +self:F2() +return self.ZoneProbability +end +function ZONE_BASE:GetZoneMaybe() +self:F2() +local Randomization=math.random() +if Randomization<=self.ZoneProbability then +return self +else +return nil +end +end +ZONE_RADIUS={ +ClassName="ZONE_RADIUS", +} +function ZONE_RADIUS:New(ZoneName,Vec2,Radius) +local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) +self:F({ZoneName,Vec2,Radius}) +self.Radius=Radius +self.Vec2=Vec2 +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:BoundZone(Points,CountryID,UnBound) +local Point={} +local Vec2=self:GetVec2() +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,(360/Points)do +local Radial=Angle*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +local CountryName=_DATABASE.COUNTRY_NAME[CountryID] +local Tire={ +["country"]=CountryName, +["category"]="Fortifications", +["canCargo"]=false, +["shape_name"]="H-tyre_B_WF", +["type"]="Black_Tyre_WF", +["y"]=Point.y, +["x"]=Point.x, +["name"]=string.format("%s-Tire #%0d",self:GetName(),Angle), +["heading"]=0, +} +local Group=coalition.addStaticObject(CountryID,Tire) +if UnBound and UnBound==true then +Group:destroy() +end +end +return self +end +function ZONE_RADIUS:SmokeZone(SmokeColor,Points,AddHeight,AngleOffset) +self:F2(SmokeColor) +local Point={} +local Vec2=self:GetVec2() +AddHeight=AddHeight or 0 +AngleOffset=AngleOffset or 0 +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,360/Points do +local Radial=(Angle+AngleOffset)*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +POINT_VEC2:New(Point.x,Point.y,AddHeight):Smoke(SmokeColor) +end +return self +end +function ZONE_RADIUS:FlareZone(FlareColor,Points,Azimuth,AddHeight) +self:F2({FlareColor,Azimuth}) +local Point={} +local Vec2=self:GetVec2() +AddHeight=AddHeight or 0 +Points=Points and Points or 360 +local Angle +local RadialBase=math.pi*2 +for Angle=0,360,360/Points do +local Radial=Angle*RadialBase/360 +Point.x=Vec2.x+math.cos(Radial)*self:GetRadius() +Point.y=Vec2.y+math.sin(Radial)*self:GetRadius() +POINT_VEC2:New(Point.x,Point.y,AddHeight):Flare(FlareColor,Azimuth) +end +return self +end +function ZONE_RADIUS:GetRadius() +self:F2(self.ZoneName) +self:T2({self.Radius}) +return self.Radius +end +function ZONE_RADIUS:SetRadius(Radius) +self:F2(self.ZoneName) +self.Radius=Radius +self:T2({self.Radius}) +return self.Radius +end +function ZONE_RADIUS:GetVec2() +self:F2(self.ZoneName) +self:T2({self.Vec2}) +return self.Vec2 +end +function ZONE_RADIUS:SetVec2(Vec2) +self:F2(self.ZoneName) +self.Vec2=Vec2 +self:T2({self.Vec2}) +return self.Vec2 +end +function ZONE_RADIUS:GetVec3(Height) +self:F2({self.ZoneName,Height}) +Height=Height or 0 +local Vec2=self:GetVec2() +local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y} +self:T2({Vec3}) +return Vec3 +end +function ZONE_RADIUS:Scan(ObjectCategories,UnitCategories) +self.ScanData={} +self.ScanData.Coalitions={} +self.ScanData.Scenery={} +self.ScanData.Units={} +local ZoneCoord=self:GetCoordinate() +local ZoneRadius=self:GetRadius() +self:F({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()}) +local SphereSearch={ +id=world.VolumeType.SPHERE, +params={ +point=ZoneCoord:GetVec3(), +radius=ZoneRadius, +} +} +local function EvaluateZone(ZoneObject) +if ZoneObject then +local ObjectCategory=ZoneObject:getCategory() +if(ObjectCategory==Object.Category.UNIT and ZoneObject:isExist()and ZoneObject:isActive())or(ObjectCategory==Object.Category.STATIC and ZoneObject:isExist())then +local CoalitionDCSUnit=ZoneObject:getCoalition() +local Include=false +if not UnitCategories then +Include=true +else +local CategoryDCSUnit=ZoneObject:getDesc().category +for UnitCategoryID,UnitCategory in pairs(UnitCategories)do +if UnitCategory==CategoryDCSUnit then +Include=true +break +end +end +end +if Include then +local CoalitionDCSUnit=ZoneObject:getCoalition() +self.ScanData.Coalitions[CoalitionDCSUnit]=true +self.ScanData.Units[ZoneObject]=ZoneObject +self:F2({Name=ZoneObject:getName(),Coalition=CoalitionDCSUnit}) +end +end +if ObjectCategory==Object.Category.SCENERY then +local SceneryType=ZoneObject:getTypeName() +local SceneryName=ZoneObject:getName() +self.ScanData.Scenery[SceneryType]=self.ScanData.Scenery[SceneryType]or{} +self.ScanData.Scenery[SceneryType][SceneryName]=SCENERY:Register(SceneryName,ZoneObject) +self:F2({SCENERY=self.ScanData.Scenery[SceneryType][SceneryName]}) +end +end +return true +end +world.searchObjects(ObjectCategories,SphereSearch,EvaluateZone) +end +function ZONE_RADIUS:GetScannedUnits() +return self.ScanData.Units +end +function ZONE_RADIUS:GetScannedSetUnit() +local SetUnit=SET_UNIT:New() +if self.ScanData then +for ObjectID,UnitObject in pairs(self.ScanData.Units)do +local UnitObject=UnitObject +if UnitObject:isExist()then +local FoundUnit=UNIT:FindByName(UnitObject:getName()) +if FoundUnit then +SetUnit:AddUnit(FoundUnit) +else +local FoundStatic=STATIC:FindByName(UnitObject:getName()) +if FoundStatic then +SetUnit:AddUnit(FoundStatic) +end +end +end +end +end +return SetUnit +end +function ZONE_RADIUS:GetScannedSetGroup() +self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() +self.ScanSetGroup.Set={} +if self.ScanData then +for ObjectID,UnitObject in pairs(self.ScanData.Units)do +local UnitObject=UnitObject +if UnitObject:isExist()then +local FoundUnit=UNIT:FindByName(UnitObject:getName()) +if FoundUnit then +local group=FoundUnit:GetGroup() +self.ScanSetGroup:AddGroup(group) +end +end +end +end +return self.ScanSetGroup +end +function ZONE_RADIUS:CountScannedCoalitions() +local Count=0 +for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do +Count=Count+1 +end +return Count +end +function ZONE_RADIUS:CheckScannedCoalition(Coalition) +if Coalition then +return self.ScanData.Coalitions[Coalition] +end +return nil +end +function ZONE_RADIUS:GetScannedCoalition(Coalition) +if Coalition then +return self.ScanData.Coalitions[Coalition] +else +local Count=0 +local ReturnCoalition=nil +for CoalitionID,Coalition in pairs(self.ScanData.Coalitions)do +Count=Count+1 +ReturnCoalition=CoalitionID +end +if Count~=1 then +ReturnCoalition=nil +end +return ReturnCoalition +end +end +function ZONE_RADIUS:GetScannedSceneryType(SceneryType) +return self.ScanData.Scenery[SceneryType] +end +function ZONE_RADIUS:GetScannedScenery() +return self.ScanData.Scenery +end +function ZONE_RADIUS:IsAllInZoneOfCoalition(Coalition) +return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==true +end +function ZONE_RADIUS:IsAllInZoneOfOtherCoalition(Coalition) +return self:CountScannedCoalitions()==1 and self:GetScannedCoalition(Coalition)==nil +end +function ZONE_RADIUS:IsSomeInZoneOfCoalition(Coalition) +return self:CountScannedCoalitions()>1 and self:GetScannedCoalition(Coalition)==true +end +function ZONE_RADIUS:IsNoneInZoneOfCoalition(Coalition) +return self:GetScannedCoalition(Coalition)==nil +end +function ZONE_RADIUS:IsNoneInZone() +return self:CountScannedCoalitions()==0 +end +function ZONE_RADIUS:SearchZone(EvaluateFunction,ObjectCategories) +local SearchZoneResult=true +local ZoneCoord=self:GetCoordinate() +local ZoneRadius=self:GetRadius() +self:F({ZoneCoord=ZoneCoord,ZoneRadius=ZoneRadius,ZoneCoordLL=ZoneCoord:ToStringLLDMS()}) +local SphereSearch={ +id=world.VolumeType.SPHERE, +params={ +point=ZoneCoord:GetVec3(), +radius=ZoneRadius/2, +} +} +local function EvaluateZone(ZoneDCSUnit) +local ZoneUnit=UNIT:Find(ZoneDCSUnit) +return EvaluateFunction(ZoneUnit) +end +world.searchObjects(Object.Category.UNIT,SphereSearch,EvaluateZone) +end +function ZONE_RADIUS:IsVec2InZone(Vec2) +self:F2(Vec2) +local ZoneVec2=self:GetVec2() +if ZoneVec2 then +if((Vec2.x-ZoneVec2.x)^2+(Vec2.y-ZoneVec2.y)^2)^0.5<=self:GetRadius()then +return true +end +end +return false +end +function ZONE_RADIUS:IsVec3InZone(Vec3) +self:F2(Vec3) +local InZone=self:IsVec2InZone({x=Vec3.x,y=Vec3.z}) +return InZone +end +function ZONE_RADIUS:GetRandomVec2(inner,outer) +self:F(self.ZoneName,inner,outer) +local Point={} +local Vec2=self:GetVec2() +local _inner=inner or 0 +local _outer=outer or self:GetRadius() +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); +self:T({Point}) +return Point +end +function ZONE_RADIUS:GetRandomPointVec2(inner,outer) +self:F(self.ZoneName,inner,outer) +local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2(inner,outer)) +self:T3({PointVec2}) +return PointVec2 +end +function ZONE_RADIUS:GetRandomVec3(inner,outer) +self:F(self.ZoneName,inner,outer) +local Vec2=self:GetRandomVec2(inner,outer) +self:T3({x=Vec2.x,y=self.y,z=Vec2.y}) +return{x=Vec2.x,y=self.y,z=Vec2.y} +end +function ZONE_RADIUS:GetRandomPointVec3(inner,outer) +self:F(self.ZoneName,inner,outer) +local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2(inner,outer)) +self:T3({PointVec3}) +return PointVec3 +end +function ZONE_RADIUS:GetRandomCoordinate(inner,outer) +self:F(self.ZoneName,inner,outer) +local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2(inner,outer)) +self:T3({Coordinate=Coordinate}) +return Coordinate +end +ZONE={ +ClassName="ZONE", +} +function ZONE:New(ZoneName) +local Zone=trigger.misc.getZone(ZoneName) +if not Zone then +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)) +self:F(ZoneName) +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 +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 +local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneUNIT:GetVec2(),Radius)) +self:F({ZoneName,ZoneUNIT:GetVec2(),Radius}) +self.ZoneUNIT=ZoneUNIT +self.LastVec2=ZoneUNIT:GetVec2() +_EVENTDISPATCHER:CreateEventNewZone(self) +return self +end +function ZONE_UNIT:GetVec2() +self:F2(self.ZoneName) +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 +self:T2({ZoneVec2}) +return nil +end +function ZONE_UNIT:GetRandomVec2() +self:F(self.ZoneName) +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(); +self:T({RandomVec2}) +return RandomVec2 +end +function ZONE_UNIT:GetVec3(Height) +self:F2(self.ZoneName) +Height=Height or 0 +local Vec2=self:GetVec2() +local Vec3={x=Vec2.x,y=land.getHeight(self:GetVec2())+Height,z=Vec2.y} +self:T2({Vec3}) +return Vec3 +end +ZONE_GROUP={ +ClassName="ZONE_GROUP", +} +function ZONE_GROUP:New(ZoneName,ZoneGROUP,Radius) +local self=BASE:Inherit(self,ZONE_RADIUS:New(ZoneName,ZoneGROUP:GetVec2(),Radius)) +self:F({ZoneName,ZoneGROUP:GetVec2(),Radius}) +self._.ZoneGROUP=ZoneGROUP +self._.ZoneVec2Cache=self._.ZoneGROUP:GetVec2() +_EVENTDISPATCHER:CreateEventNewZone(self) +return self +end +function ZONE_GROUP:GetVec2() +self:F(self.ZoneName) +local ZoneVec2=nil +if self._.ZoneGROUP:IsAlive()then +ZoneVec2=self._.ZoneGROUP:GetVec2() +self._.ZoneVec2Cache=ZoneVec2 +else +ZoneVec2=self._.ZoneVec2Cache +end +self:T({ZoneVec2}) +return ZoneVec2 +end +function ZONE_GROUP:GetRandomVec2() +self:F(self.ZoneName) +local Point={} +local Vec2=self._.ZoneGROUP:GetVec2() +local angle=math.random()*math.pi*2; +Point.x=Vec2.x+math.cos(angle)*math.random()*self:GetRadius(); +Point.y=Vec2.y+math.sin(angle)*math.random()*self:GetRadius(); +self:T({Point}) +return Point +end +function ZONE_GROUP:GetRandomPointVec2(inner,outer) +self:F(self.ZoneName,inner,outer) +local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2()) +self:T3({PointVec2}) +return PointVec2 +end +ZONE_POLYGON_BASE={ +ClassName="ZONE_POLYGON_BASE", +} +function ZONE_POLYGON_BASE:New(ZoneName,PointsArray) +local self=BASE:Inherit(self,ZONE_BASE:New(ZoneName)) +self:F({ZoneName,PointsArray}) +if PointsArray then +self._.Polygon={} +for i=1,#PointsArray do +self._.Polygon[i]={} +self._.Polygon[i].x=PointsArray[i].x +self._.Polygon[i].y=PointsArray[i].y +end +end +return self +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 +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 +return self +end +function ZONE_POLYGON_BASE:GetVec2() +self:F(self.ZoneName) +local Bounds=self:GetBoundingSquare() +return{x=(Bounds.x2+Bounds.x1)/2,y=(Bounds.y2+Bounds.y1)/2} +end +function ZONE_POLYGON_BASE:Flush() +self:F2() +self:F({Polygon=self.ZoneName,Coordinates=self._.Polygon}) +return self +end +function ZONE_POLYGON_BASE:BoundZone(UnBound) +local i +local j +local Segments=10 +i=1 +j=#self._.Polygon +while i<=#self._.Polygon do +self:T({i,j,self._.Polygon[i],self._.Polygon[j]}) +local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x +local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y +for Segment=0,Segments do +local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) +local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) +local Tire={ +["country"]="USA", +["category"]="Fortifications", +["canCargo"]=false, +["shape_name"]="H-tyre_B_WF", +["type"]="Black_Tyre_WF", +["y"]=PointY, +["x"]=PointX, +["name"]=string.format("%s-Tire #%0d",self:GetName(),((i-1)*Segments)+Segment), +["heading"]=0, +} +local Group=coalition.addStaticObject(country.id.USA,Tire) +if UnBound and UnBound==true then +Group:destroy() +end +end +j=i +i=i+1 +end +return self +end +function ZONE_POLYGON_BASE:SmokeZone(SmokeColor,Segments) +self:F2(SmokeColor) +Segments=Segments or 10 +local i=1 +local j=#self._.Polygon +while i<=#self._.Polygon do +self:T({i,j,self._.Polygon[i],self._.Polygon[j]}) +local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x +local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y +for Segment=0,Segments do +local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) +local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) +POINT_VEC2:New(PointX,PointY):Smoke(SmokeColor) +end +j=i +i=i+1 +end +return self +end +function ZONE_POLYGON_BASE:FlareZone(FlareColor,Segments,Azimuth,AddHeight) +self:F2(FlareColor) +Segments=Segments or 10 +AddHeight=AddHeight or 0 +local i=1 +local j=#self._.Polygon +while i<=#self._.Polygon do +self:T({i,j,self._.Polygon[i],self._.Polygon[j]}) +local DeltaX=self._.Polygon[j].x-self._.Polygon[i].x +local DeltaY=self._.Polygon[j].y-self._.Polygon[i].y +for Segment=0,Segments do +local PointX=self._.Polygon[i].x+(Segment*DeltaX/Segments) +local PointY=self._.Polygon[i].y+(Segment*DeltaY/Segments) +POINT_VEC2:New(PointX,PointY,AddHeight):Flare(FlareColor,Azimuth) +end +j=i +i=i+1 +end +return self +end +function ZONE_POLYGON_BASE:IsVec2InZone(Vec2) +self:F2(Vec2) +local Next +local Prev +local InPolygon=false +Next=1 +Prev=#self._.Polygon +while Next<=#self._.Polygon do +self:T({Next,Prev,self._.Polygon[Next],self._.Polygon[Prev]}) +if(((self._.Polygon[Next].y>Vec2.y)~=(self._.Polygon[Prev].y>Vec2.y))and +(Vec2.x<(self._.Polygon[Prev].x-self._.Polygon[Next].x)*(Vec2.y-self._.Polygon[Next].y)/(self._.Polygon[Prev].y-self._.Polygon[Next].y)+self._.Polygon[Next].x) +)then +InPolygon=not InPolygon +end +self:T2({InPolygon=InPolygon}) +Prev=Next +Next=Next+1 +end +self:T({InPolygon=InPolygon}) +return InPolygon +end +function ZONE_POLYGON_BASE:GetRandomVec2() +self:F2() +local Vec2Found=false +local Vec2 +local BS=self:GetBoundingSquare() +self:T2(BS) +while Vec2Found==false do +Vec2={x=math.random(BS.x1,BS.x2),y=math.random(BS.y1,BS.y2)} +self:T2(Vec2) +if self:IsVec2InZone(Vec2)then +Vec2Found=true +end +end +self:T2(Vec2) +return Vec2 +end +function ZONE_POLYGON_BASE:GetRandomPointVec2() +self:F2() +local PointVec2=POINT_VEC2:NewFromVec2(self:GetRandomVec2()) +self:T2(PointVec2) +return PointVec2 +end +function ZONE_POLYGON_BASE:GetRandomPointVec3() +self:F2() +local PointVec3=POINT_VEC3:NewFromVec2(self:GetRandomVec2()) +self:T2(PointVec3) +return PointVec3 +end +function ZONE_POLYGON_BASE:GetRandomCoordinate() +self:F2() +local Coordinate=COORDINATE:NewFromVec2(self:GetRandomVec2()) +self:T2(Coordinate) +return Coordinate +end +function ZONE_POLYGON_BASE:GetBoundingSquare() +local x1=self._.Polygon[1].x +local y1=self._.Polygon[1].y +local x2=self._.Polygon[1].x +local y2=self._.Polygon[1].y +for i=2,#self._.Polygon do +self:T2({self._.Polygon[i],x1,y1,x2,y2}) +x1=(x1>self._.Polygon[i].x)and self._.Polygon[i].x or x1 +x2=(x2self._.Polygon[i].y)and self._.Polygon[i].y or y1 +y2=(y20))then +for group_num,Template in pairs(obj_type_data.group)do +if obj_type_name~="static"and Template and Template.units and type(Template.units)=='table'then +self:_RegisterGroupTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID) +else +self:_RegisterStaticTemplate(Template,CoalitionSide,_DATABASECategory[string.lower(CategoryName)],CountryID) +end +end +end +end +end +end +end +end +end +end +return self +end +function DATABASE:AccountHits(Event) +self:F({Event}) +if Event.IniPlayerName~=nil then +self:T("Hitting Something") +if Event.TgtCategory then +self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{} +local Hit=self.HITS[Event.TgtUnitName] +Hit.Players=Hit.Players or{} +Hit.Players[Event.IniPlayerName]=true +end +end +if Event.WeaponPlayerName~=nil then +self:T("Hitting Scenery") +if Event.TgtCategory then +if Event.WeaponCoalition then +self.HITS[Event.TgtUnitName]=self.HITS[Event.TgtUnitName]or{} +local Hit=self.HITS[Event.TgtUnitName] +Hit.Players=Hit.Players or{} +Hit.Players[Event.WeaponPlayerName]=true +else +end +end +end +end +function DATABASE:AccountDestroys(Event) +self:F({Event}) +local TargetUnit=nil +local TargetGroup=nil +local TargetUnitName="" +local TargetGroupName="" +local TargetPlayerName="" +local TargetCoalition=nil +local TargetCategory=nil +local TargetType=nil +local TargetUnitCoalition=nil +local TargetUnitCategory=nil +local TargetUnitType=nil +if Event.IniDCSUnit then +TargetUnit=Event.IniUnit +TargetUnitName=Event.IniDCSUnitName +TargetGroup=Event.IniDCSGroup +TargetGroupName=Event.IniDCSGroupName +TargetPlayerName=Event.IniPlayerName +TargetCoalition=Event.IniCoalition +TargetCategory=Event.IniCategory +TargetType=Event.IniTypeName +TargetUnitType=TargetType +self:T({TargetUnitName,TargetGroupName,TargetPlayerName,TargetCoalition,TargetCategory,TargetType}) +end +local Destroyed=false +if self.HITS[Event.IniUnitName]then +self.DESTROYS[Event.IniUnitName]=self.DESTROYS[Event.IniUnitName]or{} +self.DESTROYS[Event.IniUnitName]=true +end +end +do +SET_BASE={ +ClassName="SET_BASE", +Filter={}, +Set={}, +List={}, +Index={}, +Database=nil, +CallScheduler=nil, +TimeInterval=nil, +YieldInterval=nil, +} +function SET_BASE:New(Database) +local self=BASE:Inherit(self,FSM:New()) +self.Database=Database +self:SetStartState("Started") +self:AddTransition("*","Added","*") +self:AddTransition("*","Removed","*") +self.YieldInterval=10 +self.TimeInterval=0.001 +self.Set={} +self.Index={} +self.CallScheduler=SCHEDULER:New(self) +self:SetEventPriority(2) +return self +end +function SET_BASE:Clear() +for Name,Object in pairs(self.Set)do +self:Remove(Name) +end +return self +end +function SET_BASE:_Find(ObjectName) +local ObjectFound=self.Set[ObjectName] +return ObjectFound +end +function SET_BASE:GetSet() +self:F2() +return self.Set or{} +end +function SET_BASE:GetSetNames() +self:F2() +local Names={} +for Name,Object in pairs(self.Set)do +table.insert(Names,Name) +end +return Names +end +function SET_BASE:GetSetObjects() +self:F2() +local Objects={} +for Name,Object in pairs(self.Set)do +table.insert(Objects,Object) +end +return Objects +end +function SET_BASE:Remove(ObjectName,NoTriggerEvent) +self:F2({ObjectName=ObjectName}) +local 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 not NoTriggerEvent then +self:Removed(ObjectName,Object) +end +end +end +function SET_BASE:Add(ObjectName,Object) +self:F2({ObjectName=ObjectName,Object=Object}) +if self.Set[ObjectName]then +self:Remove(ObjectName,true) +end +self.Set[ObjectName]=Object +table.insert(self.Index,ObjectName) +self:Added(ObjectName,Object) +end +function SET_BASE:AddObject(Object) +self:F2(Object.ObjectName) +self:T(Object.UnitName) +self:T(Object.ObjectName) +self:Add(Object.ObjectName,Object) +end +function SET_BASE: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(intersection) +end +end +return intersection +end +function SET_BASE:GetSetComplement(SetB) +local complement=SET_BASE:New() +local union=self:GetSetUnion(SetA,SetB) +for _,Object in pairs(union.Set)do +if SetA:IsIncludeObject(Object)and SetB:IsIncludeObject(Object)then +intersection:Add(intersection) +end +end +return intersection +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) +self:F(ObjectName) +local Object=self.Set[ObjectName] +self:T3({ObjectName,Object}) +return Object +end +function SET_BASE:GetFirst() +local ObjectName=self.Index[1] +local FirstObject=self.Set[ObjectName] +self:T3({FirstObject}) +return FirstObject +end +function SET_BASE:GetLast() +local ObjectName=self.Index[#self.Index] +local LastObject=self.Set[ObjectName] +self:T3({LastObject}) +return LastObject +end +function SET_BASE:GetRandom() +local RandomItem=self.Set[self.Index[math.random(#self.Index)]] +self:T3({RandomItem}) +return RandomItem +end +function SET_BASE:Count() +return self.Index and#self.Index or 0 +end +function SET_BASE:SetDatabase(BaseSet) +local OtherFilter=routines.utils.deepCopy(BaseSet.Filter) +self.Filter=OtherFilter +self.Database=BaseSet:GetSet() +return self +end +function SET_BASE:SetIteratorIntervals(YieldInterval,TimeInterval) +self.YieldInterval=YieldInterval +self.TimeInterval=TimeInterval +return self +end +function SET_BASE:SetSomeIteratorLimit(Limit) +self.SomeIteratorLimit=Limit or 1 +return self +end +function SET_BASE:GetSomeIteratorLimit() +return self.SomeIteratorLimit or self:Count() +end +function SET_BASE:FilterOnce() +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:_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(PointVec2) +self:F2(PointVec2) +local NearestObject=nil +local ClosestDistance=nil +for ObjectID,ObjectData in pairs(self.Set)do +if NearestObject==nil then +NearestObject=ObjectData +ClosestDistance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) +else +local Distance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) +if Distance=Limit then +break +end +end +return true +end +local co=CoRoutine +local function Schedule() +local status,res=co() +self:T3({status,res}) +if status==false then +error(res) +end +if res==false then +return true +end +return false +end +Schedule() +return self +end +function SET_BASE:IsIncludeObject(Object) +self:F3(Object) +return true +end +function SET_BASE:IsInSet(ObjectName) +self:F3(Object) +return true +end +function SET_BASE:GetObjectNames() +self:F3() +local ObjectNames="" +for ObjectName,Object in pairs(self.Set)do +ObjectNames=ObjectNames..ObjectName..", " +end +return ObjectNames +end +function SET_BASE:Flush(MasterObject) +self:F3() +local ObjectNames="" +for ObjectName,Object in pairs(self.Set)do +ObjectNames=ObjectNames..ObjectName..", " +end +self:F({MasterObject=MasterObject and MasterObject:GetClassNameAndID(),"Objects in Set:",ObjectNames}) +return ObjectNames +end +end +do +SET_GROUP={ +ClassName="SET_GROUP", +Filter={ +Coalitions=nil, +Categories=nil, +Countries=nil, +GroupPrefixes=nil, +}, +FilterMeta={ +Coalitions={ +red=coalition.side.RED, +blue=coalition.side.BLUE, +neutral=coalition.side.NEUTRAL, +}, +Categories={ +plane=Group.Category.AIRPLANE, +helicopter=Group.Category.HELICOPTER, +ground=Group.Category.GROUND, +ship=Group.Category.SHIP, +structure=Group.Category.STRUCTURE, +}, +}, +} +function SET_GROUP:New() +local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.GROUPS)) +self:FilterActive(false) +return self +end +function SET_GROUP:GetAliveSet() +self:F2() +local AliveSet=SET_GROUP:New() +for GroupName,GroupObject in pairs(self.Set)do +local GroupObject=GroupObject +if GroupObject then +if GroupObject:IsAlive()then +AliveSet:Add(GroupName,GroupObject) +end +end +end +return AliveSet.Set or{} +end +function SET_GROUP:GetUnitTypeNames() +self:F2() +local MT={} +local UnitTypes={} +local ReportUnitTypes=REPORT:New() +for GroupID,GroupData in pairs(self:GetSet())do +local Units=GroupData:GetUnits() +for UnitID,UnitData in pairs(Units)do +if UnitData:IsAlive()then +local UnitType=UnitData:GetTypeName() +if not UnitTypes[UnitType]then +UnitTypes[UnitType]=1 +else +UnitTypes[UnitType]=UnitTypes[UnitType]+1 +end +end +end +end +for UnitTypeID,UnitType in pairs(UnitTypes)do +ReportUnitTypes:Add(UnitType.." of "..UnitTypeID) +end +return ReportUnitTypes +end +function SET_GROUP:AddGroup(group) +self:Add(group:GetName(),group) +for UnitID,UnitData in pairs(group:GetUnits())do +UnitData:SetCargoBayWeightLimit() +end +return self +end +function SET_GROUP:AddGroupsByName(AddGroupNames) +local AddGroupNamesArray=(type(AddGroupNames)=="table")and AddGroupNames or{AddGroupNames} +for AddGroupID,AddGroupName in pairs(AddGroupNamesArray)do +self:Add(AddGroupName,GROUP:FindByName(AddGroupName)) +end +return self +end +function SET_GROUP:RemoveGroupsByName(RemoveGroupNames) +local RemoveGroupNamesArray=(type(RemoveGroupNames)=="table")and RemoveGroupNames or{RemoveGroupNames} +for RemoveGroupID,RemoveGroupName in pairs(RemoveGroupNamesArray)do +self:Remove(RemoveGroupName) +end +return self +end +function SET_GROUP:FindGroup(GroupName) +local GroupFound=self.Set[GroupName] +return GroupFound +end +function SET_GROUP:FindNearestGroupFromPointVec2(PointVec2) +self:F2(PointVec2) +local NearestGroup=nil +local ClosestDistance=nil +for ObjectID,ObjectData in pairs(self.Set)do +if NearestGroup==nil then +NearestGroup=ObjectData +ClosestDistance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) +else +local Distance=PointVec2:DistanceFromPointVec2(ObjectData:GetCoordinate()) +if DistanceMaxThreatLevelA2G then +MaxThreatLevelA2G=ThreatLevelA2G +MaxThreatText=ThreatText +end +end +self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText}) +return MaxThreatLevelA2G,MaxThreatText +end +function SET_UNIT:GetCoordinate() +local 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 UnitName,UnitData in pairs(self:GetSet())do +local Unit=UnitData +local Coordinate=Unit:GetCoordinate() +x1=(Coordinate.xx2)and Coordinate.x or x2 +y1=(Coordinate.yy2)and Coordinate.y or y2 +z1=(Coordinate.yz2)and Coordinate.z or z2 +local Velocity=Coordinate:GetVelocity() +if Velocity~=0 then +MaxVelocity=(MaxVelocity5 then +HeadingSet=nil +break +end +end +end +end +return HeadingSet +end +function SET_UNIT:HasRadar(RadarType) +self:F2(RadarType) +local RadarCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitSensorTest=UnitData +local HasSensors +if RadarType then +HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR,RadarType) +else +HasSensors=UnitSensorTest:HasSensors(Unit.SensorType.RADAR) +end +self:T3(HasSensors) +if HasSensors then +RadarCount=RadarCount+1 +end +end +return RadarCount +end +function SET_UNIT:HasSEAD() +self:F2() +local SEADCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitSEAD=UnitData +if UnitSEAD:IsAlive()then +local UnitSEADAttributes=UnitSEAD:GetDesc().attributes +local HasSEAD=UnitSEAD:HasSEAD() +self:T3(HasSEAD) +if HasSEAD then +SEADCount=SEADCount+1 +end +end +end +return SEADCount +end +function SET_UNIT:HasGroundUnits() +self:F2() +local GroundUnitCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitTest=UnitData +if UnitTest:IsGround()then +GroundUnitCount=GroundUnitCount+1 +end +end +return GroundUnitCount +end +function SET_UNIT:HasAirUnits() +self:F2() +local AirUnitCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitTest=UnitData +if UnitTest:IsAir()then +AirUnitCount=AirUnitCount+1 +end +end +return AirUnitCount +end +function SET_UNIT:HasFriendlyUnits(FriendlyCoalition) +self:F2() +local FriendlyUnitCount=0 +for UnitID,UnitData in pairs(self:GetSet())do +local UnitTest=UnitData +if UnitTest:IsFriendly(FriendlyCoalition)then +FriendlyUnitCount=FriendlyUnitCount+1 +end +end +return FriendlyUnitCount +end +function SET_UNIT:IsIncludeObject(MUnit) +self:F2(MUnit) +local MUnitInclude=false +if MUnit:IsAlive()~=nil then +MUnitInclude=true +if self.Filter.Active~=nil then +local MUnitActive=false +if self.Filter.Active==false or(self.Filter.Active==true and MUnit:IsActive()==true)then +MUnitActive=true +end +MUnitInclude=MUnitInclude and MUnitActive +end +if self.Filter.Coalitions then +local MUnitCoalition=false +for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do +self:F({"Coalition:",MUnit:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MUnit:GetCoalition()then +MUnitCoalition=true +end +end +MUnitInclude=MUnitInclude and MUnitCoalition +end +if self.Filter.Categories then +local MUnitCategory=false +for CategoryID,CategoryName in pairs(self.Filter.Categories)do +self:T3({"Category:",MUnit:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName}) +if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MUnit:GetDesc().category then +MUnitCategory=true +end +end +MUnitInclude=MUnitInclude and MUnitCategory +end +if self.Filter.Types then +local MUnitType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +self:T3({"Type:",MUnit:GetTypeName(),TypeName}) +if TypeName==MUnit:GetTypeName()then +MUnitType=true +end +end +MUnitInclude=MUnitInclude and MUnitType +end +if self.Filter.Countries then +local MUnitCountry=false +for CountryID,CountryName in pairs(self.Filter.Countries)do +self:T3({"Country:",MUnit:GetCountry(),CountryName}) +if country.id[CountryName]==MUnit:GetCountry()then +MUnitCountry=true +end +end +MUnitInclude=MUnitInclude and MUnitCountry +end +if self.Filter.UnitPrefixes then +local MUnitPrefix=false +for UnitPrefixId,UnitPrefix in pairs(self.Filter.UnitPrefixes)do +self:T3({"Prefix:",string.find(MUnit:GetName(),UnitPrefix,1),UnitPrefix}) +if string.find(MUnit:GetName(),UnitPrefix,1)then +MUnitPrefix=true +end +end +MUnitInclude=MUnitInclude and MUnitPrefix +end +if self.Filter.RadarTypes then +local MUnitRadar=false +for RadarTypeID,RadarType in pairs(self.Filter.RadarTypes)do +self:T3({"Radar:",RadarType}) +if MUnit:HasSensors(Unit.SensorType.RADAR,RadarType)==true then +if MUnit:GetRadar()==true then +self:T3("RADAR Found") +end +MUnitRadar=true +end +end +MUnitInclude=MUnitInclude and MUnitRadar +end +if self.Filter.SEAD then +local MUnitSEAD=false +if MUnit:HasSEAD()==true then +self:T3("SEAD Found") +MUnitSEAD=true +end +MUnitInclude=MUnitInclude and MUnitSEAD +end +end +self:T2(MUnitInclude) +return MUnitInclude +end +function SET_UNIT:GetTypeNames(Delimiter) +Delimiter=Delimiter or", " +local TypeReport=REPORT:New() +local Types={} +for UnitName,UnitData in pairs(self:GetSet())do +local Unit=UnitData +local UnitTypeName=Unit:GetTypeName() +if not Types[UnitTypeName]then +Types[UnitTypeName]=UnitTypeName +TypeReport:Add(UnitTypeName) +end +end +return TypeReport:Text(Delimiter) +end +function SET_UNIT:SetCargoBayWeightLimit() +local Set=self:GetSet() +for UnitID,UnitData in pairs(Set)do +UnitData:SetCargoBayWeightLimit() +end +end +end +do +SET_STATIC={ +ClassName="SET_STATIC", +Statics={}, +Filter={ +Coalitions=nil, +Categories=nil, +Types=nil, +Countries=nil, +StaticPrefixes=nil, +}, +FilterMeta={ +Coalitions={ +red=coalition.side.RED, +blue=coalition.side.BLUE, +neutral=coalition.side.NEUTRAL, +}, +Categories={ +plane=Unit.Category.AIRPLANE, +helicopter=Unit.Category.HELICOPTER, +ground=Unit.Category.GROUND_STATIC, +ship=Unit.Category.SHIP, +structure=Unit.Category.STRUCTURE, +}, +}, +} +function SET_STATIC:New() +local self=BASE:Inherit(self,SET_BASE:New(_DATABASE.STATICS)) +return self +end +function SET_STATIC:AddStatic(AddStatic) +self:F2(AddStatic:GetName()) +self:Add(AddStatic:GetName(),AddStatic) +return self +end +function SET_STATIC:AddStaticsByName(AddStaticNames) +local AddStaticNamesArray=(type(AddStaticNames)=="table")and AddStaticNames or{AddStaticNames} +self:T(AddStaticNamesArray) +for AddStaticID,AddStaticName in pairs(AddStaticNamesArray)do +self:Add(AddStaticName,STATIC:FindByName(AddStaticName)) +end +return self +end +function SET_STATIC:RemoveStaticsByName(RemoveStaticNames) +local RemoveStaticNamesArray=(type(RemoveStaticNames)=="table")and RemoveStaticNames or{RemoveStaticNames} +for RemoveStaticID,RemoveStaticName in pairs(RemoveStaticNamesArray)do +self:Remove(RemoveStaticName) +end +return self +end +function SET_STATIC:FindStatic(StaticName) +local StaticFound=self.Set[StaticName] +return StaticFound +end +function SET_STATIC:FilterCoalitions(Coalitions) +if not self.Filter.Coalitions then +self.Filter.Coalitions={} +end +if type(Coalitions)~="table"then +Coalitions={Coalitions} +end +for CoalitionID,Coalition in pairs(Coalitions)do +self.Filter.Coalitions[Coalition]=Coalition +end +return self +end +function SET_STATIC:FilterCategories(Categories) +if not self.Filter.Categories then +self.Filter.Categories={} +end +if type(Categories)~="table"then +Categories={Categories} +end +for CategoryID,Category in pairs(Categories)do +self.Filter.Categories[Category]=Category +end +return self +end +function SET_STATIC:FilterTypes(Types) +if not self.Filter.Types then +self.Filter.Types={} +end +if type(Types)~="table"then +Types={Types} +end +for TypeID,Type in pairs(Types)do +self.Filter.Types[Type]=Type +end +return self +end +function SET_STATIC:FilterCountries(Countries) +if not self.Filter.Countries then +self.Filter.Countries={} +end +if type(Countries)~="table"then +Countries={Countries} +end +for CountryID,Country in pairs(Countries)do +self.Filter.Countries[Country]=Country +end +return self +end +function SET_STATIC:FilterPrefixes(Prefixes) +if not self.Filter.StaticPrefixes then +self.Filter.StaticPrefixes={} +end +if type(Prefixes)~="table"then +Prefixes={Prefixes} +end +for PrefixID,Prefix in pairs(Prefixes)do +self.Filter.StaticPrefixes[Prefix]=Prefix +end +return self +end +function SET_STATIC:FilterStart() +if _DATABASE then +self:_FilterStart() +self:HandleEvent(EVENTS.Birth,self._EventOnBirth) +self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) +end +return self +end +function SET_STATIC:CountAlive() +local Set=self:GetSet() +local CountU=0 +for UnitID,UnitData in pairs(Set)do +if UnitData and UnitData:IsAlive()then +CountU=CountU+1 +end +end +return CountU +end +function SET_STATIC:AddInDatabase(Event) +self:F3({Event}) +if Event.IniObjectCategory==Object.Category.STATIC then +if not self.Database[Event.IniDCSUnitName]then +self.Database[Event.IniDCSUnitName]=STATIC:Register(Event.IniDCSUnitName) +self:T3(self.Database[Event.IniDCSUnitName]) +end +end +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_STATIC:FindInDatabase(Event) +self:F2({Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName],Event}) +return Event.IniDCSUnitName,self.Set[Event.IniDCSUnitName] +end +do +function SET_STATIC:IsPartiallyInZone(Zone) +local IsPartiallyInZone=false +local function EvaluateZone(ZoneStatic) +local ZoneStaticName=ZoneStatic:GetName() +if self:FindStatic(ZoneStaticName)then +IsPartiallyInZone=true +return false +end +return true +end +return IsPartiallyInZone +end +function SET_STATIC:IsNotInZone(Zone) +local IsNotInZone=true +local function EvaluateZone(ZoneStatic) +local ZoneStaticName=ZoneStatic:GetName() +if self:FindStatic(ZoneStaticName)then +IsNotInZone=false +return false +end +return true +end +Zone:Search(EvaluateZone) +return IsNotInZone +end +function SET_STATIC:ForEachStaticInZone(IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +end +function SET_STATIC:ForEachStatic(IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +function SET_STATIC:ForEachStaticCompletelyInZone(ZoneObject,IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet(), +function(ZoneObject,StaticObject) +if StaticObject:IsInZone(ZoneObject)then +return true +else +return false +end +end,{ZoneObject}) +return self +end +function SET_STATIC:ForEachStaticNotInZone(ZoneObject,IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet(), +function(ZoneObject,StaticObject) +if StaticObject:IsNotInZone(ZoneObject)then +return true +else +return false +end +end,{ZoneObject}) +return self +end +function SET_STATIC:GetStaticTypes() +self:F2() +local MT={} +local StaticTypes={} +for StaticID,StaticData in pairs(self:GetSet())do +local TextStatic=StaticData +if TextStatic:IsAlive()then +local StaticType=TextStatic:GetTypeName() +if not StaticTypes[StaticType]then +StaticTypes[StaticType]=1 +else +StaticTypes[StaticType]=StaticTypes[StaticType]+1 +end +end +end +for StaticTypeID,StaticType in pairs(StaticTypes)do +MT[#MT+1]=StaticType.." of "..StaticTypeID +end +return StaticTypes +end +function SET_STATIC:GetStaticTypesText() +self:F2() +local MT={} +local StaticTypes=self:GetStaticTypes() +for StaticTypeID,StaticType in pairs(StaticTypes)do +MT[#MT+1]=StaticType.." of "..StaticTypeID +end +return table.concat(MT,", ") +end +function SET_STATIC:GetCoordinate() +local Coordinate=self:GetFirst():GetCoordinate() +local x1=Coordinate.x +local x2=Coordinate.x +local y1=Coordinate.y +local y2=Coordinate.y +local z1=Coordinate.z +local z2=Coordinate.z +local MaxVelocity=0 +local AvgHeading=nil +local MovingCount=0 +for StaticName,StaticData in pairs(self:GetSet())do +local Static=StaticData +local Coordinate=Static:GetCoordinate() +x1=(Coordinate.xx2)and Coordinate.x or x2 +y1=(Coordinate.yy2)and Coordinate.y or y2 +z1=(Coordinate.yz2)and Coordinate.z or z2 +local Velocity=Coordinate:GetVelocity() +if Velocity~=0 then +MaxVelocity=(MaxVelocity5 then +HeadingSet=nil +break +end +end +end +end +return HeadingSet +end +function SET_STATIC:CalculateThreatLevelA2G() +local MaxThreatLevelA2G=0 +local MaxThreatText="" +for StaticName,StaticData in pairs(self:GetSet())do +local ThreatStatic=StaticData +local ThreatLevelA2G,ThreatText=ThreatStatic:GetThreatLevel() +if ThreatLevelA2G>MaxThreatLevelA2G then +MaxThreatLevelA2G=ThreatLevelA2G +MaxThreatText=ThreatText +end +end +self:F({MaxThreatLevelA2G=MaxThreatLevelA2G,MaxThreatText=MaxThreatText}) +return MaxThreatLevelA2G,MaxThreatText +end +function SET_STATIC:IsIncludeObject(MStatic) +self:F2(MStatic) +local MStaticInclude=true +if self.Filter.Coalitions then +local MStaticCoalition=false +for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do +self:T3({"Coalition:",MStatic:GetCoalition(),self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==MStatic:GetCoalition()then +MStaticCoalition=true +end +end +MStaticInclude=MStaticInclude and MStaticCoalition +end +if self.Filter.Categories then +local MStaticCategory=false +for CategoryID,CategoryName in pairs(self.Filter.Categories)do +self:T3({"Category:",MStatic:GetDesc().category,self.FilterMeta.Categories[CategoryName],CategoryName}) +if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==MStatic:GetDesc().category then +MStaticCategory=true +end +end +MStaticInclude=MStaticInclude and MStaticCategory +end +if self.Filter.Types then +local MStaticType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +self:T3({"Type:",MStatic:GetTypeName(),TypeName}) +if TypeName==MStatic:GetTypeName()then +MStaticType=true +end +end +MStaticInclude=MStaticInclude and MStaticType +end +if self.Filter.Countries then +local MStaticCountry=false +for CountryID,CountryName in pairs(self.Filter.Countries)do +self:T3({"Country:",MStatic:GetCountry(),CountryName}) +if country.id[CountryName]==MStatic:GetCountry()then +MStaticCountry=true +end +end +MStaticInclude=MStaticInclude and MStaticCountry +end +if self.Filter.StaticPrefixes then +local MStaticPrefix=false +for StaticPrefixId,StaticPrefix in pairs(self.Filter.StaticPrefixes)do +self:T3({"Prefix:",string.find(MStatic:GetName(),StaticPrefix,1),StaticPrefix}) +if string.find(MStatic:GetName(),StaticPrefix,1)then +MStaticPrefix=true +end +end +MStaticInclude=MStaticInclude and MStaticPrefix +end +self:T2(MStaticInclude) +return MStaticInclude +end +function SET_STATIC:GetTypeNames(Delimiter) +Delimiter=Delimiter or", " +local TypeReport=REPORT:New() +local Types={} +for StaticName,StaticData in pairs(self:GetSet())do +local Static=StaticData +local StaticTypeName=Static:GetTypeName() +if not Types[StaticTypeName]then +Types[StaticTypeName]=StaticTypeName +TypeReport:Add(StaticTypeName) +end +end +return TypeReport:Text(Delimiter) +end +end +do +SET_CLIENT={ +ClassName="SET_CLIENT", +Clients={}, +Filter={ +Coalitions=nil, +Categories=nil, +Types=nil, +Countries=nil, +ClientPrefixes=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: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:FilterActive(Active) +Active=Active or not(Active==false) +self.Filter.Active=Active +return self +end +function SET_CLIENT:FilterStart() +if _DATABASE then +self:_FilterStart() +self:HandleEvent(EVENTS.Birth,self._EventOnBirth) +self:HandleEvent(EVENTS.Dead,self._EventOnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._EventOnDeadOrCrash) +end +return self +end +function SET_CLIENT:AddInDatabase(Event) +self:F3({Event}) +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_CLIENT:FindInDatabase(Event) +self:F3({Event}) +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_CLIENT:ForEachClient(IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +function SET_CLIENT:ForEachClientInZone(ZoneObject,IteratorFunction,...) +self:F2(arg) +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:F2(arg) +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:IsIncludeObject(MClient) +self:F2(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)then +MClientActive=true +end +MClientInclude=MClientInclude and MClientActive +end +if self.Filter.Coalitions then +local MClientCoalition=false +for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do +local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName) +self:T3({"Coalition:",ClientCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==ClientCoalitionID then +MClientCoalition=true +end +end +self:T({"Evaluated Coalition",MClientCoalition}) +MClientInclude=MClientInclude and MClientCoalition +end +if self.Filter.Categories then +local MClientCategory=false +for CategoryID,CategoryName in pairs(self.Filter.Categories)do +local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName) +self:T3({"Category:",ClientCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName}) +if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==ClientCategoryID then +MClientCategory=true +end +end +self:T({"Evaluated Category",MClientCategory}) +MClientInclude=MClientInclude and MClientCategory +end +if self.Filter.Types then +local MClientType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +self:T3({"Type:",MClient:GetTypeName(),TypeName}) +if TypeName==MClient:GetTypeName()then +MClientType=true +end +end +self:T({"Evaluated Type",MClientType}) +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) +self:T3({"Country:",ClientCountryID,country.id[CountryName],CountryName}) +if country.id[CountryName]and country.id[CountryName]==ClientCountryID then +MClientCountry=true +end +end +self:T({"Evaluated Country",MClientCountry}) +MClientInclude=MClientInclude and MClientCountry +end +if self.Filter.ClientPrefixes then +local MClientPrefix=false +for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do +self:T3({"Prefix:",string.find(MClient.UnitName,ClientPrefix,1),ClientPrefix}) +if string.find(MClient.UnitName,ClientPrefix,1)then +MClientPrefix=true +end +end +self:T({"Evaluated Prefix",MClientPrefix}) +MClientInclude=MClientInclude and MClientPrefix +end +end +self:T2(MClientInclude) +return MClientInclude +end +end +do +SET_PLAYER={ +ClassName="SET_PLAYER", +Clients={}, +Filter={ +Coalitions=nil, +Categories=nil, +Types=nil, +Countries=nil, +ClientPrefixes=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: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) +end +return self +end +function SET_PLAYER:AddInDatabase(Event) +self:F3({Event}) +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_PLAYER:FindInDatabase(Event) +self:F3({Event}) +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_PLAYER:ForEachPlayer(IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +function SET_PLAYER:ForEachPlayerInZone(ZoneObject,IteratorFunction,...) +self:F2(arg) +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:F2(arg) +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) +self:F2(MClient) +local MClientInclude=true +if MClient then +local MClientName=MClient.UnitName +if self.Filter.Coalitions then +local MClientCoalition=false +for CoalitionID,CoalitionName in pairs(self.Filter.Coalitions)do +local ClientCoalitionID=_DATABASE:GetCoalitionFromClientTemplate(MClientName) +self:T3({"Coalition:",ClientCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==ClientCoalitionID then +MClientCoalition=true +end +end +self:T({"Evaluated Coalition",MClientCoalition}) +MClientInclude=MClientInclude and MClientCoalition +end +if self.Filter.Categories then +local MClientCategory=false +for CategoryID,CategoryName in pairs(self.Filter.Categories)do +local ClientCategoryID=_DATABASE:GetCategoryFromClientTemplate(MClientName) +self:T3({"Category:",ClientCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName}) +if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==ClientCategoryID then +MClientCategory=true +end +end +self:T({"Evaluated Category",MClientCategory}) +MClientInclude=MClientInclude and MClientCategory +end +if self.Filter.Types then +local MClientType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +self:T3({"Type:",MClient:GetTypeName(),TypeName}) +if TypeName==MClient:GetTypeName()then +MClientType=true +end +end +self:T({"Evaluated Type",MClientType}) +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) +self:T3({"Country:",ClientCountryID,country.id[CountryName],CountryName}) +if country.id[CountryName]and country.id[CountryName]==ClientCountryID then +MClientCountry=true +end +end +self:T({"Evaluated Country",MClientCountry}) +MClientInclude=MClientInclude and MClientCountry +end +if self.Filter.ClientPrefixes then +local MClientPrefix=false +for ClientPrefixId,ClientPrefix in pairs(self.Filter.ClientPrefixes)do +self:T3({"Prefix:",string.find(MClient.UnitName,ClientPrefix,1),ClientPrefix}) +if string.find(MClient.UnitName,ClientPrefix,1)then +MClientPrefix=true +end +end +self:T({"Evaluated Prefix",MClientPrefix}) +MClientInclude=MClientInclude and MClientPrefix +end +end +self:T2(MClientInclude) +return MClientInclude +end +end +do +SET_AIRBASE={ +ClassName="SET_AIRBASE", +Airbases={}, +Filter={ +Coalitions=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) +self:F({Distance=Distance}) +if Distance<=Range then +AirbaseFound=AirbaseObject +break +end +end +return AirbaseFound +end +function SET_AIRBASE:GetRandomAirbase() +local RandomAirbase=self:GetRandom() +self:F({RandomAirbase=RandomAirbase:GetName()}) +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: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) +self:F3({Event}) +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_AIRBASE:ForEachAirbase(IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +function SET_AIRBASE:FindNearestAirbaseFromPointVec2(PointVec2) +self:F2(PointVec2) +local NearestAirbase=self:FindNearestObjectFromPointVec2(PointVec2) +return NearestAirbase +end +function SET_AIRBASE:IsIncludeObject(MAirbase) +self:F2(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) +self:T3({"Coalition:",AirbaseCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==AirbaseCoalitionID then +MAirbaseCoalition=true +end +end +self:T({"Evaluated Coalition",MAirbaseCoalition}) +MAirbaseInclude=MAirbaseInclude and MAirbaseCoalition +end +if self.Filter.Categories then +local MAirbaseCategory=false +for CategoryID,CategoryName in pairs(self.Filter.Categories)do +local AirbaseCategoryID=_DATABASE:GetCategoryFromAirbase(MAirbaseName) +self:T3({"Category:",AirbaseCategoryID,self.FilterMeta.Categories[CategoryName],CategoryName}) +if self.FilterMeta.Categories[CategoryName]and self.FilterMeta.Categories[CategoryName]==AirbaseCategoryID then +MAirbaseCategory=true +end +end +self:T({"Evaluated Category",MAirbaseCategory}) +MAirbaseInclude=MAirbaseInclude and MAirbaseCategory +end +end +self:T2(MAirbaseInclude) +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) +self:F3({Event}) +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_CARGO:FindInDatabase(Event) +self:F3({Event}) +return Event.IniDCSUnitName,self.Database[Event.IniDCSUnitName] +end +function SET_CARGO:ForEachCargo(IteratorFunction,...) +self:F2(arg) +self:ForEach(IteratorFunction,arg,self:GetSet()) +return self +end +function SET_CARGO:FindNearestCargoFromPointVec2(PointVec2) +self:F2(PointVec2) +local NearestCargo=self:FindNearestObjectFromPointVec2(PointVec2) +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) +self:F2(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() +self:T3({"Coalition:",CargoCoalitionID,self.FilterMeta.Coalitions[CoalitionName],CoalitionName}) +if self.FilterMeta.Coalitions[CoalitionName]and self.FilterMeta.Coalitions[CoalitionName]==CargoCoalitionID then +MCargoCoalition=true +end +end +self:F({"Evaluated Coalition",MCargoCoalition}) +MCargoInclude=MCargoInclude and MCargoCoalition +end +if self.Filter.Types then +local MCargoType=false +for TypeID,TypeName in pairs(self.Filter.Types)do +self:T3({"Type:",MCargo:GetType(),TypeName}) +if TypeName==MCargo:GetType()then +MCargoType=true +end +end +self:F({"Evaluated Type",MCargoType}) +MCargoInclude=MCargoInclude and MCargoType +end +if self.Filter.CargoPrefixes then +local MCargoPrefix=false +for CargoPrefixId,CargoPrefix in pairs(self.Filter.CargoPrefixes)do +self:T3({"Prefix:",string.find(MCargo.Name,CargoPrefix,1),CargoPrefix}) +if string.find(MCargo.Name,CargoPrefix,1)then +MCargoPrefix=true +end +end +self:F({"Evaluated Prefix",MCargoPrefix}) +MCargoInclude=MCargoInclude and MCargoPrefix +end +end +self:T2(MCargoInclude) +return MCargoInclude +end +function SET_CARGO:OnEventNewCargo(EventData) +self:F({"New Cargo",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) +self:F3({EventData}) +if EventData.Cargo then +local Cargo=_DATABASE:FindCargo(EventData.Cargo.Name) +if Cargo and Cargo.Name then +self:F({CargoNoDestroy=Cargo.NoDestroy}) +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={ +}, +} +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=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=ZoneObject:getCategory() +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: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 d1 then +Radials=2-Radials +end +local RadialMultiplier +if InnerRadius and InnerRadius<=OuterRadius then +RadialMultiplier=(OuterRadius-InnerRadius)*Radials+InnerRadius +else +RadialMultiplier=OuterRadius*Radials +end +local RandomVec2 +if OuterRadius>0 then +RandomVec2={x=math.cos(Theta)*RadialMultiplier+self.x,y=math.sin(Theta)*RadialMultiplier+self.z} +else +RandomVec2={x=self.x,y=self.z} +end +return RandomVec2 +end +function COORDINATE:GetRandomCoordinateInRadius(OuterRadius,InnerRadius) +self:F2({OuterRadius,InnerRadius}) +return COORDINATE:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius)) +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) +return{x=TargetCoordinate.x-self.x,y=TargetCoordinate.y-self.y,z=TargetCoordinate.z-self.z} +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) +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) +local a={x=TargetCoordinate.x-self.x,y=0,z=TargetCoordinate.z-self.z} +local norm=UTILS.VecNorm(a) +return norm +end +function COORDINATE:GetTemperature(height) +self:F2(height) +local y=height or self.y +local point={x=self.x,y=height or self.y,z=self.z} +local T,P=atmosphere.getTemperatureAndPressure(point) +return T-273.15 +end +function COORDINATE:GetTemperatureText(height,Settings) +local DegreesCelcius=self:GetTemperature(height) +local Settings=Settings or _SETTINGS +if DegreesCelcius then +if Settings:IsMetric()then +return string.format(" %-2.2f °C",DegreesCelcius) +else +return string.format(" %-2.2f °F",UTILS.CelciusToFarenheit(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:GetWind(height) +local landheight=self:GetLandHeight()+0.1 +local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z} +local wind=atmosphere.getWind(point) +local direction=math.deg(math.atan2(wind.z,wind.x)) +if direction<0 then +direction=360+direction +end +if direction>180 then +direction=direction-180 +else +direction=direction+180 +end +local strength=math.sqrt((wind.x)^2+(wind.z)^2) +return direction,strength +end +function COORDINATE:GetWindWithTurbulenceVec3(height) +local landheight=self:GetLandHeight()+0.1 +local point={x=self.x,y=math.max(height or self.y,landheight),z=self.z} +local vec3=atmosphere.getWindWithTurbulence(point) +return vec3 +end +function COORDINATE:GetWindText(height,Settings) +local Direction,Strength=self:GetWind(height) +local Settings=Settings or _SETTINGS +if Direction and Strength then +if Settings:IsMetric()then +return string.format(" %d ° at %3.2f mps",Direction,UTILS.MpsToKmph(Strength)) +else +return string.format(" %d ° at %3.2f kps",Direction,UTILS.MpsToKnots(Strength)) +end +else +return" no wind" +end +return nil +end +function COORDINATE:Get3DDistance(TargetCoordinate) +local TargetVec3=TargetCoordinate:GetVec3() +local SourceVec3=self:GetVec3() +return((TargetVec3.x-SourceVec3.x)^2+(TargetVec3.y-SourceVec3.y)^2+(TargetVec3.z-SourceVec3.z)^2)^0.5 +end +function COORDINATE:GetBearingText(AngleRadians,Precision,Settings,Language) +local Settings=Settings or _SETTINGS +local AngleDegrees=UTILS.Round(UTILS.ToDegree(AngleRadians),Precision) +local s=string.format('%03d°',AngleDegrees) +return s +end +function COORDINATE:GetDistanceText(Distance,Settings,Language) +local Settings=Settings or _SETTINGS +local Language=Language or"EN" +local DistanceText +if Settings:IsMetric()then +if Language=="EN"then +DistanceText=" for "..UTILS.Round(Distance/1000,2).." km" +elseif Language=="RU"then +DistanceText=" за "..UTILS.Round(Distance/1000,2).." километров" +end +else +if Language=="EN"then +DistanceText=" for "..UTILS.Round(UTILS.MetersToNM(Distance),2).." miles" +elseif Language=="RU"then +DistanceText=" за "..UTILS.Round(UTILS.MetersToNM(Distance),2).." миль" +end +end +return DistanceText +end +function COORDINATE:GetAltitudeText(Settings,Language) +local Altitude=self.y +local Settings=Settings or _SETTINGS +local Language=Language or"EN" +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) +local Settings=Settings or _SETTINGS +local BearingText=self:GetBearingText(AngleRadians,0,Settings,Language) +local DistanceText=self:GetDistanceText(Distance,Settings,Language) +local BRText=BearingText..DistanceText +return BRText +end +function COORDINATE:GetBRAText(AngleRadians,Distance,Settings,Language) +local Settings=Settings or _SETTINGS +local BearingText=self:GetBearingText(AngleRadians,0,Settings,Language) +local DistanceText=self:GetDistanceText(Distance,Settings,Language) +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: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=true +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=true +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=true +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:GetClosestAirbase2(Category,Coalition) +local airbases=AIRBASE.GetAllAirbases(Coalition) +local closest=nil +local distmin=nil +for _,_airbase in pairs(airbases)do +local airbase=_airbase +if airbase then +local category=airbase:GetAirbaseCategory() +if Category and Category==category or Category==nil then +local dist=self:Get2DDistance(airbase:GetCoordinate()) +if closest==nil then +distmin=dist +closest=airbase +else +if dist=2 then +for i=1,#Path-1 do +Way=Way+Path[i+1]:Get2DDistance(Path[i]) +end +else +return nil,nil,false +end +return Path,Way,GotPath +end +function COORDINATE:GetSurfaceType() +local vec2=self:GetVec2() +local surface=land.getSurfaceType(vec2) +return surface +end +function COORDINATE:IsSurfaceTypeLand() +return self:GetSurfaceType()==land.SurfaceType.LAND +end +function COORDINATE:IsSurfaceTypeLand() +return self:GetSurfaceType()==land.SurfaceType.LAND +end +function COORDINATE:IsSurfaceTypeRoad() +return self:GetSurfaceType()==land.SurfaceType.ROAD +end +function COORDINATE:IsSurfaceTypeRunway() +return self:GetSurfaceType()==land.SurfaceType.RUNWAY +end +function COORDINATE:IsSurfaceTypeShallowWater() +return self:GetSurfaceType()==land.SurfaceType.SHALLOW_WATER +end +function COORDINATE:IsSurfaceTypeWater() +return self:GetSurfaceType()==land.SurfaceType.WATER +end +function COORDINATE:Explosion(ExplosionIntensity,Delay) +self:F2({ExplosionIntensity}) +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) +self:F2() +trigger.action.illuminationBomb(self:GetVec3(),power) +end +function COORDINATE:Smoke(SmokeColor) +self:F2({SmokeColor}) +trigger.action.smoke(self:GetVec3(),SmokeColor) +end +function COORDINATE:SmokeGreen() +self:F2() +self:Smoke(SMOKECOLOR.Green) +end +function COORDINATE:SmokeRed() +self:F2() +self:Smoke(SMOKECOLOR.Red) +end +function COORDINATE:SmokeWhite() +self:F2() +self:Smoke(SMOKECOLOR.White) +end +function COORDINATE:SmokeOrange() +self:F2() +self:Smoke(SMOKECOLOR.Orange) +end +function COORDINATE:SmokeBlue() +self:F2() +self:Smoke(SMOKECOLOR.Blue) +end +function COORDINATE:BigSmokeAndFire(preset,density) +self:F2({preset=preset,density=density}) +density=density or 0.5 +trigger.action.effectSmokeBig(self:GetVec3(),preset,density) +end +function COORDINATE:BigSmokeAndFireSmall(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire,density) +end +function COORDINATE:BigSmokeAndFireMedium(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire,density) +end +function COORDINATE:BigSmokeAndFireLarge(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire,density) +end +function COORDINATE:BigSmokeAndFireHuge(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire,density) +end +function COORDINATE:BigSmokeSmall(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke,density) +end +function COORDINATE:BigSmokeMedium(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke,density) +end +function COORDINATE:BigSmokeLarge(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke,density) +end +function COORDINATE:BigSmokeHuge(density) +self:F2({density=density}) +density=density or 0.5 +self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke,density) +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,LineType,Color,Alpha,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,LineType,Color,Alpha,FillColor,FillAlpha,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{1,0,0} +FillColor[4]=FillAlpha or 0.5 +trigger.action.circleToAll(Coalition,MarkID,vec3,Radius,Color,FillColor,LineType,ReadOnly,Text or"") +return MarkID +end +end +function COORDINATE:IsLOS(ToCoordinate,Offset) +Offset=Offset or 2 +local FromVec3=self:GetVec3() +FromVec3.y=FromVec3.y+Offset +local ToVec3=ToCoordinate:GetVec3() +ToVec3.y=ToVec3.y+Offset +local IsLOS=land.isVisible(FromVec3,ToVec3) +return IsLOS +end +function COORDINATE:IsInRadius(Coordinate,Radius) +local InVec2=self:GetVec2() +local Vec2=Coordinate:GetVec2() +local InRadius=UTILS.IsInRadius(InVec2,Vec2,Radius) +return InRadius +end +function COORDINATE:IsInSphere(Coordinate,Radius) +local InVec3=self:GetVec3() +local Vec3=Coordinate:GetVec3() +local InSphere=UTILS.IsInSphere(InVec3,Vec3,Radius) +return InSphere +end +function COORDINATE:GetSunriseAtDate(Day,Month,Year,InSeconds) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +if InSeconds then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetSunriseAtDayOfYear(DayOfYear,InSeconds) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +if InSeconds then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetSunrise(InSeconds) +local DayOfYear=UTILS.GetMissionDayOfYear() +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +local date=UTILS.GetDCSMissionDate() +if InSeconds then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetMinutesToSunrise(OnlyToday) +local time=UTILS.SecondsOfToday() +local sunrise=nil +local delta=nil +if OnlyToday then +sunrise=self:GetSunrise(true) +delta=sunrise-time +else +local DayOfYear=UTILS.GetMissionDayOfYear()+1 +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +delta=sunrise+UTILS.SecondsToMidnight() +end +return delta/60 +end +function COORDINATE:IsDay(Clock) +if Clock then +local Time=UTILS.ClockToSeconds(Clock) +local clock=UTILS.Split(Clock,"+")[1] +local DayOfYear=UTILS.GetMissionDayOfYear(Time) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,true,Tdiff) +local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +local time=UTILS.ClockToSeconds(clock) +if time>sunrise and time<=sunset then +return true +else +return false +end +else +local sunrise=self:GetSunrise(true) +local sunset=self:GetSunset(true) +local time=UTILS.SecondsOfToday() +if time>sunrise and time<=sunset then +return true +else +return false +end +end +end +function COORDINATE:IsNight(Clock) +return not self:IsDay(Clock) +end +function COORDINATE:GetSunsetAtDate(Day,Month,Year,InSeconds) +local DayOfYear=UTILS.GetDayOfYear(Year,Month,Day) +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +if InSeconds then +return sunset +else +return UTILS.SecondsToClock(sunset,true) +end +end +function COORDINATE:GetSunset(InSeconds) +local DayOfYear=UTILS.GetMissionDayOfYear() +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +local sunrise=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +local date=UTILS.GetDCSMissionDate() +if InSeconds then +return sunrise +else +return UTILS.SecondsToClock(sunrise,true) +end +end +function COORDINATE:GetMinutesToSunset(OnlyToday) +local time=UTILS.SecondsOfToday() +local sunset=nil +local delta=nil +if OnlyToday then +sunset=self:GetSunset(true) +delta=sunset-time +else +local DayOfYear=UTILS.GetMissionDayOfYear()+1 +local Latitude,Longitude=self:GetLLDDM() +local Tdiff=UTILS.GMTToLocalTimeDifference() +sunset=UTILS.GetSunRiseAndSet(DayOfYear,Latitude,Longitude,false,Tdiff) +delta=sunset+UTILS.SecondsToMidnight() +end +return delta/60 +end +function COORDINATE:ToStringBR(FromCoordinate,Settings) +local DirectionVec3=FromCoordinate:GetDirectionVec3(self) +local AngleRadians=self:GetAngleRadians(DirectionVec3) +local Distance=self:Get2DDistance(FromCoordinate) +return"BR, "..self:GetBRText(AngleRadians,Distance,Settings) +end +function COORDINATE:ToStringBRA(FromCoordinate,Settings,Language) +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,Language) +end +function COORDINATE:ToStringBULLS(Coalition,Settings) +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) +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: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:ToStringFromRP(ReferenceCoord,ReferenceName,Controllable,Settings) +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).." 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).." from "..ReferenceName +end +return nil +end +function COORDINATE:ToStringA2G(Controllable,Settings) +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)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,Language) +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,Language) +else +return self:ToStringMGRS(Settings,Language) +end +end +if Settings:IsA2A_BULLS()then +local Coalition=Controllable:GetCoalition() +return self:ToStringBULLS(Coalition,Settings,Language) +end +if Settings:IsA2A_LL_DMS()then +return self:ToStringLLDMS(Settings,Language) +end +if Settings:IsA2A_LL_DDM()then +return self:ToStringLLDDM(Settings,Language) +end +if Settings:IsA2A_MGRS()then +return self:ToStringMGRS(Settings,Language) +end +return nil +end +function COORDINATE:ToString(Controllable,Settings,Task) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +local ModeA2A=nil +if Task then +if Task:IsInstanceOf(TASK_A2A)then +ModeA2A=true +else +if Task:IsInstanceOf(TASK_A2G)then +ModeA2A=false +else +if Task:IsInstanceOf(TASK_CARGO)then +ModeA2A=false +end +if Task:IsInstanceOf(TASK_CAPTURE_ZONE)then +ModeA2A=false +end +end +end +end +if ModeA2A==nil then +local IsAir=Controllable and(Controllable:IsAirPlane()or Controllable:IsHelicopter())or false +if IsAir then +ModeA2A=true +else +ModeA2A=false +end +end +if ModeA2A==true then +return self:ToStringA2A(Controllable,Settings) +else +return self:ToStringA2G(Controllable,Settings) +end +return nil +end +function COORDINATE:ToStringPressure(Controllable,Settings) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +return self:GetPressureText(nil,Settings) +end +function COORDINATE:ToStringWind(Controllable,Settings) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +return self:GetWindText(nil,Settings) +end +function COORDINATE:ToStringTemperature(Controllable,Settings) +self:F2({Controllable=Controllable and Controllable:GetName()}) +local Settings=Settings or(Controllable and _DATABASE:GetPlayerSettings(Controllable:GetPlayerName()))or _SETTINGS +return self:GetTemperatureText(nil,Settings) +end +end +do +POINT_VEC3={ +ClassName="POINT_VEC3", +Metric=true, +RoutePointAltType={ +BARO="BARO", +}, +RoutePointType={ +TakeOffParking="TakeOffParking", +TurningPoint="Turning Point", +}, +RoutePointAction={ +FromParkingArea="From Parking Area", +TurningPoint="Turning Point", +}, +} +function POINT_VEC3:New(x,y,z) +local self=BASE:Inherit(self,COORDINATE:New(x,y,z)) +self:F2(self) +return self +end +function POINT_VEC3:NewFromVec2(Vec2,LandHeightAdd) +local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd)) +self:F2(self) +return self +end +function POINT_VEC3:NewFromVec3(Vec3) +local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3)) +self:F2(self) +return self +end +function POINT_VEC3:GetX() +return self.x +end +function POINT_VEC3:GetY() +return self.y +end +function POINT_VEC3:GetZ() +return self.z +end +function POINT_VEC3:SetX(x) +self.x=x +return self +end +function POINT_VEC3:SetY(y) +self.y=y +return self +end +function POINT_VEC3:SetZ(z) +self.z=z +return self +end +function POINT_VEC3:AddX(x) +self.x=self.x+x +return self +end +function POINT_VEC3:AddY(y) +self.y=self.y+y +return self +end +function POINT_VEC3:AddZ(z) +self.z=self.z+z +return self +end +function POINT_VEC3:GetRandomPointVec3InRadius(OuterRadius,InnerRadius) +return POINT_VEC3:NewFromVec3(self:GetRandomVec3InRadius(OuterRadius,InnerRadius)) +end +end +do +POINT_VEC2={ +ClassName="POINT_VEC2", +} +function POINT_VEC2:New(x,y,LandHeightAdd) +local LandHeight=land.getHeight({["x"]=x,["y"]=y}) +LandHeightAdd=LandHeightAdd or 0 +LandHeight=LandHeight+LandHeightAdd +local self=BASE:Inherit(self,COORDINATE:New(x,LandHeight,y)) +self:F2(self) +return self +end +function POINT_VEC2:NewFromVec2(Vec2,LandHeightAdd) +local LandHeight=land.getHeight(Vec2) +LandHeightAdd=LandHeightAdd or 0 +LandHeight=LandHeight+LandHeightAdd +local self=BASE:Inherit(self,COORDINATE:NewFromVec2(Vec2,LandHeightAdd)) +self:F2(self) +return self +end +function POINT_VEC2:NewFromVec3(Vec3) +local self=BASE:Inherit(self,COORDINATE:NewFromVec3(Vec3)) +self:F2(self) +return self +end +function POINT_VEC2:GetX() +return self.x +end +function POINT_VEC2:GetY() +return self.z +end +function POINT_VEC2:SetX(x) +self.x=x +return self +end +function POINT_VEC2:SetY(y) +self.z=y +return self +end +function POINT_VEC2:GetLat() +return self.x +end +function POINT_VEC2:SetLat(x) +self.x=x +return self +end +function POINT_VEC2:GetLon() +return self.z +end +function POINT_VEC2:SetLon(z) +self.z=z +return self +end +function POINT_VEC2:GetAlt() +return self.y~=0 or land.getHeight({x=self.x,y=self.z}) +end +function POINT_VEC2:SetAlt(Altitude) +self.y=Altitude or land.getHeight({x=self.x,y=self.z}) +return self +end +function POINT_VEC2:AddX(x) +self.x=self.x+x +return self +end +function POINT_VEC2:AddY(y) +self.z=self.z+y +return self +end +function POINT_VEC2:AddAlt(Altitude) +self.y=land.getHeight({x=self.x,y=self.z})+Altitude or 0 +return self +end +function POINT_VEC2:GetRandomPointVec2InRadius(OuterRadius,InnerRadius) +self:F2({OuterRadius,InnerRadius}) +return POINT_VEC2:NewFromVec2(self:GetRandomVec2InRadius(OuterRadius,InnerRadius)) +end +function POINT_VEC2:DistanceFromPointVec2(PointVec2Reference) +self:F2(PointVec2Reference) +local Distance=((PointVec2Reference.x-self.x)^2+(PointVec2Reference.z-self.z)^2)^0.5 +self:T2(Distance) +return Distance +end +end +do +VELOCITY={ +ClassName="VELOCITY", +} +function VELOCITY:New(VelocityMps) +local self=BASE:Inherit(self,BASE:New()) +self:F({}) +self.Velocity=VelocityMps +return self +end +function VELOCITY:Set(VelocityMps) +self.Velocity=VelocityMps +return self +end +function VELOCITY:Get() +return self.Velocity +end +function VELOCITY:SetKmph(VelocityKmph) +self.Velocity=UTILS.KmphToMps(VelocityKmph) +return self +end +function VELOCITY:GetKmph() +return UTILS.MpsToKmph(self.Velocity) +end +function VELOCITY:SetMiph(VelocityMiph) +self.Velocity=UTILS.MiphToMps(VelocityMiph) +return self +end +function VELOCITY:GetMiph() +return UTILS.MpsToMiph(self.Velocity) +end +function VELOCITY:GetText(Settings) +local Settings=Settings or _SETTINGS +if self.Velocity~=0 then +if Settings:IsMetric()then +return string.format("%d km/h",UTILS.MpsToKmph(self.Velocity)) +else +return string.format("%d mi/h",UTILS.MpsToMiph(self.Velocity)) +end +else +return"stationary" +end +end +function VELOCITY:ToString(VelocityGroup,Settings) +self:F({Group=VelocityGroup and VelocityGroup:GetName()}) +local Settings=Settings or(VelocityGroup and _DATABASE:GetPlayerSettings(VelocityGroup:GetPlayerName()))or _SETTINGS +return self:GetText(Settings) +end +end +do +VELOCITY_POSITIONABLE={ +ClassName="VELOCITY_POSITIONABLE", +} +function VELOCITY_POSITIONABLE:New(Positionable) +local self=BASE:Inherit(self,VELOCITY:New()) +self:F({}) +self.Positionable=Positionable +return self +end +function VELOCITY_POSITIONABLE:Get() +return self.Positionable:GetVelocityMPS()or 0 +end +function VELOCITY_POSITIONABLE:GetKmph() +return UTILS.MpsToKmph(self.Positionable:GetVelocityMPS()or 0) +end +function VELOCITY_POSITIONABLE:GetMiph() +return UTILS.MpsToMiph(self.Positionable:GetVelocityMPS()or 0) +end +function VELOCITY_POSITIONABLE:ToString() +self:F({Group=self.Positionable and self.Positionable:GetName()}) +local Settings=Settings or(self.Positionable and _DATABASE:GetPlayerSettings(self.Positionable:GetPlayerName()))or _SETTINGS +self.Velocity=self.Positionable:GetVelocityMPS() +return self:GetText(Settings) +end +end +MESSAGE={ +ClassName="MESSAGE", +MessageCategory=0, +MessageID=0, +} +MESSAGE.Type={ +Update="Update", +Information="Information", +Briefing="Briefing Report", +Overview="Overview Report", +Detailed="Detailed Report" +} +function MESSAGE:New(MessageText,MessageDuration,MessageCategory,ClearScreen) +local self=BASE:Inherit(self,BASE:New()) +self:F({MessageText,MessageDuration,MessageCategory}) +self.MessageType=nil +if MessageCategory and MessageCategory~=""then +if MessageCategory:sub(-1)~="\n"then +self.MessageCategory=MessageCategory..": " +else +self.MessageCategory=MessageCategory:sub(1,-2)..":\n" +end +else +self.MessageCategory="" +end +self.ClearScreen=false +if ClearScreen~=nil then +self.ClearScreen=ClearScreen +end +self.MessageDuration=MessageDuration or 5 +self.MessageTime=timer.getTime() +self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1) +self.MessageSent=false +self.MessageGroup=false +self.MessageCoalition=false +return self +end +function MESSAGE:NewType(MessageText,MessageType,ClearScreen) +local self=BASE:Inherit(self,BASE:New()) +self:F({MessageText}) +self.MessageType=MessageType +self.ClearScreen=false +if ClearScreen~=nil then +self.ClearScreen=ClearScreen +end +self.MessageTime=timer.getTime() +self.MessageText=MessageText:gsub("^\n","",1):gsub("\n$","",1) +return self +end +function MESSAGE:Clear() +self:F() +self.ClearScreen=true +return self +end +function MESSAGE:ToClient(Client,Settings) +self:F(Client) +if Client and Client:GetClientGroupID()then +if self.MessageType then +local Settings=Settings or(Client and _DATABASE:GetPlayerSettings(Client:GetPlayerName()))or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if self.MessageDuration~=0 then +local ClientGroupID=Client:GetClientGroupID() +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +trigger.action.outTextForGroup(ClientGroupID,self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +return self +end +function MESSAGE:ToGroup(Group,Settings) +self:F(Group.GroupName) +if Group then +if self.MessageType then +local Settings=Settings or(Group and _DATABASE:GetPlayerSettings(Group:GetPlayerName()))or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if self.MessageDuration~=0 then +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +trigger.action.outTextForGroup(Group:GetID(),self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +return self +end +function MESSAGE: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.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +end +return self +end +function MESSAGE:ToCoalitionIf(CoalitionSide,Condition) +self:F(CoalitionSide) +if Condition and Condition==true then +self:ToCoalition(CoalitionSide) +end +return self +end +function MESSAGE:ToAll(Settings) +self:F() +if self.MessageType then +local Settings=Settings or _SETTINGS +self.MessageDuration=Settings:GetMessageTime(self.MessageType) +self.MessageCategory="" +end +if self.MessageDuration~=0 then +self:T(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$","").." / "..self.MessageDuration) +trigger.action.outText(self.MessageCategory..self.MessageText:gsub("\n$",""):gsub("\n$",""),self.MessageDuration,self.ClearScreen) +end +return self +end +function MESSAGE:ToAllIf(Condition) +if Condition and Condition==true then +self:ToAll() +end +return self +end +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:T2(Transition) +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) +self:T({From,Event}) +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 +self:T(Process._Scores) +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) +self:T2("Added methods: "..Event..", "..__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:T2("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 %.1f sec SCHEDULED with CallID=%s",EventName,DelaySeconds,tostring(CallID))) +else +self:T2(string.format("NEGATIVE Event %s delayed by %.1f 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 %.1f 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 +self:T2({CallID=CallID}) +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 +self:T({ParentFrom,ParentEvent,self.subs[ParentFrom],self.subs[ParentFrom][ParentEvent]}) +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 +self:T3({Map,Event}) +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) +self:T({self:GetClassNameAndID()}) +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 +self:T(EndState) +NewFsm:AddEndState(EndState) +end +for ScoreID,Score in pairs(self:GetScores())do +self:T(Score) +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.Controllable +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 +RADIO={ +ClassName="RADIO", +FileName="", +Frequency=0, +Modulation=radio.modulation.AM, +Subtitle="", +SubtitleDuration=0, +Power=100, +Loop=false, +alias=nil, +} +function RADIO:New(Positionable) +local self=BASE:Inherit(self,BASE:New()) +self:F(Positionable) +if Positionable:GetPointVec2()then +self.Positionable=Positionable +return self +end +self:E({error="The passed positionable is invalid, no RADIO created!",positionable=Positionable}) +return nil +end +function RADIO:SetAlias(alias) +self.alias=tostring(alias) +return self +end +function RADIO:GetAlias() +return tostring(self.alias) +end +function RADIO:SetFileName(FileName) +self:F2(FileName) +if type(FileName)=="string"then +if FileName:find(".ogg")or FileName:find(".wav")then +if not FileName:find("l10n/DEFAULT/")then +FileName="l10n/DEFAULT/"..FileName +end +self.FileName=FileName +return self +end +end +self:E({"File name invalid. Maybe something wrong with the extension?",FileName}) +return self +end +function RADIO:SetFrequency(Frequency) +self:F2(Frequency) +if type(Frequency)=="number"then +if(Frequency>=30 and Frequency<=87.995)or(Frequency>=108 and Frequency<=173.995)or(Frequency>=225 and Frequency<=399.975)then +self.Frequency=Frequency*1000000 +if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then +local commandSetFrequency={ +id="SetFrequency", +params={ +frequency=self.Frequency, +modulation=self.Modulation, +} +} +self:T2(commandSetFrequency) +self.Positionable:SetCommand(commandSetFrequency) +end +return self +end +end +self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.",Frequency}) +return self +end +function RADIO:SetModulation(Modulation) +self:F2(Modulation) +if type(Modulation)=="number"then +if Modulation==radio.modulation.AM or Modulation==radio.modulation.FM then +self.Modulation=Modulation +return self +end +end +self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.",self.Modulation}) +return self +end +function RADIO:SetPower(Power) +self:F2(Power) +if type(Power)=="number"then +self.Power=math.floor(math.abs(Power)) +else +self:E({"Power is invalid. Power unchanged.",self.Power}) +end +return self +end +function RADIO:SetLoop(Loop) +self:F2(Loop) +if type(Loop)=="boolean"then +self.Loop=Loop +return self +end +self:E({"Loop is invalid. Loop unchanged.",self.Loop}) +return self +end +function RADIO:SetSubtitle(Subtitle,SubtitleDuration) +self:F2({Subtitle,SubtitleDuration}) +if type(Subtitle)=="string"then +self.Subtitle=Subtitle +else +self.Subtitle="" +self:E({"Subtitle is invalid. Subtitle reset.",self.Subtitle}) +end +if type(SubtitleDuration)=="number"then +self.SubtitleDuration=SubtitleDuration +else +self.SubtitleDuration=0 +self:E({"SubtitleDuration is invalid. SubtitleDuration reset.",self.SubtitleDuration}) +end +return self +end +function RADIO:NewGenericTransmission(FileName,Frequency,Modulation,Power,Loop) +self:F({FileName,Frequency,Modulation,Power}) +self:SetFileName(FileName) +if Frequency then self:SetFrequency(Frequency)end +if Modulation then self:SetModulation(Modulation)end +if Power then self:SetPower(Power)end +if Loop then self:SetLoop(Loop)end +return self +end +function RADIO:NewUnitTransmission(FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop) +self:F({FileName,Subtitle,SubtitleDuration,Frequency,Modulation,Loop}) +self:SetFileName(FileName) +if Modulation then +self:SetModulation(Modulation) +end +if Frequency then +self:SetFrequency(Frequency) +end +if Subtitle then +self:SetSubtitle(Subtitle,SubtitleDuration or 0) +end +if Loop then +self:SetLoop(Loop) +end +return self +end +function RADIO:Broadcast(viatrigger) +self:F({viatrigger=viatrigger}) +if(self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP")and(not viatrigger)then +self:T("Broadcasting from a UNIT or a GROUP") +local commandTransmitMessage={ +id="TransmitMessage", +params={ +file=self.FileName, +duration=self.SubtitleDuration, +subtitle=self.Subtitle, +loop=self.Loop, +}} +self:T3(commandTransmitMessage) +self.Positionable:SetCommand(commandTransmitMessage) +else +self:T("Broadcasting from a POSITIONABLE") +trigger.action.radioTransmission(self.FileName,self.Positionable:GetPositionVec3(),self.Modulation,self.Loop,self.Frequency,self.Power,tostring(self.ID)) +end +return self +end +function RADIO:StopBroadcast() +self:F() +if self.Positionable.ClassName=="UNIT"or self.Positionable.ClassName=="GROUP"then +local commandStopTransmission={id="StopTransmission",params={}} +self.Positionable:SetCommand(commandStopTransmission) +else +trigger.action.stopRadioTransmission(tostring(self.ID)) +end +return self +end +BEACON={ +ClassName="BEACON", +Positionable=nil, +name=nil, +} +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}) +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~="Y"then +self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.",self.Positionable}) +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:AATACAN(TACANChannel,Message,Bearing,BeaconDuration) +self:F({TACANChannel,Message,Bearing,BeaconDuration}) +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=5 +else +System=14 +end +if IsValid then +self:T2({"AA TACAN BEACON started !"}) +self.Positionable:SetCommand({ +id="ActivateBeacon", +params={ +type=4, +system=System, +callsign=Message, +frequency=Frequency, +} +}) +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 +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}) +trigger.action.radioTransmission(FileName,self.Positionable:GetPositionVec3(),Modulation,true,Frequency,Power,tostring(self.ID)) +if BeaconDuration then +SCHEDULER:New(nil, +function() +self:StopRadioBeacon() +end,{},BeaconDuration) +end +end +end +function BEACON:StopRadioBeacon() +self:F() +trigger.action.stopRadioTransmission(tostring(self.ID)) +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 +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(delay) +else +self.RQid=self.scheduler:Schedule(nil,RADIOQUEUE._CheckRadioQueue,{self},delay,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:SetDigit(digit,filename,duration,path,subtitle,subduration) +local transmission={} +transmission.filename=filename +transmission.duration=duration +transmission.path=path or"l10n/DEFAULT/" +transmission.subtitle=nil +transmission.subduration=nil +if type(digit)=="number"then +digit=tostring(digit) +end +self.numbers[digit]=transmission +return self +end +function RADIOQUEUE:AddTransmission(transmission) +self:F({transmission=transmission}) +transmission.isplaying=false +transmission.Tstarted=nil +table.insert(self.queue,transmission) +if self.schedonce and not self.checking then +self:_CheckRadioQueueDelayed() +end +return self +end +function RADIOQUEUE:NewTransmission(filename,duration,path,tstart,interval,subtitle,subduration) +if not filename then +self:E(self.lid.."ERROR: No filename specified.") +return nil +end +if type(filename)~="string"then +self:E(self.lid.."ERROR: Filename specified is NOT a string.") +return nil +end +if not duration then +self:E(self.lid.."ERROR: No duration of transmission specified.") +return nil +end +if type(duration)~="number"then +self:E(self.lid.."ERROR: Duration specified is NOT a number.") +return nil +end +local transmission={} +transmission.filename=filename +transmission.duration=duration +transmission.path=path or"l10n/DEFAULT/" +transmission.Tplay=tstart or timer.getAbsTime() +transmission.subtitle=subtitle +transmission.interval=interval or 0 +if transmission.subtitle then +transmission.subduration=subduration or 5 +else +transmission.subduration=nil +end +self:AddTransmission(transmission) +return self +end +function RADIOQUEUE:Number2Transmission(number,delay,interval) +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 numbers=_split(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) +local sender=self:_GetRadioSender() +local filename=string.format("%s%s",transmission.path,transmission.filename) +if sender then +self:T(self.lid..string.format("Broadcasting from aircraft %s",sender:GetName())) +if not self.senderinit then +local commandFrequency={ +id="SetFrequency", +params={ +frequency=self.frequency, +modulation=self.modulation, +}} +sender:SetCommand(commandFrequency) +self.senderinit=true +end +local subtitle=nil +local duration=nil +if transmission.subtitle and transmission.subduration and transmission.subduration>0 then +subtitle=transmission.subtitle +duration=transmission.subduration +end +local commandTransmit={ +id="TransmitMessage", +params={ +file=filename, +duration=duration, +subtitle=subtitle, +loop=false, +}} +sender:SetCommand(commandTransmit) +if self.Debugmode then +local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"") +MESSAGE:New(text,2,"RADIOQUEUE "..self.alias):ToAll() +end +else +self:T(self.lid..string.format("Broadcasting via trigger.action.radioTransmission().")) +local vec3=nil +if self.sendername then +vec3=self:_GetRadioSenderCoord() +end +if self.sendercoord and not vec3 then +vec3=self.sendercoord:GetVec3() +end +if vec3 then +self:T("Sending") +self:T({filename=filename,vec3=vec3,modulation=self.modulation,frequency=self.frequency,power=self.power}) +trigger.action.radioTransmission(filename,vec3,self.modulation,false,self.frequency,self.power) +if self.Debugmode then +local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s",filename,self.frequency/1000000,transmission.duration,transmission.subtitle or"") +MESSAGE:New(string.format(text,filename,transmission.duration,transmission.subtitle or""),5,"RADIOQUEUE "..self.alias):ToAll() +end +end +end +end +function RADIOQUEUE:_CheckRadioQueueDelayed(delay) +self.checking=true +self:ScheduleOnce(delay or self.dt,RADIOQUEUE._CheckRadioQueue,self) +end +function RADIOQUEUE:_CheckRadioQueue() +if#self.queue==0 then +self.checking=false +return +end +local time=timer.getAbsTime() +local playing=false +local next=nil +local remove=nil +for i,_transmission in ipairs(self.queue)do +local transmission=_transmission +if time>=transmission.Tplay then +if transmission.isplaying then +if time>=transmission.Tstarted+transmission.duration then +transmission.isplaying=false +remove=i +self.Tlast=time +else +playing=true +end +else +local Tlast=self.Tlast +if transmission.interval==nil then +if next==nil then +next=transmission +end +else +if Tlast==nil or time-Tlast>=transmission.interval then +next=transmission +else +end +end +if next or Tlast then +break +end +end +else +end +end +if next~=nil and not playing then +self:Broadcast(next) +next.isplaying=true +next.Tstarted=time +end +if remove then +table.remove(self.queue,remove) +end +if self.schedonce then +self:_CheckRadioQueueDelayed() +end +end +function RADIOQUEUE:_GetRadioSender() +local sender=nil +if self.sendername then +sender=UNIT:FindByName(self.sendername) +if sender and sender:IsAlive()and(sender:IsAir()or sender:IsGround())then +return sender +end +end +return nil +end +function RADIOQUEUE:_GetRadioSenderCoord() +local vec3=nil +if self.sendername then +local sender=UNIT:FindByName(self.sendername) +if sender and sender:IsAlive()then +return sender:GetVec3() +end +local sender=STATIC:FindByName(self.sendername,false) +if sender then +return sender:GetVec3() +end +end +return nil +end +RADIOSPEECH={ +ClassName="RADIOSPEECH", +Vocabulary={ +EN={}, +DE={}, +RU={}, +} +} +RADIOSPEECH.Vocabulary.EN={ +["1"]={"1",0.25}, +["2"]={"2",0.25}, +["3"]={"3",0.30}, +["4"]={"4",0.35}, +["5"]={"5",0.35}, +["6"]={"6",0.42}, +["7"]={"7",0.38}, +["8"]={"8",0.20}, +["9"]={"9",0.32}, +["10"]={"10",0.35}, +["11"]={"11",0.40}, +["12"]={"12",0.42}, +["13"]={"13",0.38}, +["14"]={"14",0.42}, +["15"]={"15",0.42}, +["16"]={"16",0.52}, +["17"]={"17",0.59}, +["18"]={"18",0.40}, +["19"]={"19",0.47}, +["20"]={"20",0.38}, +["30"]={"30",0.29}, +["40"]={"40",0.35}, +["50"]={"50",0.32}, +["60"]={"60",0.44}, +["70"]={"70",0.48}, +["80"]={"80",0.26}, +["90"]={"90",0.36}, +["100"]={"100",0.55}, +["200"]={"200",0.55}, +["300"]={"300",0.61}, +["400"]={"400",0.60}, +["500"]={"500",0.61}, +["600"]={"600",0.65}, +["700"]={"700",0.70}, +["800"]={"800",0.54}, +["900"]={"900",0.60}, +["1000"]={"1000",0.60}, +["2000"]={"2000",0.61}, +["3000"]={"3000",0.64}, +["4000"]={"4000",0.62}, +["5000"]={"5000",0.69}, +["6000"]={"6000",0.69}, +["7000"]={"7000",0.75}, +["8000"]={"8000",0.59}, +["9000"]={"9000",0.65}, +["chevy"]={"chevy",0.35}, +["colt"]={"colt",0.35}, +["springfield"]={"springfield",0.65}, +["dodge"]={"dodge",0.35}, +["enfield"]={"enfield",0.5}, +["ford"]={"ford",0.32}, +["pontiac"]={"pontiac",0.55}, +["uzi"]={"uzi",0.28}, +["degrees"]={"degrees",0.5}, +["kilometers"]={"kilometers",0.65}, +["km"]={"kilometers",0.65}, +["miles"]={"miles",0.45}, +["meters"]={"meters",0.41}, +["mi"]={"miles",0.45}, +["feet"]={"feet",0.29}, +["br"]={"br",1.1}, +["bra"]={"bra",0.3}, +["returning to base"]={"returning_to_base",0.85}, +["on route to ground target"]={"on_route_to_ground_target",1.05}, +["intercepting bogeys"]={"intercepting_bogeys",1.00}, +["engaging ground target"]={"engaging_ground_target",1.20}, +["engaging bogeys"]={"engaging_bogeys",0.81}, +["wheels up"]={"wheels_up",0.42}, +["landing at base"]={"landing at base",0.8}, +["patrolling"]={"patrolling",0.55}, +["for"]={"for",0.31}, +["and"]={"and",0.31}, +["at"]={"at",0.3}, +["dot"]={"dot",0.26}, +["defender"]={"defender",0.45}, +} +RADIOSPEECH.Vocabulary.RU={ +["1"]={"1",0.34}, +["2"]={"2",0.30}, +["3"]={"3",0.23}, +["4"]={"4",0.51}, +["5"]={"5",0.31}, +["6"]={"6",0.44}, +["7"]={"7",0.25}, +["8"]={"8",0.43}, +["9"]={"9",0.45}, +["10"]={"10",0.53}, +["11"]={"11",0.66}, +["12"]={"12",0.70}, +["13"]={"13",0.66}, +["14"]={"14",0.80}, +["15"]={"15",0.65}, +["16"]={"16",0.75}, +["17"]={"17",0.74}, +["18"]={"18",0.85}, +["19"]={"19",0.80}, +["20"]={"20",0.58}, +["30"]={"30",0.51}, +["40"]={"40",0.51}, +["50"]={"50",0.67}, +["60"]={"60",0.76}, +["70"]={"70",0.68}, +["80"]={"80",0.84}, +["90"]={"90",0.71}, +["100"]={"100",0.35}, +["200"]={"200",0.59}, +["300"]={"300",0.53}, +["400"]={"400",0.70}, +["500"]={"500",0.50}, +["600"]={"600",0.58}, +["700"]={"700",0.64}, +["800"]={"800",0.77}, +["900"]={"900",0.75}, +["1000"]={"1000",0.87}, +["2000"]={"2000",0.83}, +["3000"]={"3000",0.84}, +["4000"]={"4000",1.00}, +["5000"]={"5000",0.77}, +["6000"]={"6000",0.90}, +["7000"]={"7000",0.77}, +["8000"]={"8000",0.92}, +["9000"]={"9000",0.87}, +["степени"]={"degrees",0.5}, +["километров"]={"kilometers",0.65}, +["km"]={"kilometers",0.65}, +["миль"]={"miles",0.45}, +["mi"]={"miles",0.45}, +["метры"]={"meters",0.41}, +["m"]={"meters",0.41}, +["ноги"]={"feet",0.37}, +["br"]={"br",1.1}, +["bra"]={"bra",0.3}, +["возвращаясь на базу"]={"returning_to_base",1.40}, +["на пути к наземной цели"]={"on_route_to_ground_target",1.45}, +["перехват самолетов"]={"intercepting_bogeys",1.22}, +["поражение наземной цели"]={"engaging_ground_target",1.53}, +["захватывающие самолеты"]={"engaging_bogeys",1.68}, +["колеса вверх"]={"wheels_up",0.92}, +["посадка на базу"]={"landing at base",1.04}, +["патрулирующий"]={"patrolling",0.96}, +["за"]={"for",0.27}, +["и"]={"and",0.17}, +["в"]={"at",0.19}, +["dot"]={"dot",0.51}, +["defender"]={"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 +SPAWN={ +ClassName="SPAWN", +SpawnTemplatePrefix=nil, +SpawnAliasPrefix=nil, +} +SPAWN.Takeoff={ +Air=1, +Runway=2, +Hot=3, +Cold=4, +} +function SPAWN:New(SpawnTemplatePrefix) +local self=BASE:Inherit(self,BASE:New()) +self:F({SpawnTemplatePrefix}) +local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix) +if TemplateGroup then +self.SpawnTemplatePrefix=SpawnTemplatePrefix +self.SpawnIndex=0 +self.SpawnCount=0 +self.AliveUnits=0 +self.SpawnIsScheduled=false +self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix) +self.Repeat=false +self.UnControlled=false +self.SpawnInitLimit=false +self.SpawnMaxUnitsAlive=0 +self.SpawnMaxGroups=0 +self.SpawnRandomize=false +self.SpawnVisible=false +self.AIOnOff=true +self.SpawnUnControlled=false +self.SpawnInitKeepUnitNames=false +self.DelayOnOff=false +self.SpawnGrouping=nil +self.SpawnInitLivery=nil +self.SpawnInitSkill=nil +self.SpawnInitFreq=nil +self.SpawnInitModu=nil +self.SpawnInitRadio=nil +self.SpawnInitModex=nil +self.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:NewWithAlias(SpawnTemplatePrefix,SpawnAliasPrefix) +local self=BASE:Inherit(self,BASE:New()) +self:F({SpawnTemplatePrefix,SpawnAliasPrefix}) +local TemplateGroup=GROUP:FindByName(SpawnTemplatePrefix) +if TemplateGroup then +self.SpawnTemplatePrefix=SpawnTemplatePrefix +self.SpawnAliasPrefix=SpawnAliasPrefix +self.SpawnIndex=0 +self.SpawnCount=0 +self.AliveUnits=0 +self.SpawnIsScheduled=false +self.SpawnTemplate=self._GetTemplate(self,SpawnTemplatePrefix) +self.Repeat=false +self.UnControlled=false +self.SpawnInitLimit=false +self.SpawnMaxUnitsAlive=0 +self.SpawnMaxGroups=0 +self.SpawnRandomize=false +self.SpawnVisible=false +self.AIOnOff=true +self.SpawnUnControlled=false +self.SpawnInitKeepUnitNames=false +self.DelayOnOff=false +self.SpawnGrouping=nil +self.SpawnInitLivery=nil +self.SpawnInitSkill=nil +self.SpawnInitFreq=nil +self.SpawnInitModu=nil +self.SpawnInitRadio=nil +self.SpawnInitModex=nil +self.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) +local self=BASE:Inherit(self,BASE:New()) +self:F({SpawnTemplate,SpawnTemplatePrefix,SpawnAliasPrefix}) +if SpawnAliasPrefix==nil or SpawnAliasPrefix==""then +BASE:I("ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set") +return nil +end +if SpawnTemplate then +self.SpawnTemplate=SpawnTemplate +self.SpawnTemplatePrefix=SpawnTemplatePrefix +self.SpawnAliasPrefix=SpawnAliasPrefix +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.SpawnInitAirbase=nil +self.TweakedTemplate=true +self.SpawnGroups={} +else +error("There is no template provided for SpawnTemplatePrefix = '"..SpawnTemplatePrefix.."'") +end +self:SetEventPriority(5) +self.SpawnHookScheduler=SCHEDULER:New(nil) +return self +end +function SPAWN:InitLimit(SpawnMaxUnitsAlive,SpawnMaxGroups) +self:F({self.SpawnTemplatePrefix,SpawnMaxUnitsAlive,SpawnMaxGroups}) +self.SpawnInitLimit=true +self.SpawnMaxUnitsAlive=SpawnMaxUnitsAlive +self.SpawnMaxGroups=SpawnMaxGroups +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_InitializeSpawnGroups(SpawnGroupID) +end +return self +end +function SPAWN:InitKeepUnitNames(KeepUnitNames) +self:F() +self.SpawnInitKeepUnitNames=KeepUnitNames or true +return self +end +function SPAWN:InitLateActivated(LateActivated) +self:F() +self.LateActivated=LateActivated or true +return self +end +function SPAWN:InitAirbase(AirbaseName,Takeoff,TerminalType) +self:F() +self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName) +self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot +self.SpawnInitTerminalType=TerminalType +return self +end +function SPAWN:InitHeading(HeadingMin,HeadingMax) +self:F() +self.SpawnInitHeadingMin=HeadingMin +self.SpawnInitHeadingMax=HeadingMax +return self +end +function SPAWN:InitGroupHeading(HeadingMin,HeadingMax,unitVar) +self:F({HeadingMin=HeadingMin,HeadingMax=HeadingMax,unitVar=unitVar}) +self.SpawnInitGroupHeadingMin=HeadingMin +self.SpawnInitGroupHeadingMax=HeadingMax +self.SpawnInitGroupUnitVar=unitVar +return self +end +function SPAWN:InitCoalition(Coalition) +self:F({coalition=Coalition}) +self.SpawnInitCoalition=Coalition +return self +end +function SPAWN:InitCountry(Country) +self:F() +self.SpawnInitCountry=Country +return self +end +function SPAWN:InitCategory(Category) +self:F() +self.SpawnInitCategory=Category +return self +end +function SPAWN:InitLivery(Livery) +self:F({livery=Livery}) +self.SpawnInitLivery=Livery +return self +end +function SPAWN:InitSkill(Skill) +self:F({skill=Skill}) +if Skill:lower()=="average"then +self.SpawnInitSkill="Average" +elseif Skill:lower()=="good"then +self.SpawnInitSkill="Good" +elseif Skill:lower()=="excellent"then +self.SpawnInitSkill="Excellent" +elseif Skill:lower()=="random"then +self.SpawnInitSkill="Random" +else +self.SpawnInitSkill="High" +end +return self +end +function SPAWN:InitRadioCommsOnOff(switch) +self:F({switch=switch}) +self.SpawnInitRadio=switch or true +return self +end +function SPAWN:InitRadioFrequency(frequency) +self:F({frequency=frequency}) +self.SpawnInitFreq=frequency +return self +end +function SPAWN:InitRadioModulation(modulation) +self:F({modulation=modulation}) +if modulation and modulation:lower()=="fm"then +self.SpawnInitModu=radio.modulation.FM +else +self.SpawnInitModu=radio.modulation.AM +end +return self +end +function SPAWN:InitModex(modex) +if modex then +self.SpawnInitModex=tonumber(modex) +end +return self +end +function SPAWN:InitRandomizeRoute(SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight) +self:F({self.SpawnTemplatePrefix,SpawnStartPoint,SpawnEndPoint,SpawnRadius,SpawnHeight}) +self.SpawnRandomizeRoute=true +self.SpawnRandomizeRouteStartPoint=SpawnStartPoint +self.SpawnRandomizeRouteEndPoint=SpawnEndPoint +self.SpawnRandomizeRouteRadius=SpawnRadius +self.SpawnRandomizeRouteHeight=SpawnHeight +for GroupID=1,self.SpawnMaxGroups do +self:_RandomizeRoute(GroupID) +end +return self +end +function SPAWN:InitRandomizePosition(RandomizePosition,OuterRadius,InnerRadius) +self:F({self.SpawnTemplatePrefix,RandomizePosition,OuterRadius,InnerRadius}) +self.SpawnRandomizePosition=RandomizePosition or false +self.SpawnRandomizePositionOuterRadius=OuterRadius or 0 +self.SpawnRandomizePositionInnerRadius=InnerRadius or 0 +for GroupID=1,self.SpawnMaxGroups do +self:_RandomizeRoute(GroupID) +end +return self +end +function SPAWN:InitRandomizeUnits(RandomizeUnits,OuterRadius,InnerRadius) +self:F({self.SpawnTemplatePrefix,RandomizeUnits,OuterRadius,InnerRadius}) +self.SpawnRandomizeUnits=RandomizeUnits or false +self.SpawnOuterRadius=OuterRadius or 0 +self.SpawnInnerRadius=InnerRadius or 0 +for GroupID=1,self.SpawnMaxGroups do +self:_RandomizeRoute(GroupID) +end +return self +end +function SPAWN:InitRandomizeTemplate(SpawnTemplatePrefixTable) +self:F({self.SpawnTemplatePrefix,SpawnTemplatePrefixTable}) +self.SpawnTemplatePrefixTable=SpawnTemplatePrefixTable +self.SpawnRandomizeTemplate=true +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_RandomizeTemplate(SpawnGroupID) +end +return self +end +function SPAWN:InitRandomizeTemplateSet(SpawnTemplateSet) +self:F({self.SpawnTemplatePrefix}) +self.SpawnTemplatePrefixTable=SpawnTemplateSet:GetSetNames() +self.SpawnRandomizeTemplate=true +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_RandomizeTemplate(SpawnGroupID) +end +return self +end +function SPAWN:InitRandomizeTemplatePrefixes(SpawnTemplatePrefixes) +self:F({self.SpawnTemplatePrefix}) +local SpawnTemplateSet=SET_GROUP:New():FilterPrefixes(SpawnTemplatePrefixes):FilterOnce() +self:InitRandomizeTemplateSet(SpawnTemplateSet) +return self +end +function SPAWN:InitGrouping(Grouping) +self:F({self.SpawnTemplatePrefix,Grouping}) +self.SpawnGrouping=Grouping +return self +end +function SPAWN:InitRandomizeZones(SpawnZoneTable) +self:F({self.SpawnTemplatePrefix,SpawnZoneTable}) +self.SpawnZoneTable=SpawnZoneTable +self.SpawnRandomizeZones=true +for SpawnGroupID=1,self.SpawnMaxGroups do +self:_RandomizeZones(SpawnGroupID) +end +return self +end +function SPAWN:InitRepeat() +self:F({self.SpawnTemplatePrefix,self.SpawnIndex}) +self.Repeat=true +self.RepeatOnEngineShutDown=false +self.RepeatOnLanding=true +return self +end +function SPAWN:InitRepeatOnLanding() +self:F({self.SpawnTemplatePrefix}) +self:InitRepeat() +self.RepeatOnEngineShutDown=false +self.RepeatOnLanding=true +return self +end +function SPAWN:InitRepeatOnEngineShutDown() +self:F({self.SpawnTemplatePrefix}) +self:InitRepeat() +self.RepeatOnEngineShutDown=true +self.RepeatOnLanding=false +return self +end +function SPAWN:InitCleanUp(SpawnCleanUpInterval) +self:F({self.SpawnTemplatePrefix,SpawnCleanUpInterval}) +self.SpawnCleanUpInterval=SpawnCleanUpInterval +self.SpawnCleanUpTimeStamps={} +local SpawnGroup,SpawnCursor=self:GetFirstAliveGroup() +self:T({"CleanUp Scheduler:",SpawnGroup}) +self.CleanUpScheduler=SCHEDULER:New(self,self._SpawnCleanUpScheduler,{},1,SpawnCleanUpInterval,0.2) +return self +end +function SPAWN:InitArray(SpawnAngle,SpawnWidth,SpawnDeltaX,SpawnDeltaY) +self:F({self.SpawnTemplatePrefix,SpawnAngle,SpawnWidth,SpawnDeltaX,SpawnDeltaY}) +self.SpawnVisible=true +local SpawnX=0 +local SpawnY=0 +local SpawnXIndex=0 +local SpawnYIndex=0 +for SpawnGroupID=1,self.SpawnMaxGroups do +self:T({SpawnX,SpawnY,SpawnXIndex,SpawnYIndex}) +self.SpawnGroups[SpawnGroupID].Visible=true +self.SpawnGroups[SpawnGroupID].Spawned=false +SpawnXIndex=SpawnXIndex+1 +if SpawnWidth and SpawnWidth~=0 then +if SpawnXIndex>=SpawnWidth then +SpawnXIndex=0 +SpawnYIndex=SpawnYIndex+1 +end +end +local SpawnRootX=self.SpawnGroups[SpawnGroupID].SpawnTemplate.x +local SpawnRootY=self.SpawnGroups[SpawnGroupID].SpawnTemplate.y +self:_TranslateRotate(SpawnGroupID,SpawnRootX,SpawnRootY,SpawnX,SpawnY,SpawnAngle) +self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation=true +self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible=true +self.SpawnGroups[SpawnGroupID].Visible=true +self:HandleEvent(EVENTS.Birth,self._OnBirth) +self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) +if self.Repeat then +self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) +self:HandleEvent(EVENTS.Land,self._OnLand) +end +if self.RepeatOnEngineShutDown then +self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown) +end +self.SpawnGroups[SpawnGroupID].Group=_DATABASE:Spawn(self.SpawnGroups[SpawnGroupID].SpawnTemplate) +SpawnX=SpawnXIndex*SpawnDeltaX +SpawnY=SpawnYIndex*SpawnDeltaY +end +return self +end +do +function SPAWN:InitAIOnOff(AIOnOff) +self.AIOnOff=AIOnOff +return self +end +function SPAWN:InitAIOn() +return self:InitAIOnOff(true) +end +function SPAWN:InitAIOff() +return self:InitAIOnOff(false) +end +end +do +function SPAWN:InitDelayOnOff(DelayOnOff) +self.DelayOnOff=DelayOnOff +return self +end +function SPAWN:InitDelayOn() +return self:InitDelayOnOff(true) +end +function SPAWN:InitDelayOff() +return self:InitDelayOnOff(false) +end +end +function SPAWN:Spawn() +self:F({self.SpawnTemplatePrefix,self.SpawnIndex,self.AliveUnits}) +if self.SpawnInitAirbase then +return self:SpawnAtAirbase(self.SpawnInitAirbase,self.SpawnInitTakeoff,nil,self.SpawnInitTerminalType) +else +return self:SpawnWithIndex(self.SpawnIndex+1) +end +end +function SPAWN:ReSpawn(SpawnIndex) +self:F({self.SpawnTemplatePrefix,SpawnIndex}) +if not SpawnIndex then +SpawnIndex=1 +end +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +local WayPoints=SpawnGroup and SpawnGroup.WayPoints or nil +if SpawnGroup then +local SpawnDCSGroup=SpawnGroup:GetDCSObject() +if SpawnDCSGroup then +SpawnGroup:Destroy() +end +end +local SpawnGroup=self:SpawnWithIndex(SpawnIndex) +if SpawnGroup and WayPoints then +SpawnGroup:WayPointInitialize(WayPoints) +SpawnGroup:WayPointExecute(1,5) +end +if SpawnGroup.ReSpawnFunction then +SpawnGroup:ReSpawnFunction() +end +SpawnGroup:ResetEvents() +return SpawnGroup +end +function SPAWN:SetSpawnIndex(SpawnIndex) +self.SpawnIndex=SpawnIndex or 0 +end +function SPAWN:SpawnWithIndex(SpawnIndex,NoBirth) +self:F2({SpawnTemplatePrefix=self.SpawnTemplatePrefix,SpawnIndex=SpawnIndex,AliveUnits=self.AliveUnits,SpawnMaxGroups=self.SpawnMaxGroups}) +if self:_GetSpawnIndex(SpawnIndex)then +if self.SpawnGroups[self.SpawnIndex].Visible then +self.SpawnGroups[self.SpawnIndex].Group:Activate() +else +local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate +self:T(SpawnTemplate.name) +if SpawnTemplate then +local PointVec3=POINT_VEC3:New(SpawnTemplate.route.points[1].x,SpawnTemplate.route.points[1].alt,SpawnTemplate.route.points[1].y) +self:T({"Current point of ",self.SpawnTemplatePrefix,PointVec3}) +if self.SpawnRandomizePosition then +local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnRandomizePositionOuterRadius,self.SpawnRandomizePositionInnerRadius) +local CurrentX=SpawnTemplate.units[1].x +local CurrentY=SpawnTemplate.units[1].y +SpawnTemplate.x=RandomVec2.x +SpawnTemplate.y=RandomVec2.y +for UnitID=1,#SpawnTemplate.units do +SpawnTemplate.units[UnitID].x=SpawnTemplate.units[UnitID].x+(RandomVec2.x-CurrentX) +SpawnTemplate.units[UnitID].y=SpawnTemplate.units[UnitID].y+(RandomVec2.y-CurrentY) +self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) +end +end +if self.SpawnRandomizeUnits then +for UnitID=1,#SpawnTemplate.units do +local RandomVec2=PointVec3:GetRandomVec2InRadius(self.SpawnOuterRadius,self.SpawnInnerRadius) +SpawnTemplate.units[UnitID].x=RandomVec2.x +SpawnTemplate.units[UnitID].y=RandomVec2.y +self:T('SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) +end +end +local function _Heading(courseDeg) +local h +if courseDeg<=180 then +h=math.rad(courseDeg) +else +h=-math.rad(360-courseDeg) +end +return h +end +local Rad180=math.rad(180) +local function _HeadingRad(courseRad) +if courseRad<=Rad180 then +return courseRad +else +return-((2*Rad180)-courseRad) +end +end +local function _RandomInRange(min,max) +if min and max then +return min+(math.random()*(max-min)) +else +return min +end +end +if self.SpawnInitGroupHeadingMin and#SpawnTemplate.units>0 then +local pivotX=SpawnTemplate.units[1].x +local pivotY=SpawnTemplate.units[1].y +local headingRad=math.rad(_RandomInRange(self.SpawnInitGroupHeadingMin or 0,self.SpawnInitGroupHeadingMax)) +local cosHeading=math.cos(headingRad) +local sinHeading=math.sin(headingRad) +local unitVarRad=math.rad(self.SpawnInitGroupUnitVar or 0) +for UnitID=1,#SpawnTemplate.units do +if 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 +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.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 +SpawnTemplate.units[UnitID].onboard_num=string.format("%03d",self.SpawnInitModex+(UnitID-1)) +end +end +if self.SpawnInitRadio then +SpawnTemplate.communication=self.SpawnInitRadio +end +if self.SpawnInitFreq then +SpawnTemplate.frequency=self.SpawnInitFreq +end +if self.SpawnInitModu then +SpawnTemplate.modulation=self.SpawnInitModu +end +SpawnTemplate.CategoryID=self.SpawnInitCategory or SpawnTemplate.CategoryID +SpawnTemplate.CountryID=self.SpawnInitCountry or SpawnTemplate.CountryID +SpawnTemplate.CoalitionID=self.SpawnInitCoalition or SpawnTemplate.CoalitionID +end +if not NoBirth then +self:HandleEvent(EVENTS.Birth,self._OnBirth) +end +self:HandleEvent(EVENTS.Dead,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self._OnDeadOrCrash) +self:HandleEvent(EVENTS.RemoveUnit,self._OnDeadOrCrash) +if self.Repeat then +self:HandleEvent(EVENTS.Takeoff,self._OnTakeOff) +self:HandleEvent(EVENTS.Land,self._OnLand) +end +if self.RepeatOnEngineShutDown then +self:HandleEvent(EVENTS.EngineShutdown,self._OnEngineShutDown) +end +self.SpawnGroups[self.SpawnIndex].Group=_DATABASE:Spawn(SpawnTemplate) +local SpawnGroup=self.SpawnGroups[self.SpawnIndex].Group +if SpawnGroup then +SpawnGroup:SetAIOnOff(self.AIOnOff) +end +self:T3(SpawnTemplate.name) +if self.SpawnFunctionHook then +self.SpawnHookScheduler:Schedule(nil,self.SpawnFunctionHook,{self.SpawnGroups[self.SpawnIndex].Group,unpack(self.SpawnFunctionArguments)},0.1) +end +end +self.SpawnGroups[self.SpawnIndex].Spawned=true +return self.SpawnGroups[self.SpawnIndex].Group +else +end +return nil +end +function SPAWN:SpawnScheduled(SpawnTime,SpawnTimeVariation) +self:F({SpawnTime,SpawnTimeVariation}) +if SpawnTime~=nil and SpawnTimeVariation~=nil then +local InitialDelay=0 +if self.DelayOnOff==true then +InitialDelay=math.random(SpawnTime-SpawnTime*SpawnTimeVariation,SpawnTime+SpawnTime*SpawnTimeVariation) +end +self.SpawnScheduler=SCHEDULER:New(self,self._Scheduler,{},InitialDelay,SpawnTime,SpawnTimeVariation) +end +return self +end +function SPAWN:SpawnScheduleStart() +self:F({self.SpawnTemplatePrefix}) +self.SpawnScheduler:Start() +return self +end +function SPAWN:SpawnScheduleStop() +self:F({self.SpawnTemplatePrefix}) +self.SpawnScheduler:Stop() +return self +end +function SPAWN:OnSpawnGroup(SpawnCallBackFunction,...) +self:F("OnSpawnGroup") +self.SpawnFunctionHook=SpawnCallBackFunction +self.SpawnFunctionArguments={} +if arg then +self.SpawnFunctionArguments=arg +end +return self +end +function SPAWN:SpawnAtAirbase(SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType,EmergencyAirSpawn,Parkingdata) +self:F({self.SpawnTemplatePrefix,SpawnAirbase,Takeoff,TakeoffAltitude,TerminalType}) +local PointVec3=SpawnAirbase:GetCoordinate() +self:T2(PointVec3) +Takeoff=Takeoff or SPAWN.Takeoff.Hot +if EmergencyAirSpawn==nil then +EmergencyAirSpawn=true +end +self:F({SpawnIndex=self.SpawnIndex}) +if self:_GetSpawnIndex(self.SpawnIndex+1)then +local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate +self:F({SpawnTemplate=SpawnTemplate}) +if SpawnTemplate then +local GroupAlive=self:GetGroupFromIndex(self.SpawnIndex) +self:F({GroupAlive=GroupAlive}) +self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase}) +local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) +local TemplateUnit=TemplateGroup:GetUnit(1) +local group=TemplateGroup +local istransport=group:HasAttribute("Transports")and group:HasAttribute("Planes") +local isawacs=group:HasAttribute("AWACS") +local isfighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) +local isbomber=group:HasAttribute("Strategic bombers") +local istanker=group:HasAttribute("Tankers") +local ishelo=TemplateUnit:HasAttribute("Helicopters") +local nunits=#SpawnTemplate.units +local SpawnPoint=SpawnTemplate.route.points[1] +SpawnPoint.linkUnit=nil +SpawnPoint.helipadId=nil +SpawnPoint.airdromeId=nil +local AirbaseID=SpawnAirbase:GetID() +local AirbaseCategory=SpawnAirbase:GetAirbaseCategory() +self:F({AirbaseCategory=AirbaseCategory}) +if AirbaseCategory==Airbase.Category.SHIP then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.HELIPAD then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +SpawnPoint.airdromeId=AirbaseID +end +SpawnPoint.alt=0 +SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local spawnonground=not(Takeoff==SPAWN.Takeoff.Air) +self:T({spawnonground=spawnonground,TOtype=Takeoff,TOair=Takeoff==SPAWN.Takeoff.Air}) +local spawnonship=false +local spawnonfarp=false +local spawnonrunway=false +local spawnonairport=false +if spawnonground then +if AirbaseCategory==Airbase.Category.SHIP then +spawnonship=true +elseif AirbaseCategory==Airbase.Category.HELIPAD then +spawnonfarp=true +elseif AirbaseCategory==Airbase.Category.AIRDROME then +spawnonairport=true +end +spawnonrunway=Takeoff==SPAWN.Takeoff.Runway +end +local parkingspots={} +local parkingindex={} +local spots +if spawnonground and not SpawnTemplate.parked then +local nfree=0 +local termtype=TerminalType +if spawnonrunway then +if spawnonship then +if ishelo then +termtype=AIRBASE.TerminalType.HelicopterUsable +else +termtype=AIRBASE.TerminalType.OpenMedOrBig +end +else +termtype=AIRBASE.TerminalType.Runway +end +end +local scanradius=50 +local scanunits=true +local scanstatics=true +local scanscenery=false +local verysafe=false +if spawnonship or spawnonfarp or spawnonrunway then +self:T(string.format("Group %s is spawned on farp/ship/runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true) +spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true) +else +if ishelo then +if termtype==nil then +self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterOnly)) +spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata) +nfree=#spots +if nfree=1 then +for i=1,nunits do +table.insert(parkingspots,spots[1].Coordinate) +table.insert(parkingindex,spots[1].TerminalID) +end +PointVec3=spots[1].Coordinate +else +_notenough=true +end +elseif spawnonairport then +if nfree>=nunits then +for i=1,nunits do +table.insert(parkingspots,spots[i].Coordinate) +table.insert(parkingindex,spots[i].TerminalID) +end +else +_notenough=true +end +end +if _notenough then +if EmergencyAirSpawn and not self.SpawnUnControlled then +self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +spawnonground=false +spawnonship=false +spawnonfarp=false +spawnonrunway=false +SpawnPoint.type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] +PointVec3.x=PointVec3.x+math.random(-500,500) +PointVec3.z=PointVec3.z+math.random(-500,500) +if ishelo then +PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) +else +PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) +end +Takeoff=GROUP.Takeoff.Air +else +self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +return nil +end +end +else +if TakeoffAltitude then +PointVec3.y=TakeoffAltitude +else +if ishelo then +PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) +else +PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) +end +end +end +if not SpawnTemplate.parked then +SpawnTemplate.parked=true +for UnitID=1,nunits do +self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=PointVec3.x+(SX-BX) +local TY=PointVec3.z+(SY-BY) +if spawnonground then +if spawnonship or spawnonfarp or spawnonrunway then +self:T(string.format("Group %s spawning at farp, ship or runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +SpawnTemplate.units[UnitID].x=PointVec3.x +SpawnTemplate.units[UnitID].y=PointVec3.z +SpawnTemplate.units[UnitID].alt=PointVec3.y +else +self:T(string.format("Group %s spawning at airbase %s on parking spot id %d",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),parkingindex[UnitID])) +SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x +SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z +SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y +end +else +self:T(string.format("Group %s spawning in air at %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +SpawnTemplate.units[UnitID].alt=PointVec3.y +end +UnitTemplate.parking=nil +UnitTemplate.parking_id=nil +if parkingindex[UnitID]then +UnitTemplate.parking=parkingindex[UnitID] +end +self:T(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking))) +self:T(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking_id))) +self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) +end +end +SpawnPoint.x=PointVec3.x +SpawnPoint.y=PointVec3.z +SpawnPoint.alt=PointVec3.y +SpawnTemplate.x=PointVec3.x +SpawnTemplate.y=PointVec3.z +SpawnTemplate.uncontrolled=self.SpawnUnControlled +local GroupSpawned=self:SpawnWithIndex(self.SpawnIndex) +if Takeoff==GROUP.Takeoff.Air then +for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do +SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5) +end +end +if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then +SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0) +end +return GroupSpawned +end +end +return nil +end +function SPAWN:SpawnAtParkingSpot(Airbase,Spots,Takeoff) +self:F({Airbase=Airbase,Spots=Spots,Takeoff=Takeoff}) +if type(Spots)~="table"then +Spots={Spots} +end +local group=GROUP:FindByName(self.SpawnTemplatePrefix) +local nunits=self.SpawnGrouping or#group:GetUnits() +if nunits then +if#Spots=nunits then +return self:SpawnAtAirbase(Airbase,Takeoff,nil,nil,nil,Parkingdata) +else +self:E("ERROR: Could not find enough free parking spots!") +end +else +self:E("ERROR: Could not get number of units in group!") +end +return nil +end +function SPAWN:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex) +self:F({SpawnIndex=SpawnIndex,SpawnMaxGroups=self.SpawnMaxGroups}) +local PointVec3=SpawnAirbase:GetCoordinate() +self:T2(PointVec3) +local Takeoff=SPAWN.Takeoff.Cold +local SpawnTemplate=self.SpawnGroups[SpawnIndex].SpawnTemplate +if SpawnTemplate then +local GroupAlive=self:GetGroupFromIndex(SpawnIndex) +self:T({"Current point of ",self.SpawnTemplatePrefix,SpawnAirbase}) +local TemplateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) +local TemplateUnit=TemplateGroup:GetUnit(1) +local ishelo=TemplateUnit:HasAttribute("Helicopters") +local isbomber=TemplateUnit:HasAttribute("Bombers") +local istransport=TemplateUnit:HasAttribute("Transports") +local isfighter=TemplateUnit:HasAttribute("Battleplanes") +local nunits=#SpawnTemplate.units +local SpawnPoint=SpawnTemplate.route.points[1] +SpawnPoint.linkUnit=nil +SpawnPoint.helipadId=nil +SpawnPoint.airdromeId=nil +local AirbaseID=SpawnAirbase:GetID() +local AirbaseCategory=SpawnAirbase:GetAirbaseCategory() +self:F({AirbaseCategory=AirbaseCategory}) +if AirbaseCategory==Airbase.Category.SHIP then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.HELIPAD then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +SpawnPoint.airdromeId=AirbaseID +end +SpawnPoint.alt=0 +SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local spawnonground=not(Takeoff==SPAWN.Takeoff.Air) +self:T({spawnonground=spawnonground,TOtype=Takeoff,TOair=Takeoff==SPAWN.Takeoff.Air}) +local spawnonship=false +local spawnonfarp=false +local spawnonrunway=false +local spawnonairport=false +if spawnonground then +if AirbaseCategory==Airbase.Category.SHIP then +spawnonship=true +elseif AirbaseCategory==Airbase.Category.HELIPAD then +spawnonfarp=true +elseif AirbaseCategory==Airbase.Category.AIRDROME then +spawnonairport=true +end +spawnonrunway=Takeoff==SPAWN.Takeoff.Runway +end +local parkingspots={} +local parkingindex={} +local spots +if spawnonground and not SpawnTemplate.parked then +local nfree=0 +local termtype=TerminalType +local scanradius=50 +local scanunits=true +local scanstatics=true +local scanscenery=false +local verysafe=false +if spawnonship or spawnonfarp or spawnonrunway then +self:T(string.format("Group %s is spawned on farp/ship/runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype,true) +spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype,true) +else +if ishelo then +if termtype==nil then +self:T(string.format("Helo group %s is at %s using terminal type %d.",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),AIRBASE.TerminalType.HelicopterOnly)) +spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup,AIRBASE.TerminalType.HelicopterOnly,scanradius,scanunits,scanstatics,scanscenery,verysafe,nunits,Parkingdata) +nfree=#spots +if nfree=1 then +for i=1,nunits do +table.insert(parkingspots,spots[1].Coordinate) +table.insert(parkingindex,spots[1].TerminalID) +end +PointVec3=spots[1].Coordinate +else +_notenough=true +end +elseif spawnonairport then +if nfree>=nunits then +for i=1,nunits do +table.insert(parkingspots,spots[i].Coordinate) +table.insert(parkingindex,spots[i].TerminalID) +end +else +_notenough=true +end +end +if _notenough then +if not self.SpawnUnControlled then +else +self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +return nil +end +end +else +end +if not SpawnTemplate.parked then +SpawnTemplate.parked=true +for UnitID=1,nunits do +self:F('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=PointVec3.x+(SX-BX) +local TY=PointVec3.z+(SY-BY) +if spawnonground then +if spawnonship or spawnonfarp or spawnonrunway then +self:T(string.format("Group %s spawning at farp, ship or runway %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +SpawnTemplate.units[UnitID].x=PointVec3.x +SpawnTemplate.units[UnitID].y=PointVec3.z +SpawnTemplate.units[UnitID].alt=PointVec3.y +else +self:T(string.format("Group %s spawning at airbase %s on parking spot id %d",self.SpawnTemplatePrefix,SpawnAirbase:GetName(),parkingindex[UnitID])) +SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x +SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z +SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y +end +else +self:T(string.format("Group %s spawning in air at %s.",self.SpawnTemplatePrefix,SpawnAirbase:GetName())) +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +SpawnTemplate.units[UnitID].alt=PointVec3.y +end +UnitTemplate.parking=nil +UnitTemplate.parking_id=nil +if parkingindex[UnitID]then +UnitTemplate.parking=parkingindex[UnitID] +end +self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking))) +self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix,UnitID,tostring(UnitTemplate.parking_id))) +self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) +end +end +SpawnPoint.x=PointVec3.x +SpawnPoint.y=PointVec3.z +SpawnPoint.alt=PointVec3.y +SpawnTemplate.x=PointVec3.x +SpawnTemplate.y=PointVec3.z +SpawnTemplate.uncontrolled=true +local GroupSpawned=self:SpawnWithIndex(SpawnIndex,true) +if Takeoff==GROUP.Takeoff.Air then +for UnitID,UnitSpawned in pairs(GroupSpawned:GetUnits())do +SCHEDULER:New(nil,BASE.CreateEventTakeoff,{GroupSpawned,timer.getTime(),UnitSpawned:GetDCSObject()},5) +end +end +if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then +SCHEDULER:New(nil,AIRBASE.CheckOnRunWay,{SpawnAirbase,GroupSpawned,75,true},1.0) +end +end +end +function SPAWN:ParkAtAirbase(SpawnAirbase,TerminalType,Parkingdata) +self:F({self.SpawnTemplatePrefix,SpawnAirbase,TerminalType}) +self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,1) +for SpawnIndex=2,self.SpawnMaxGroups do +self:ParkAircraft(SpawnAirbase,TerminalType,Parkingdata,SpawnIndex) +end +self:SetSpawnIndex(0) +return nil +end +function SPAWN:SpawnFromVec3(Vec3,SpawnIndex) +self:F({self.SpawnTemplatePrefix,Vec3,SpawnIndex}) +local PointVec3=POINT_VEC3:NewFromVec3(Vec3) +self:T2(PointVec3) +if SpawnIndex then +else +SpawnIndex=self.SpawnIndex+1 +end +if self:_GetSpawnIndex(SpawnIndex)then +local SpawnTemplate=self.SpawnGroups[self.SpawnIndex].SpawnTemplate +if SpawnTemplate then +self:T({"Current point of ",self.SpawnTemplatePrefix,Vec3}) +local TemplateHeight=SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil +SpawnTemplate.route=SpawnTemplate.route or{} +SpawnTemplate.route.points=SpawnTemplate.route.points or{} +SpawnTemplate.route.points[1]=SpawnTemplate.route.points[1]or{} +SpawnTemplate.route.points[1].x=SpawnTemplate.route.points[1].x or 0 +SpawnTemplate.route.points[1].y=SpawnTemplate.route.points[1].y or 0 +for UnitID=1,#SpawnTemplate.units do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x or 0 +local SY=UnitTemplate.y or 0 +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=Vec3.x+(SX-BX) +local TY=Vec3.z+(SY-BY) +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +if SpawnTemplate.CategoryID~=Group.Category.SHIP then +SpawnTemplate.units[UnitID].alt=Vec3.y or TemplateHeight +end +self:T('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) +end +SpawnTemplate.route.points[1].x=Vec3.x +SpawnTemplate.route.points[1].y=Vec3.z +if SpawnTemplate.CategoryID~=Group.Category.SHIP then +SpawnTemplate.route.points[1].alt=Vec3.y or TemplateHeight +end +SpawnTemplate.x=Vec3.x +SpawnTemplate.y=Vec3.z +SpawnTemplate.alt=Vec3.y or TemplateHeight +return self:SpawnWithIndex(self.SpawnIndex) +end +end +return nil +end +function SPAWN:SpawnFromCoordinate(Coordinate,SpawnIndex) +self:F({self.SpawnTemplatePrefix,SpawnIndex}) +return self:SpawnFromVec3(Coordinate:GetVec3(),SpawnIndex) +end +function SPAWN:SpawnFromPointVec3(PointVec3,SpawnIndex) +self:F({self.SpawnTemplatePrefix,SpawnIndex}) +return self:SpawnFromVec3(PointVec3:GetVec3(),SpawnIndex) +end +function SPAWN:SpawnFromVec2(Vec2,MinHeight,MaxHeight,SpawnIndex) +self:F({self.SpawnTemplatePrefix,self.SpawnIndex,Vec2,MinHeight,MaxHeight,SpawnIndex}) +local Height=nil +if MinHeight and MaxHeight then +Height=math.random(MinHeight,MaxHeight) +end +return self:SpawnFromVec3({x=Vec2.x,y=Height,z=Vec2.y},SpawnIndex) +end +function SPAWN:SpawnFromPointVec2(PointVec2,MinHeight,MaxHeight,SpawnIndex) +self:F({self.SpawnTemplatePrefix,self.SpawnIndex}) +return self:SpawnFromVec2(PointVec2:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +function SPAWN:SpawnFromUnit(HostUnit,MinHeight,MaxHeight,SpawnIndex) +self:F({self.SpawnTemplatePrefix,HostUnit,MinHeight,MaxHeight,SpawnIndex}) +if HostUnit and HostUnit:IsAlive()~=nil then +return self:SpawnFromVec2(HostUnit:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +return nil +end +function SPAWN:SpawnFromStatic(HostStatic,MinHeight,MaxHeight,SpawnIndex) +self:F({self.SpawnTemplatePrefix,HostStatic,MinHeight,MaxHeight,SpawnIndex}) +if HostStatic and HostStatic:IsAlive()then +return self:SpawnFromVec2(HostStatic:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +return nil +end +function SPAWN:SpawnInZone(Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex) +self:F({self.SpawnTemplatePrefix,Zone,RandomizeGroup,MinHeight,MaxHeight,SpawnIndex}) +if Zone then +if RandomizeGroup then +return self:SpawnFromVec2(Zone:GetRandomVec2(),MinHeight,MaxHeight,SpawnIndex) +else +return self:SpawnFromVec2(Zone:GetVec2(),MinHeight,MaxHeight,SpawnIndex) +end +end +return nil +end +function SPAWN:InitUnControlled(UnControlled) +self:F2({self.SpawnTemplatePrefix,UnControlled}) +self.SpawnUnControlled=(UnControlled==true)and true or nil +for SpawnGroupID=1,self.SpawnMaxGroups do +self.SpawnGroups[SpawnGroupID].UnControlled=self.SpawnUnControlled +end +return self +end +function SPAWN:GetCoordinate() +local LateGroup=GROUP:FindByName(self.SpawnTemplatePrefix) +if LateGroup then +return LateGroup:GetCoordinate() +end +return nil +end +function SPAWN:SpawnGroupName(SpawnIndex) +self:F({self.SpawnTemplatePrefix,SpawnIndex}) +local SpawnPrefix=self.SpawnTemplatePrefix +if self.SpawnAliasPrefix then +SpawnPrefix=self.SpawnAliasPrefix +end +if SpawnIndex then +local SpawnName=string.format('%s#%03d',SpawnPrefix,SpawnIndex) +self:T(SpawnName) +return SpawnName +else +self:T(SpawnPrefix) +return SpawnPrefix +end +end +function SPAWN:GetFirstAliveGroup() +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) +for SpawnIndex=1,self.SpawnCount do +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +if SpawnGroup and SpawnGroup:IsAlive()then +return SpawnGroup,SpawnIndex +end +end +return nil,nil +end +function SPAWN:GetNextAliveGroup(SpawnIndexStart) +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndexStart}) +SpawnIndexStart=SpawnIndexStart+1 +for SpawnIndex=SpawnIndexStart,self.SpawnCount do +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +if SpawnGroup and SpawnGroup:IsAlive()then +return SpawnGroup,SpawnIndex +end +end +return nil,nil +end +function SPAWN:GetLastAliveGroup() +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) +for SpawnIndex=self.SpawnCount,1,-1 do +local SpawnGroup=self:GetGroupFromIndex(SpawnIndex) +if SpawnGroup and SpawnGroup:IsAlive()then +self.SpawnIndex=SpawnIndex +return SpawnGroup +end +end +self.SpawnIndex=nil +return nil +end +function SPAWN:GetGroupFromIndex(SpawnIndex) +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex}) +if not SpawnIndex then +SpawnIndex=1 +end +if self.SpawnGroups and self.SpawnGroups[SpawnIndex]then +local SpawnGroup=self.SpawnGroups[SpawnIndex].Group +return SpawnGroup +else +return nil +end +end +function SPAWN:_GetPrefixFromGroup(SpawnGroup) +self:F3({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnGroup}) +local GroupName=SpawnGroup:GetName() +if GroupName then +local SpawnPrefix=string.match(GroupName,".*#") +if SpawnPrefix then +SpawnPrefix=SpawnPrefix:sub(1,-2) +end +return SpawnPrefix +end +return nil +end +function SPAWN:GetSpawnIndexFromGroup(SpawnGroup) +self:F2({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnGroup}) +local IndexString=string.match(SpawnGroup:GetName(),"#(%d*)$"):sub(2) +local Index=tonumber(IndexString) +self:T3(IndexString,Index) +return Index +end +function SPAWN:_GetLastIndex() +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) +return self.SpawnMaxGroups +end +function SPAWN:_InitializeSpawnGroups(SpawnIndex) +self:F3({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnIndex}) +if not self.SpawnGroups[SpawnIndex]then +self.SpawnGroups[SpawnIndex]={} +self.SpawnGroups[SpawnIndex].Visible=false +self.SpawnGroups[SpawnIndex].Spawned=false +self.SpawnGroups[SpawnIndex].UnControlled=false +self.SpawnGroups[SpawnIndex].SpawnTime=0 +self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix=self.SpawnTemplatePrefix +self.SpawnGroups[SpawnIndex].SpawnTemplate=self:_Prepare(self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix,SpawnIndex) +end +self:_RandomizeTemplate(SpawnIndex) +self:_RandomizeRoute(SpawnIndex) +return self.SpawnGroups[SpawnIndex] +end +function SPAWN:_GetGroupCategoryID(SpawnPrefix) +local TemplateGroup=Group.getByName(SpawnPrefix) +if TemplateGroup then +return TemplateGroup:getCategory() +else +return nil +end +end +function SPAWN:_GetGroupCoalitionID(SpawnPrefix) +local TemplateGroup=Group.getByName(SpawnPrefix) +if TemplateGroup then +return TemplateGroup:getCoalition() +else +return nil +end +end +function SPAWN:_GetGroupCountryID(SpawnPrefix) +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnPrefix}) +local TemplateGroup=Group.getByName(SpawnPrefix) +if TemplateGroup then +local TemplateUnits=TemplateGroup:getUnits() +return TemplateUnits[1]:getCountry() +else +return nil +end +end +function SPAWN:_GetTemplate(SpawnTemplatePrefix) +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix,SpawnTemplatePrefix}) +local SpawnTemplate=nil +local Template=_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template +self:F({Template=Template}) +SpawnTemplate=UTILS.DeepCopy(_DATABASE.Templates.Groups[SpawnTemplatePrefix].Template) +if SpawnTemplate==nil then +error('No Template returned for SpawnTemplatePrefix = '..SpawnTemplatePrefix) +end +self:T3({SpawnTemplate}) +return SpawnTemplate +end +function SPAWN:_Prepare(SpawnTemplatePrefix,SpawnIndex) +self:F({self.SpawnTemplatePrefix,self.SpawnAliasPrefix}) +local SpawnTemplate +if self.TweakedTemplate~=nil and self.TweakedTemplate==true then +BASE:I("WARNING: You are using a tweaked template.") +SpawnTemplate=self.SpawnTemplate +else +SpawnTemplate=self:_GetTemplate(SpawnTemplatePrefix) +SpawnTemplate.name=self:SpawnGroupName(SpawnIndex) +end +SpawnTemplate.groupId=nil +SpawnTemplate.lateActivation=self.LateActivated or false +if SpawnTemplate.CategoryID==Group.Category.GROUND then +self:T3("For ground units, visible needs to be false...") +SpawnTemplate.visible=false +end +if self.SpawnGrouping then +local UnitAmount=#SpawnTemplate.units +self:F({UnitAmount=UnitAmount,SpawnGrouping=self.SpawnGrouping}) +if UnitAmount>self.SpawnGrouping then +for UnitID=self.SpawnGrouping+1,UnitAmount do +SpawnTemplate.units[UnitID]=nil +end +else +if UnitAmount0 then +self.Tstop=timer.getTime()+Delay +else +if self.tid then +self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!",self.ncalls)) +timer.removeFunction(self.tid) +self.isrunning=false +end +end +return self +end +function TIMER:SetMaxFunctionCalls(Nmax) +self.ncallsMax=Nmax +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.LaseScheduler=SCHEDULER:New(self) +self:SetEventPriority(5) +self.Lasing=false +return self +end +function SPOT:onafterLaseOn(From,Event,To,Target,LaserCode,Duration) +self:F({"LaseOn",Target,LaserCode,Duration}) +local function StopLase(self) +self:LaseOff() +end +self.Target=Target +self.LaserCode=LaserCode +self.Lasing=true +local RecceDcsUnit=self.Recce:GetDCSObject() +self.SpotIR=Spot.createInfraRed(RecceDcsUnit,{x=0,y=2,z=0},Target:GetPointVec3():AddY(1):GetVec3()) +self.SpotLaser=Spot.createLaser(RecceDcsUnit,{x=0,y=2,z=0},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) +end +function SPOT:onafterLaseOnCoordinate(From,Event,To,Coordinate,LaserCode,Duration) +self:F({"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) +end +function SPOT:OnEventDead(EventData) +self:F({Dead=EventData.IniDCSUnitName,Target=self.Target}) +if self.Target then +if EventData.IniDCSUnitName==self.Target:GetName()then +self:F({"Target dead ",self.Target:GetName()}) +self:Destroyed() +self:LaseOff() +end +end +end +function SPOT:onafterLasing(From,Event,To) +if self.Target and self.Target:IsAlive()then +self.SpotIR:setPoint(self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):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)/100,y=self.TargetCoord.y+math.random(-100,100)/100,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.25) +else +self:F({"Target is not alive",self.Target:IsAlive()}) +end +end +function SPOT:onafterLaseOff(From,Event,To) +self:F({"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 +end +ASTAR={ +ClassName="ASTAR", +Debug=nil, +lid=nil, +nodes={}, +counter=1, +Nnodes=0, +ncost=0, +ncostcache=0, +nvalid=0, +nvalidcache=0, +} +ASTAR.INF=1/0 +ASTAR.version="0.4.0" +function ASTAR:New() +local self=BASE:Inherit(self,BASE:New()) +self.lid="ASTAR | " +return self +end +function ASTAR:SetStartCoordinate(Coordinate) +self.startCoord=Coordinate +return self +end +function ASTAR:SetEndCoordinate(Coordinate) +self.endCoord=Coordinate +return self +end +function ASTAR:GetNodeFromCoordinate(Coordinate) +local node={} +node.coordinate=Coordinate +node.surfacetype=Coordinate:GetSurfaceType() +node.id=self.counter +node.valid={} +node.cost={} +self.counter=self.counter+1 +return node +end +function ASTAR:AddNode(Node) +self.nodes[Node.id]=Node +self.Nnodes=self.Nnodes+1 +return self +end +function ASTAR:AddNodeFromCoordinate(Coordinate) +local node=self:GetNodeFromCoordinate(Coordinate) +self:AddNode(node) +return node +end +function ASTAR:CheckValidSurfaceType(Node,SurfaceTypes) +if SurfaceTypes then +if type(SurfaceTypes)~="table"then +SurfaceTypes={SurfaceTypes} +end +for _,surface in pairs(SurfaceTypes)do +if surface==Node.surfacetype then +return true +end +end +return false +else +return true +end +end +function ASTAR:SetValidNeighbourFunction(NeighbourFunction,...) +self.ValidNeighbourFunc=NeighbourFunction +self.ValidNeighbourArg={} +if arg then +self.ValidNeighbourArg=arg +end +return self +end +function ASTAR:SetValidNeighbourLoS(CorridorWidth) +self:SetValidNeighbourFunction(ASTAR.LoS,CorridorWidth) +return self +end +function ASTAR:SetValidNeighbourDistance(MaxDistance) +self:SetValidNeighbourFunction(ASTAR.DistMax,MaxDistance) +return self +end +function ASTAR:SetValidNeighbourRoad(MaxDistance) +self:SetValidNeighbourFunction(ASTAR.Road,MaxDistance) +return self +end +function ASTAR:SetCostFunction(CostFunction,...) +self.CostFunc=CostFunction +self.CostArg={} +if arg then +self.CostArg=arg +end +return self +end +function ASTAR:SetCostDist2D() +self:SetCostFunction(ASTAR.Dist2D) +return self +end +function ASTAR:SetCostDist3D() +self:SetCostFunction(ASTAR.Dist3D) +return self +end +function ASTAR:SetCostRoad() +self:SetCostFunction(ASTAR) +return self +end +function ASTAR:CreateGrid(ValidSurfaceTypes,BoxHY,SpaceX,deltaX,deltaY,MarkGrid) +local Dz=SpaceX or 10000 +local Dx=BoxHY and BoxHY/2 or 20000 +local dz=deltaX or 2000 +local dx=deltaY or dz +local angle=self.startCoord:HeadingTo(self.endCoord) +local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz +local co=COORDINATE:New(0,0,0) +local do1=co:Get2DDistance(self.startCoord) +local ho1=co:HeadingTo(self.startCoord) +local xmin=-Dx +local zmin=-Dz +local nz=dist/dz+1 +local nx=2*Dx/dx+1 +local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes",nx,nz,nx*nz) +self:T(self.lid..text) +for i=1,nx do +local x=xmin+dx*(i-1) +for j=1,nz do +local z=zmin+dz*(j-1) +local vec3=UTILS.Rotate2D({x=x,y=0,z=z},angle) +local c=COORDINATE:New(vec3.z,vec3.y,vec3.x):Translate(do1,ho1,true) +local node=self:GetNodeFromCoordinate(c) +if self:CheckValidSurfaceType(node,ValidSurfaceTypes)then +if MarkGrid then +c:MarkToAll(string.format("i=%d, j=%d surface=%d",i,j,node.surfacetype)) +end +self:AddNode(node) +end +end +end +local text=string.format("Done building grid!") +self:T2(self.lid..text) +return self +end +function ASTAR.LoS(nodeA,nodeB,corridor) +local offset=1 +local dx=corridor and corridor/2 or nil +local dy=dx +local cA=nodeA.coordinate:GetVec3() +local cB=nodeB.coordinate:GetVec3() +cA.y=offset +cB.y=offset +local los=land.isVisible(cA,cB) +if los and corridor then +local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate) +local Ap=UTILS.VecTranslate(cA,dx,heading+90) +local Bp=UTILS.VecTranslate(cB,dx,heading+90) +los=land.isVisible(Ap,Bp) +if los then +local Am=UTILS.VecTranslate(cA,dx,heading-90) +local Bm=UTILS.VecTranslate(cB,dx,heading-90) +los=land.isVisible(Am,Bm) +end +end +return los +end +function ASTAR.Road(nodeA,nodeB) +local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z) +if path then +return true +else +return false +end +end +function ASTAR.DistMax(nodeA,nodeB,distmax) +distmax=distmax or 2000 +local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate) +return dist<=distmax +end +function ASTAR.Dist2D(nodeA,nodeB) +local dist=nodeA.coordinate:Get2DDistance(nodeB) +return dist +end +function ASTAR.Dist3D(nodeA,nodeB) +local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate) +return dist +end +function ASTAR.DistRoad(nodeA,nodeB) +local path=land.findPathOnRoads("roads",nodeA.coordinate.x,nodeA.coordinate.z,nodeB.coordinate.x,nodeB.coordinate.z) +if path then +local dist=0 +for i=2,#path do +local b=path[i] +local a=path[i-1] +dist=dist+UTILS.VecDist2D(a,b) +end +return dist +end +return math.huge +end +function ASTAR:FindClosestNode(Coordinate) +local distMin=math.huge +local closeNode=nil +for _,_node in pairs(self.nodes)do +local node=_node +local dist=node.coordinate:Get2DDistance(Coordinate) +if dist1000 then +self:T(self.lid.."Adding start node to node grid!") +self:AddNode(node) +end +return self +end +function ASTAR:FindEndNode() +local node,dist=self:FindClosestNode(self.endCoord) +self.endNode=node +if dist>1000 then +self:T(self.lid.."Adding end node to node grid!") +self:AddNode(node) +end +return self +end +function ASTAR:GetPath(ExcludeStartNode,ExcludeEndNode) +self:FindStartNode() +self:FindEndNode() +local nodes=self.nodes +local start=self.startNode +local goal=self.endNode +local openset={} +local closedset={} +local came_from={} +local g_score={} +local f_score={} +openset[start.id]=true +local Nopen=1 +g_score[start.id]=0 +f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start,goal) +local T0=timer.getAbsTime() +local text=string.format("Starting A* pathfinding with %d Nodes",self.Nnodes) +self:T(self.lid..text) +local Tstart=UTILS.GetOSTime() +while Nopen>0 do +local current=self:_LowestFscore(openset,f_score) +if current.id==goal.id then +local path=self:_UnwindPath({},came_from,goal) +if not ExcludeEndNode then +table.insert(path,goal) +end +if ExcludeStartNode then +table.remove(path,1) +end +local Tstop=UTILS.GetOSTime() +local dT=nil +if Tstart and Tstop then +dT=Tstop-Tstart +end +local text=string.format("Found path with %d nodes (%d total)",#path,self.Nnodes) +if dT then +text=text..string.format(", OS Time %.6f sec",dT) +end +text=text..string.format(", Nvalid=%d [%d cached]",self.nvalid,self.nvalidcache) +text=text..string.format(", Ncost=%d [%d cached]",self.ncost,self.ncostcache) +self:T(self.lid..text) +return path +end +openset[current.id]=nil +Nopen=Nopen-1 +closedset[current.id]=true +local neighbors=self:_NeighbourNodes(current,nodes) +for _,neighbor in pairs(neighbors)do +if self:_NotIn(closedset,neighbor.id)then +local tentative_g_score=g_score[current.id]+self:_DistNodes(current,neighbor) +if self:_NotIn(openset,neighbor.id)or tentative_g_score0 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 +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 +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: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: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: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("bulding 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: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 +function POSITIONABLE:SetCargoBayWeightLimit(WeightLimit) +if WeightLimit then +self.__.CargoBayWeightLimit=WeightLimit +elseif self.__.CargoBayWeightLimit~=nil then +else +if self:IsAir()then +local Desc=self:GetDesc() +self:F({Desc=Desc}) +local Weights={ +["C-17A"]=35000, +["C-130"]=22000 +} +self.__.CargoBayWeightLimit=Weights[Desc.typeName]or(Desc.massMax-(Desc.massEmpty+Desc.fuelMassMax)) +elseif self:IsShip()then +local Desc=self:GetDesc() +self:F({Desc=Desc}) +local Weights={ +["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, +} +self.__.CargoBayWeightLimit=(Weights[Desc.typeName]or 50000) +else +local Desc=self:GetDesc() +local Weights={ +["AAV7"]=25, +["Bedford_MWD"]=8, +["Blitz_36-6700A"]=10, +["BMD-1"]=9, +["BMP-1"]=8, +["BMP-2"]=7, +["BMP-3"]=8, +["Boman"]=25, +["BTR-80"]=9, +["BTR-82A"]=9, +["BTR_D"]=12, +["Cobra"]=8, +["Land_Rover_101_FC"]=11, +["Land_Rover_109_S3"]=7, +["LAV-25"]=6, +["M-2 Bradley"]=6, +["M1043 HMMWV Armament"]=4, +["M1045 HMMWV TOW"]=4, +["M1126 Stryker ICV"]=9, +["M1134 Stryker ATGM"]=9, +["M2A1_halftrack"]=9, +["M-113"]=9, +["Marder"]=6, +["MCV-80"]=9, +["MLRS FDDM"]=4, +["MTLB"]=25, +["GAZ-66"]=8, +["GAZ-3307"]=12, +["GAZ-3308"]=14, +["Grad_FDDM"]=6, +["KAMAZ Truck"]=12, +["KrAZ6322"]=12, +["M 818"]=12, +["Tigr_233036"]=6, +["TPZ"]=10, +["UAZ-469"]=4, +["Ural-375"]=12, +["Ural-4320-31"]=14, +["Ural-4320 APA-5D"]=10, +["Ural-4320T"]=14, +["ZBD04A"]=7, +} +local CargoBayWeightLimit=(Weights[Desc.typeName]or 0)*95 +self.__.CargoBayWeightLimit=CargoBayWeightLimit +end +end +self:F({CargoBayWeightLimit=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: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:CommandActivateICLS(Channel,UnitID,Callsign,Delay) +local CommandActivateICLS={ +id="ActivateICLS", +params={ +["type"]=BEACON.Type.ICLS, +["channel"]=Channel, +["unitId"]=UnitID, +["callsign"]=Callsign, +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandActivateICLS,{self},Delay) +else +self:SetCommand(CommandActivateICLS) +end +return self +end +function CONTROLLABLE:CommandDeactivateBeacon(Delay) +local CommandDeactivateBeacon={id='DeactivateBeacon',params={}} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandActivateBeacon,{self},Delay) +else +self:SetCommand(CommandDeactivateBeacon) +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:CommandSetFrequency(Frequency,Modulation,Delay) +local CommandSetFrequency={ +id='SetFrequency', +params={ +frequency=Frequency*1000000, +modulation=Modulation or radio.modulation.AM, +} +} +if Delay and Delay>0 then +SCHEDULER:New(nil,self.CommandSetFrequency,{self,Frequency,Modulation},Delay) +else +self:SetCommand(CommandSetFrequency) +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: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, +attackQty=AttackQty, +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=Coord:GetVec2() +local P2=nil +if CoordRaceTrack then +Pattern=AI.Task.OrbitPattern.RACE_TRACK +P2=CoordRaceTrack:GetVec2() +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:TaskLandAtVec2(Vec2,Duration) +local DCSTask={ +id='Land', +params={ +point=Vec2, +durationFlag=Duration and true or false, +duration=Duration, +}, +} +return DCSTask +end +function CONTROLLABLE:TaskLandAtZone(Zone,Duration,RandomPoint) +local Point=RandomPoint and Zone:GetRandomVec2()or Zone:GetVec2() +local DCSTask=CONTROLLABLE.TaskLandAtVec2(self,Point,Duration) +return DCSTask +end +function CONTROLLABLE:TaskFollow(FollowControllable,Vec3,LastWaypointIndex) +self:F2({self.ControllableName,FollowControllable,Vec3,LastWaypointIndex}) +local LastWaypointIndexFlag=false +local lastWptIndexFlagChangedManually=false +if LastWaypointIndex then +LastWaypointIndexFlag=true +lastWptIndexFlagChangedManually=true +end +local DCSTask={ +id='Follow', +params={ +groupId=FollowControllable:GetID(), +pos=Vec3, +lastWptIndexFlag=LastWaypointIndexFlag, +lastWptIndex=LastWaypointIndex, +lastWptIndexFlagChangedManually=lastWptIndexFlagChangedManually, +} +} +self:T3({DCSTask}) +return DCSTask +end +function CONTROLLABLE:TaskEscort(FollowControllable,Vec3,LastWaypointIndex,EngagementDistance,TargetTypes) +local DCSTask +DCSTask={ +id='Escort', +params={ +groupId=FollowControllable:GetID(), +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=100, +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:EnRouteTaskEngageGroup(AttackGroup,Priority,WeaponType,WeaponExpend,AttackQty,Direction,Altitude,AttackQtyLimit) +local DCSTask={ +id='EngageControllable', +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) +local DCSTask={ +id='FAC_EngageControllable', +params={ +groupId=AttackGroup:GetID(), +weaponType=WeaponType or"Auto", +designation=Designation, +datalink=Datalink and Datalink or false, +priority=Priority or 0, +} +} +return DCSTask +end +function CONTROLLABLE:EnRouteTaskFAC(Radius,Priority) +local DCSTask={ +id='FAC', +params={ +radius=Radius, +priority=Priority +} +} +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 From=FromCoord:WaypointGround(120) +table.insert(Waypoints,1,From) +local TaskRoute=PatrolGroup:TaskFunction("CONTROLLABLE.PatrolRoute") +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +PatrolGroup:SetTaskWaypoint(Waypoint,TaskRoute) +PatrolGroup:Route(Waypoints) +end +end +function CONTROLLABLE:PatrolRouteRandom(Speed,Formation,ToWaypoint) +local PatrolGroup=self +if not self:IsInstanceOf("GROUP")then +PatrolGroup=self:GetGroup() +end +self:F({PatrolGroup=PatrolGroup:GetName()}) +if PatrolGroup:IsGround()or PatrolGroup:IsShip()then +local Waypoints=PatrolGroup:GetTemplateRoutePoints() +local FromCoord=PatrolGroup:GetCoordinate() +local FromWaypoint=1 +if ToWaypoint then +FromWaypoint=ToWaypoint +end +local 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={} +Route[#Route+1]=FromCoord:WaypointGround(Speed,Formation) +Route[#Route+1]=ToCoord:WaypointGround(Speed,Formation) +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 not type(ZoneList)=="table"then +ZoneList={ZoneList} +end +local PatrolGroup=self +if not self:IsInstanceOf("GROUP")then +PatrolGroup=self:GetGroup() +end +DelayMin=DelayMin or 1 +if not DelayMax or DelayMaxLengthDirect*10)or(LengthRoad/LengthOnRoad*100<5)) +self:T(string.format("Length on road = %.3f km",LengthOnRoad/1000)) +self:T(string.format("Length directly = %.3f km",LengthDirect/1000)) +self:T(string.format("Length fraction = %.3f km",LengthOnRoad/LengthDirect)) +self:T(string.format("Length only road = %.3f km",LengthRoad/1000)) +self:T(string.format("Length off road = %.3f km",LengthOffRoad/1000)) +self:T(string.format("Percent on road = %.1f",LengthRoad/LengthOnRoad*100)) +end +local route={} +local canroad=false +if GotPath and LengthRoad and LengthDirect>2000 then +if LongRoad and Shortcut then +table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) +else +table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,PathOnRoad[2]:WaypointGround(Speed,"On Road")) +table.insert(route,PathOnRoad[#PathOnRoad-1]:WaypointGround(Speed,"On Road")) +local dist=ToCoordinate:Get2DDistance(PathOnRoad[#PathOnRoad-1]) +if dist>10 then +table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation)) +table.insert(route,ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5,OffRoadFormation)) +end +end +canroad=true +else +table.insert(route,FromCoordinate:WaypointGround(Speed,OffRoadFormation)) +table.insert(route,ToCoordinate:WaypointGround(Speed,OffRoadFormation)) +end +if WaypointFunction then +local N=#route +for n,waypoint in pairs(route)do +waypoint.task={} +waypoint.task.id="ComboTask" +waypoint.task.params={} +waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))} +end +end +return route,canroad +end +function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate,Speed,WaypointFunction,WaypointFunctionArguments) +self:F2({ToCoordinate=ToCoordinate,Speed=Speed}) +Speed=Speed or 20 +local FromCoordinate=self:GetCoordinate() +local PathOnRail,LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate,false,true) +self:T(string.format("Length on railroad = %.3f km",LengthOnRail/1000)) +local route={} +if PathOnRail then +table.insert(route,PathOnRail[1]:WaypointGround(Speed,"On Railroad")) +table.insert(route,PathOnRail[2]:WaypointGround(Speed,"On Railroad")) +end +if WaypointFunction then +local N=#route +for n,waypoint in pairs(route)do +waypoint.task={} +waypoint.task.id="ComboTask" +waypoint.task.params={} +waypoint.task.params.tasks={self:TaskFunction("CONTROLLABLE.___PassingWaypoint",n,N,WaypointFunction,unpack(WaypointFunctionArguments or{}))} +end +end +return route +end +function CONTROLLABLE.___PassingWaypoint(controllable,n,N,waypointfunction,...) +waypointfunction(controllable,n,N,...) +end +function CONTROLLABLE:RouteAirTo(ToCoordinate,AltType,Type,Action,Speed,DelaySeconds) +local FromCoordinate=self:GetCoordinate() +local FromWP=FromCoordinate:WaypointAir() +local ToWP=ToCoordinate:WaypointAir(AltType,Type,Action,Speed) +self:Route({FromWP,ToWP},DelaySeconds) +return self +end +function CONTROLLABLE:TaskRouteToZone(Zone,Randomize,Speed,Formation) +self:F2(Zone) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local ControllablePoint=self:GetVec2() +local PointFrom={} +PointFrom.x=ControllablePoint.x +PointFrom.y=ControllablePoint.y +PointFrom.type="Turning Point" +PointFrom.action=Formation or"Cone" +PointFrom.speed=20/3.6 +local PointTo={} +local ZonePoint +if Randomize then +ZonePoint=Zone:GetRandomVec2() +else +ZonePoint=Zone:GetVec2() +end +PointTo.x=ZonePoint.x +PointTo.y=ZonePoint.y +PointTo.type="Turning Point" +if Formation then +PointTo.action=Formation +else +PointTo.action="Cone" +end +if Speed then +PointTo.speed=Speed +else +PointTo.speed=20/3.6 +end +local Points={PointFrom,PointTo} +self:T3(Points) +self:Route(Points) +return self +end +return nil +end +function CONTROLLABLE:TaskRouteToVec2(Vec2,Speed,Formation) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local ControllablePoint=self:GetVec2() +local PointFrom={} +PointFrom.x=ControllablePoint.x +PointFrom.y=ControllablePoint.y +PointFrom.type="Turning Point" +PointFrom.action=Formation or"Cone" +PointFrom.speed=20/3.6 +local PointTo={} +PointTo.x=Vec2.x +PointTo.y=Vec2.y +PointTo.type="Turning Point" +if Formation then +PointTo.action=Formation +else +PointTo.action="Cone" +end +if Speed then +PointTo.speed=Speed +else +PointTo.speed=20/3.6 +end +local Points={PointFrom,PointTo} +self:T3(Points) +self:Route(Points) +return self +end +return nil +end +end +function CONTROLLABLE:CommandDoScript(DoScript) +local DCSDoScript={ +id="Script", +params={ +command=DoScript, +}, +} +self:T3(DCSDoScript) +return DCSDoScript +end +function CONTROLLABLE:GetTaskMission() +self:F2(self.ControllableName) +return routines.utils.deepCopy(_DATABASE.Templates.Controllables[self.ControllableName].Template) +end +function CONTROLLABLE:GetTaskRoute() +self:F2(self.ControllableName) +return routines.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]=routines.utils.deepCopy(Template.route.points[TPointID]) +if Randomize then +if not Radius then +Radius=500 +end +Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius) +Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius) +end +end +end +return Points +else +error("Template not found for Controllable : "..ControllableName) +end +return nil +end +function CONTROLLABLE:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil +local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil +local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil +local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil +local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil +local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil +local Params={} +if DetectionVisual then +Params[#Params+1]=DetectionVisual +end +if DetectionOptical then +Params[#Params+1]=DetectionOptical +end +if DetectionRadar then +Params[#Params+1]=DetectionRadar +end +if DetectionIRST then +Params[#Params+1]=DetectionIRST +end +if DetectionRWR then +Params[#Params+1]=DetectionRWR +end +if DetectionDLINK then +Params[#Params+1]=DetectionDLINK +end +self:T2({DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK}) +return self:_GetController():getDetectedTargets(Params[1],Params[2],Params[3],Params[4],Params[5],Params[6]) +end +return nil +end +function CONTROLLABLE:IsTargetDetected(DCSObject,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local DetectionVisual=(DetectVisual and DetectVisual==true)and Controller.Detection.VISUAL or nil +local DetectionOptical=(DetectOptical and DetectOptical==true)and Controller.Detection.OPTICAL or nil +local DetectionRadar=(DetectRadar and DetectRadar==true)and Controller.Detection.RADAR or nil +local DetectionIRST=(DetectIRST and DetectIRST==true)and Controller.Detection.IRST or nil +local DetectionRWR=(DetectRWR and DetectRWR==true)and Controller.Detection.RWR or nil +local DetectionDLINK=(DetectDLINK and DetectDLINK==true)and Controller.Detection.DLINK or nil +local Controller=self:_GetController() +local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity +=Controller:isTargetDetected(DCSObject,DetectionVisual,DetectionOptical,DetectionRadar,DetectionIRST,DetectionRWR,DetectionDLINK) +return TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity +end +return nil +end +function CONTROLLABLE:IsUnitDetected(Unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +if Unit and Unit:IsAlive()then +return self:IsTargetDetected(Unit:GetDCSObject(),DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +end +return nil +end +function CONTROLLABLE:IsGroupDetected(Group,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +self:F2(self.ControllableName) +if Group and Group:IsAlive()then +for _,_unit in pairs(Group:GetUnits())do +local unit=_unit +if unit and unit:IsAlive()then +local isdetected=self:IsUnitDetected(unit,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +if isdetected then +return true +end +end +end +return false +end +return nil +end +function CONTROLLABLE:GetDetectedUnitSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local unitset=SET_UNIT:New() +for DetectionObjectID,Detection in pairs(detectedtargets or{})do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local unit=UNIT:Find(DetectedObject) +if unit and unit:IsAlive()then +if not unitset:FindUnit(unit:GetName())then +unitset:AddUnit(unit) +end +end +end +end +return unitset +end +function CONTROLLABLE:GetDetectedGroupSet(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local detectedtargets=self:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local groupset=SET_GROUP:New() +for DetectionObjectID,Detection in pairs(detectedtargets or{})do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local unit=UNIT:Find(DetectedObject) +if unit and unit:IsAlive()then +local group=unit:GetGroup() +if group and not groupset:FindGroup(group:GetName())then +groupset:AddGroup(group) +end +end +end +end +return groupset +end +function CONTROLLABLE:SetOption(OptionID,OptionValue) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +Controller:setOption(OptionID,OptionValue) +return self +end +return nil +end +function CONTROLLABLE:OptionROE(ROEvalue) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,ROEvalue) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,ROEvalue) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,ROEvalue) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEHoldFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()or self:IsGround()or self:IsShip()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEHoldFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_HOLD) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.WEAPON_HOLD) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.WEAPON_HOLD) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEReturnFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()or self:IsGround()or self:IsShip()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEReturnFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.RETURN_FIRE) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.RETURN_FIRE) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.RETURN_FIRE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEOpenFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()or self:IsGround()or self:IsShip()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEOpenFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE) +elseif self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ROE,AI.Option.Ground.val.ROE.OPEN_FIRE) +elseif self:IsShip()then +Controller:setOption(AI.Option.Naval.id.ROE,AI.Option.Naval.val.ROE.OPEN_FIRE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEOpenFireWeaponFreePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEOpenFireWeaponFree() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROEWeaponFreePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROEWeaponFree() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.ROE,AI.Option.Air.val.ROE.WEAPON_FREE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTNoReactionPossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTNoReaction() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROT(ROTvalue) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,ROTvalue) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTPassiveDefensePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTPassiveDefense() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTEvadeFirePossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTEvadeFire() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionROTVerticalPossible() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +if self:IsAir()then +return true +end +return false +end +return nil +end +function CONTROLLABLE:OptionROTVertical() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT,AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAlarmStateAuto() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.AUTO) +elseif self:IsShip()then +Controller:setOption(9,0) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAlarmStateGreen() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) +elseif self:IsShip()then +Controller:setOption(9,1) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAlarmStateRed() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsGround()then +Controller:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) +elseif self:IsShip()then +Controller:setOption(9,2) +end +return self +end +return nil +end +function CONTROLLABLE:OptionRTBBingoFuel(RTB) +self:F2({self.ControllableName}) +if RTB==nil then +RTB=true +end +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.RTB_ON_BINGO,RTB) +end +return self +end +return nil +end +function CONTROLLABLE:OptionRTBAmmo(WeaponsFlag) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO,WeaponsFlag) +end +return self +end +return nil +end +function CONTROLLABLE:OptionAllowJettisonWeaponsOnThreat() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,false) +end +return self +end +return nil +end +function CONTROLLABLE:OptionKeepWeaponsOnThreat() +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if self:IsAir()then +Controller:setOption(AI.Option.Air.id.PROHIBIT_JETT,true) +end +return self +end +return nil +end +function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) +self:F2({self.ControllableName}) +if Prohibit==nil then +Prohibit=true +end +if self:IsAir()then +self:SetOption(AI.Option.Air.id.PROHIBIT_AB,Prohibit) +end +return self +end +function CONTROLLABLE:OptionECM_Never() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.ECM_USING,0) +end +return self +end +function CONTROLLABLE:OptionECM_OnlyLockByRadar() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.ECM_USING,1) +end +return self +end +function CONTROLLABLE:OptionECM_DetectedLockByRadar() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.ECM_USING,2) +end +return self +end +function CONTROLLABLE:OptionECM_AlwaysOn() +self:F2({self.ControllableName}) +if self:IsAir()then +self:SetOption(AI.Option.Air.id.ECM_USING,3) +end +return self +end +function CONTROLLABLE:WayPointInitialize(WayPoints) +self:F({WayPoints}) +if WayPoints then +self.WayPoints=WayPoints +else +self.WayPoints=self:GetTaskRoute() +end +return self +end +function CONTROLLABLE:GetWayPoints() +self:F() +if self.WayPoints then +return self.WayPoints +end +return nil +end +function CONTROLLABLE:WayPointFunction(WayPoint,WayPointIndex,WayPointFunction,...) +self:F2({WayPoint,WayPointIndex,WayPointFunction}) +table.insert(self.WayPoints[WayPoint].task.params.tasks,WayPointIndex) +self.WayPoints[WayPoint].task.params.tasks[WayPointIndex]=self:TaskFunction(WayPointFunction,arg) +return self +end +function CONTROLLABLE:WayPointExecute(WayPoint,WaitTime) +self:F({WayPoint,WaitTime}) +if not WayPoint then +WayPoint=1 +end +for TaskPointID=1,WayPoint-1 do +table.remove(self.WayPoints,1) +end +self:T3(self.WayPoints) +self:SetTask(self:TaskRoute(self.WayPoints),WaitTime) +return self +end +function CONTROLLABLE:IsAirPlane() +self:F2() +local DCSObject=self:GetDCSObject() +if DCSObject then +local Category=DCSObject:getDesc().category +return Category==Unit.Category.AIRPLANE +end +return nil +end +function CONTROLLABLE:IsHelicopter() +self:F2() +local DCSObject=self:GetDCSObject() +if DCSObject then +local Category=DCSObject:getDesc().category +return Category==Unit.Category.HELICOPTER +end +return nil +end +function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) +self:F2({self.ControllableName}) +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if RestrictBurner==true then +if self:IsAir()then +Controller:setOption(16,true) +end +else +if self:IsAir()then +Controller:setOption(16,false) +end +end +end +end +end +function CONTROLLABLE:OptionAAAttackRange(range) +self:F2({self.ControllableName}) +local range=range or 3 +if range<0 or range>4 then +range=3 +end +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsAir()then +self:SetOption(AI.Option.Air.val.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:RelocateGroundRandomInRadius(speed,radius,onroad,shortcut) +self:F2({self.ControllableName}) +local _coord=self:GetCoordinate() +local _radius=radius or 500 +local _speed=speed or 20 +local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100) +local _onroad=onroad or true +local _grptsk={} +local _candoroad=false +local _shortcut=shortcut or false +if onroad then +_grptsk,_candoroad=self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut) +self:Route(_grptsk,5) +else +self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") +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 +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_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:GetDCSObject() +local DCSGroup=Group.getByName(self.GroupName) +if DCSGroup then +return DCSGroup +end +return nil +end +function GROUP:GetPositionVec3() +self:F2(self.PositionableName) +local DCSPositionable=self:GetDCSObject() +if DCSPositionable then +local unit=DCSPositionable:getUnits()[1] +if unit then +local PositionablePosition=unit:getPosition().p +self:T3(PositionablePosition) +return PositionablePosition +end +end +return nil +end +function GROUP:IsAlive() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +if DCSGroup:isExist()then +local DCSUnit=DCSGroup:getUnit(1) +if DCSUnit then +local GroupIsAlive=DCSUnit:isActive() +self:T3(GroupIsAlive) +return GroupIsAlive +end +end +end +return nil +end +function GROUP:IsActive() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local unit=DCSGroup:getUnit(1) +if unit then +local GroupIsActive=unit:isActive() +return GroupIsActive +end +end +return nil +end +function GROUP:Destroy(GenerateEvent,delay) +self:F2(self.GroupName) +if delay and delay>0 then +self:ScheduleOnce(delay,GROUP.Destroy,self,GenerateEvent) +else +local DCSGroup=self:GetDCSObject() +if DCSGroup then +for Index,UnitData in pairs(DCSGroup:getUnits())do +if GenerateEvent and GenerateEvent==true then +if self:IsAir()then +self:CreateEventCrash(timer.getTime(),UnitData) +else +self:CreateEventDead(timer.getTime(),UnitData) +end +elseif GenerateEvent==false then +else +self:CreateEventRemoveUnit(timer.getTime(),UnitData) +end +end +USERFLAG:New(self:GetName()):Set(100) +DCSGroup:destroy() +DCSGroup=nil +end +end +return nil +end +function GROUP:GetCategory() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCategory=DCSGroup:getCategory() +self:T3(GroupCategory) +return GroupCategory +end +return nil +end +function GROUP:GetCategoryName() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local CategoryNames={ +[Group.Category.AIRPLANE]="Airplane", +[Group.Category.HELICOPTER]="Helicopter", +[Group.Category.GROUND]="Ground Unit", +[Group.Category.SHIP]="Ship", +[Group.Category.TRAIN]="Train", +} +local GroupCategory=DCSGroup:getCategory() +self:T3(GroupCategory) +return CategoryNames[GroupCategory] +end +return nil +end +function GROUP:GetCoalition() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCoalition=DCSGroup:getCoalition() +self:T3(GroupCoalition) +return GroupCoalition +end +return nil +end +function GROUP:GetCountry() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCountry=DCSGroup:getUnit(1):getCountry() +self:T3(GroupCountry) +return GroupCountry +end +return nil +end +function GROUP:HasAttribute(attribute,all) +local _units=self:GetUnits() +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 +function GROUP:GetSpeedMax() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local Units=self:GetUnits() +local speedmax=nil +for _,unit in pairs(Units)do +local unit=unit +local speed=unit:GetSpeedMax() +if speedmax==nil then +speedmax=speed +elseif speed0 then +self:ScheduleOnce(delay,GROUP.Activate,self) +else +trigger.action.activateGroup(self:GetDCSObject()) +end +return self +end +function GROUP:GetTypeName() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupTypeName=DCSGroup:getUnit(1):getTypeName() +self:T3(GroupTypeName) +return(GroupTypeName) +end +return nil +end +function GROUP:GetPlayerName() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local PlayerName=DCSGroup:getUnit(1):getPlayerName() +self:T3(PlayerName) +return(PlayerName) +end +return nil +end +function GROUP:GetCallsign() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupCallSign=DCSGroup:getUnit(1):getCallsign() +self:T3(GroupCallSign) +return GroupCallSign +end +BASE:E({"Cannot GetCallsign",Positionable=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetVec2() +local Unit=self:GetUnit(1) +if Unit then +local vec2=Unit:GetVec2() +return vec2 +end +end +function GROUP:GetVec3() +local unit=self:GetUnit(1) +if unit then +local vec3=unit:GetVec3() +return vec3 +end +self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) +return nil +end +function GROUP:GetPointVec2() +self:F2(self.GroupName) +local FirstUnit=self:GetUnit(1) +if FirstUnit then +local FirstUnitPointVec2=FirstUnit:GetPointVec2() +self:T3(FirstUnitPointVec2) +return FirstUnitPointVec2 +end +BASE:E({"Cannot GetPointVec2",Group=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetCoordinate() +local FirstUnit=self:GetUnit(1) +if FirstUnit then +local FirstUnitCoordinate=FirstUnit:GetCoordinate() +return FirstUnitCoordinate +end +BASE:E({"Cannot GetCoordinate",Group=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetRandomVec3(Radius) +self:F2(self.GroupName) +local FirstUnit=self:GetUnit(1) +if FirstUnit then +local FirstUnitRandomPointVec3=FirstUnit:GetRandomVec3(Radius) +self:T3(FirstUnitRandomPointVec3) +return FirstUnitRandomPointVec3 +end +BASE:E({"Cannot GetRandomVec3",Group=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetHeading() +self:F2(self.GroupName) +local GroupSize=self:GetSize() +local HeadingAccumulator=0 +local n=0 +if GroupSize then +for i=1,GroupSize do +local unit=self:GetUnit(i) +if unit and unit:IsAlive()then +HeadingAccumulator=HeadingAccumulator+unit:GetHeading() +n=n+1 +end +end +return math.floor(HeadingAccumulator/n) +end +BASE:E({"Cannot GetHeading",Group=self,Alive=self:IsAlive()}) +return nil +end +function GROUP:GetFuelMin() +self:F3(self.ControllableName) +if not self:GetDCSObject()then +BASE:E({"Cannot GetFuel",Group=self,Alive=self:IsAlive()}) +return 0 +end +local min=65535 +local unit=nil +local tmp=nil +for UnitID,UnitData in pairs(self:GetUnits())do +if UnitData and UnitData:IsAlive()then +tmp=UnitData:GetFuel() +if tmpGroupVelocityMax then +GroupVelocityMax=UnitVelocity +end +end +return GroupVelocityMax +end +return nil +end +function GROUP:GetMinHeight() +self:F2() +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local GroupHeightMin=999999999 +for Index,UnitData in pairs(DCSGroup:getUnits())do +local UnitData=UnitData +local UnitHeight=UnitData:getPoint() +if UnitHeightGroupHeightMax then +GroupHeightMax=UnitHeight +end +end +return GroupHeightMax +end +return nil +end +function GROUP:GetTemplate() +local GroupName=self:GetName() +return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName)) +end +function GROUP:GetTemplateRoutePoints() +local GroupName=self:GetName() +return UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName).route.points) +end +function GROUP:SetTemplateControlled(Template,Controlled) +Template.uncontrolled=not Controlled +return Template +end +function GROUP:SetTemplateCountry(Template,CountryID) +Template.CountryID=CountryID +return Template +end +function GROUP:SetTemplateCoalition(Template,CoalitionID) +Template.CoalitionID=CoalitionID +return Template +end +function GROUP:InitHeading(Heading) +self.InitRespawnHeading=Heading +return self +end +function GROUP:InitHeight(Height) +self.InitRespawnHeight=Height +return self +end +function GROUP:InitZone(Zone) +self.InitRespawnZone=Zone +return self +end +function GROUP:InitRandomizePositionZone(PositionZone) +self.InitRespawnRandomizePositionZone=PositionZone +self.InitRespawnRandomizePositionInner=nil +self.InitRespawnRandomizePositionOuter=nil +return self +end +function GROUP:InitRandomizePositionRadius(OuterRadius,InnerRadius) +self.InitRespawnRandomizePositionZone=nil +self.InitRespawnRandomizePositionOuter=OuterRadius +self.InitRespawnRandomizePositionInner=InnerRadius +return self +end +function GROUP:InitCoordinate(coordinate) +self:F({coordinate=coordinate}) +self.InitCoord=coordinate +return self +end +function GROUP:InitRadioCommsOnOff(switch) +self:F({switch=switch}) +if switch==true or switch==nil then +self.InitRespawnRadio=true +else +self.InitRespawnRadio=false +end +return self +end +function GROUP:InitRadioFrequency(frequency) +self:F({frequency=frequency}) +self.InitRespawnFreq=frequency +return self +end +function GROUP:InitRadioModulation(modulation) +self:F({modulation=modulation}) +if modulation and modulation:lower()=="fm"then +self.InitRespawnModu=radio.modulation.FM +else +self.InitRespawnModu=radio.modulation.AM +end +return self +end +function GROUP:InitModex(modex) +self:F({modex=modex}) +if modex then +self.InitRespawnModex=tonumber(modex) +end +return self +end +function GROUP:Respawn(Template,Reset) +Template=Template or self:GetTemplate() +local function _Heading(course) +local h +if course<=180 then +h=math.rad(course) +else +h=-math.rad(360-course) +end +return h +end +if self:IsAlive()then +local Zone=self.InitRespawnZone +local Vec3=Zone and Zone:GetVec3()or self:GetVec3() +local From={x=Template.x,y=Template.y} +Template.x=Vec3.x +Template.y=Vec3.z +self:F(#Template.units) +if Reset==true then +for UnitID,UnitData in pairs(self:GetUnits())do +local GroupUnit=UnitData +self:F(GroupUnit:GetName()) +if GroupUnit:IsAlive()then +self:I("FF Alive") +local GroupUnitVec3=GroupUnit:GetVec3() +if Zone then +if self.InitRespawnRandomizePositionZone then +GroupUnitVec3=Zone:GetRandomVec3() +else +if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then +GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) +else +GroupUnitVec3=Zone:GetVec3() +end +end +end +if self.InitCoord then +GroupUnitVec3=self.InitCoord:GetVec3() +end +Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y +if Zone then +Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x +Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z +else +Template.units[UnitID].x=GroupUnitVec3.x +Template.units[UnitID].y=GroupUnitVec3.z +end +Template.units[UnitID].heading=_Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading()) +Template.units[UnitID].psi=-Template.units[UnitID].heading +self:F({UnitID,Template.units[UnitID],Template.units[UnitID]}) +end +end +elseif Reset==false then +for UnitID,TemplateUnitData in pairs(Template.units)do +self:F("Reset") +local GroupUnitVec3={x=TemplateUnitData.x,y=TemplateUnitData.alt,z=TemplateUnitData.y} +if Zone then +if self.InitRespawnRandomizePositionZone then +GroupUnitVec3=Zone:GetRandomVec3() +else +if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then +GroupUnitVec3=POINT_VEC3:NewFromVec2(From):GetRandomPointVec3InRadius(self.InitRespawnRandomizePositionsOuter,self.InitRespawnRandomizePositionsInner) +else +GroupUnitVec3=Zone:GetVec3() +end +end +end +if self.InitCoord then +GroupUnitVec3=self.InitCoord:GetVec3() +end +Template.units[UnitID].alt=self.InitRespawnHeight and self.InitRespawnHeight or GroupUnitVec3.y +Template.units[UnitID].x=(Template.units[UnitID].x-From.x)+GroupUnitVec3.x +Template.units[UnitID].y=(Template.units[UnitID].y-From.y)+GroupUnitVec3.z +Template.units[UnitID].heading=self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading +self:F({UnitID,Template.units[UnitID],Template.units[UnitID]}) +end +else +local units=self:GetUnits() +for UnitID,Unit in pairs(Template.units)do +for _,_unit in pairs(units)do +local unit=_unit +if unit:GetName()==Unit.name then +local coord=unit:GetCoordinate() +local heading=unit:GetHeading() +Unit.x=coord.x +Unit.y=coord.z +Unit.alt=coord.y +Unit.heading=math.rad(heading) +Unit.psi=-Unit.heading +end +end +end +end +end +if self.InitRespawnModex then +for UnitID=1,#Template.units do +Template.units[UnitID].onboard_num=string.format("%03d",self.InitRespawnModex+(UnitID-1)) +end +end +if self.InitRespawnRadio then +Template.communication=self.InitRespawnRadio +end +if self.InitRespawnFreq then +Template.frequency=self.InitRespawnFreq +end +if self.InitRespawnModu then +Template.modulation=self.InitRespawnModu +end +self:Destroy(false) +self:T({Template=Template}) +_DATABASE:Spawn(Template) +self:ResetEvents() +return self +end +function GROUP:RespawnAtCurrentAirbase(SpawnTemplate,Takeoff,Uncontrolled) +self:F2({SpawnTemplate,Takeoff,Uncontrolled}) +if self and self:IsAlive()then +local airbase=self:GetCoordinate():GetClosestAirbase() +if airbase then +self:F2("Closest airbase = "..airbase:GetName()) +else +self:E("ERROR: could not find closest airbase!") +return nil +end +Takeoff=Takeoff or SPAWN.Takeoff.Hot +local AirbaseCoord=airbase:GetCoordinate() +SpawnTemplate=SpawnTemplate or self:GetTemplate() +if SpawnTemplate then +local SpawnPoint=SpawnTemplate.route.points[1] +SpawnPoint.linkUnit=nil +SpawnPoint.helipadId=nil +SpawnPoint.airdromeId=nil +local AirbaseID=airbase:GetID() +local AirbaseCategory=airbase:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.SHIP or AirbaseCategory==Airbase.Category.HELIPAD then +SpawnPoint.linkUnit=AirbaseID +SpawnPoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +SpawnPoint.airdromeId=AirbaseID +end +SpawnPoint.type=GROUPTEMPLATE.Takeoff[Takeoff][1] +SpawnPoint.action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local units=self:GetUnits() +local x +local y +for UnitID=1,#units do +local unit=units[UnitID] +local Parkingspot,TermialID,Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase) +self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s",tostring(Distance),tostring(TermialID))) +local uc=unit:GetCoordinate() +SpawnTemplate.units[UnitID].x=uc.x +SpawnTemplate.units[UnitID].y=uc.z +SpawnTemplate.units[UnitID].alt=uc.y +SpawnTemplate.units[UnitID].parking=TermialID +SpawnTemplate.units[UnitID].parking_id=nil +end +SpawnPoint.x=SpawnTemplate.units[1].x +SpawnPoint.y=SpawnTemplate.units[1].y +SpawnPoint.alt=SpawnTemplate.units[1].alt +SpawnTemplate.x=SpawnTemplate.units[1].x +SpawnTemplate.y=SpawnTemplate.units[1].y +SpawnTemplate.uncontrolled=Uncontrolled +if self.InitRespawnRadio then +SpawnTemplate.communication=self.InitRespawnRadio +end +if self.InitRespawnFreq then +SpawnTemplate.frequency=self.InitRespawnFreq +end +if self.InitRespawnModu then +SpawnTemplate.modulation=self.InitRespawnModu +end +self:Destroy(false) +_DATABASE:Spawn(SpawnTemplate) +self:ResetEvents() +return self +end +else +self:E("WARNING: GROUP is not alive!") +end +return nil +end +function GROUP:GetTaskMission() +self:F2(self.GroupName) +return routines.utils.deepCopy(_DATABASE.Templates.Groups[self.GroupName].Template) +end +function GROUP:GetTaskRoute() +self:F2(self.GroupName) +return routines.utils.deepCopy(_DATABASE.Templates.Groups[self.GroupName].Template.route.points) +end +function GROUP:CopyRoute(Begin,End,Randomize,Radius) +self:F2({Begin,End}) +local Points={} +local GroupName=string.match(self:GetName(),".*#") +if GroupName then +GroupName=GroupName:sub(1,-2) +else +GroupName=self:GetName() +end +self:T3({GroupName}) +local Template=_DATABASE.Templates.Groups[GroupName].Template +if Template then +if not Begin then +Begin=0 +end +if not End then +End=0 +end +for TPointID=Begin+1,#Template.route.points-End do +if Template.route.points[TPointID]then +Points[#Points+1]=routines.utils.deepCopy(Template.route.points[TPointID]) +if Randomize then +if not Radius then +Radius=500 +end +Points[#Points].x=Points[#Points].x+math.random(Radius*-1,Radius) +Points[#Points].y=Points[#Points].y+math.random(Radius*-1,Radius) +end +end +end +return Points +else +error("Template not found for Group : "..GroupName) +end +return nil +end +function GROUP:CalculateThreatLevelA2G() +local MaxThreatLevelA2G=0 +for UnitName,UnitData in pairs(self:GetUnits())do +local ThreatUnit=UnitData +local ThreatLevelA2G=ThreatUnit:GetThreatLevel() +if ThreatLevelA2G>MaxThreatLevelA2G then +MaxThreatLevelA2G=ThreatLevelA2G +end +end +self:T3(MaxThreatLevelA2G) +return MaxThreatLevelA2G +end +function GROUP:GetThreatLevel() +local threatlevelMax=0 +for UnitName,UnitData in pairs(self:GetUnits())do +local ThreatUnit=UnitData +local threatlevel=ThreatUnit:GetThreatLevel() +if threatlevel>threatlevelMax then +threatlevelMax=threatlevel +end +end +return threatlevelMax +end +function GROUP:InAir() +self:F2(self.GroupName) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +local DCSUnit=DCSGroup:getUnit(1) +if DCSUnit then +local GroupInAir=DCSGroup:getUnit(1):inAir() +self:T3(GroupInAir) +return GroupInAir +end +end +return nil +end +function GROUP:IsAirborne(AllUnits) +self:F2(self.GroupName) +local units=self:GetUnits() +if units then +if AllUnits then +for _,_unit in pairs(units)do +local unit=_unit +if unit then +local inair=unit:InAir() +if not inair then +return false +end +end +end +return true +else +for _,_unit in pairs(units)do +local unit=_unit +if unit then +local inair=unit:InAir() +if inair then +return true +end +end +return false +end +end +end +return nil +end +function GROUP:GetDCSDesc(n) +n=n or 1 +local unit=self:GetUnit(n) +if unit and unit:IsAlive()~=nil then +local desc=unit:GetDesc() +return desc +end +return nil +end +function GROUP:GetAttribute() +local attribute=GROUP.Attribute.OTHER_UNKNOWN +if self then +local transportplane=self:HasAttribute("Transports")and self:HasAttribute("Planes") +local awacs=self:HasAttribute("AWACS") +local fighter=self:HasAttribute("Fighters")or self:HasAttribute("Interceptors")or self:HasAttribute("Multirole fighters")or(self:HasAttribute("Bombers")and not self:HasAttribute("Strategic bombers")) +local bomber=self:HasAttribute("Strategic bombers") +local tanker=self:HasAttribute("Tankers") +local uav=self:HasAttribute("UAVs") +local transporthelo=self:HasAttribute("Transport helicopters") +local attackhelicopter=self:HasAttribute("Attack helicopters") +local apc=self:HasAttribute("Infantry carriers") +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") +local aaa=self:HasAttribute("AAA") +local ewr=self:HasAttribute("EWR") +local sam=self:HasAttribute("SAM elements")and(not self:HasAttribute("AAA")) +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 transportplane then +attribute=GROUP.Attribute.AIR_TRANSPORTPLANE +elseif awacs then +attribute=GROUP.Attribute.AIR_AWACS +elseif fighter then +attribute=GROUP.Attribute.AIR_FIGHTER +elseif bomber then +attribute=GROUP.Attribute.AIR_BOMBER +elseif tanker then +attribute=GROUP.Attribute.AIR_TANKER +elseif transporthelo then +attribute=GROUP.Attribute.AIR_TRANSPORTHELO +elseif attackhelicopter then +attribute=GROUP.Attribute.AIR_ATTACKHELO +elseif uav then +attribute=GROUP.Attribute.AIR_UAV +elseif apc then +attribute=GROUP.Attribute.GROUND_APC +elseif infantry then +attribute=GROUP.Attribute.GROUND_INFANTRY +elseif artillery then +attribute=GROUP.Attribute.GROUND_ARTILLERY +elseif tank then +attribute=GROUP.Attribute.GROUND_TANK +elseif aaa then +attribute=GROUP.Attribute.GROUND_AAA +elseif ewr then +attribute=GROUP.Attribute.GROUND_EWR +elseif sam then +attribute=GROUP.Attribute.GROUND_SAM +elseif truck then +attribute=GROUP.Attribute.GROUND_TRUCK +elseif train then +attribute=GROUP.Attribute.GROUND_TRAIN +elseif aircraftcarrier then +attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER +elseif warship then +attribute=GROUP.Attribute.NAVAL_WARSHIP +elseif armedship then +attribute=GROUP.Attribute.NAVAL_ARMEDSHIP +elseif unarmedship then +attribute=GROUP.Attribute.NAVAL_UNARMEDSHIP +else +if self:IsGround()then +attribute=GROUP.Attribute.GROUND_OTHER +elseif self:IsShip()then +attribute=GROUP.Attribute.NAVAL_OTHER +elseif self:IsAir()then +attribute=GROUP.Attribute.AIR_OTHER +else +attribute=GROUP.Attribute.OTHER_UNKNOWN +end +end +end +return attribute +end +do +function GROUP:RouteRTB(RTBAirbase,Speed) +self:F({RTBAirbase:GetName(),Speed}) +local DCSGroup=self:GetDCSObject() +if DCSGroup then +if RTBAirbase then +local Speed=Speed or self:GetSpeedMax()*0.8 +local coord=self:GetCoordinate() +local PointFrom=coord:WaypointAirTurningPoint(nil,Speed) +local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed,RTBAirbase) +local Points={PointFrom,PointLanding} +self:T3(Points) +local Template=self:GetTemplate() +Template.route.points=Points +self:Respawn(Template,true) +self:Route(Points) +else +self:ClearTasks() +end +end +return self +end +end +function GROUP:OnReSpawn(ReSpawnFunction) +self.ReSpawnFunction=ReSpawnFunction +end +do +function GROUP:HandleEvent(Event,EventFunction,...) +self:EventDispatcher():OnEventForGroup(self:GetName(),EventFunction,self,Event,...) +return self +end +function GROUP:UnHandleEvent(Event) +self:EventDispatcher():RemoveEvent(self,Event) +return self +end +function GROUP:ResetEvents() +self:EventDispatcher():Reset(self) +for UnitID,UnitData in pairs(self:GetUnits())do +UnitData:ResetEvents() +end +return self +end +end +do +function GROUP:GetPlayerNames() +local HasPlayers=false +local PlayerNames={} +local Units=self:GetUnits() +for UnitID,UnitData in pairs(Units)do +local Unit=UnitData +local PlayerName=Unit:GetPlayerName() +if PlayerName and PlayerName~=""then +PlayerNames=PlayerNames or{} +table.insert(PlayerNames,PlayerName) +HasPlayers=true +end +end +if HasPlayers==true then +self:F2(PlayerNames) +return PlayerNames +end +return nil +end +function GROUP:GetPlayerCount() +local PlayerCount=0 +local Units=self:GetUnits() +for UnitID,UnitData in pairs(Units or{})do +local Unit=UnitData +local PlayerName=Unit:GetPlayerName() +if PlayerName and PlayerName~=""then +PlayerCount=PlayerCount+1 +end +end +return PlayerCount +end +end +function GROUP:EnableEmission(switch) +self:F2(self.GroupName) +local switch=switch or false +local DCSUnit=self:GetDCSObject() +if DCSUnit then +DCSUnit:enableEmission(switch) +end +end +UNIT={ +ClassName="UNIT", +UnitName=nil, +} +function UNIT:Register(UnitName) +local self=BASE:Inherit(self,CONTROLLABLE:New(UnitName)) +self.UnitName=UnitName +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:Name() +return self.UnitName +end +function UNIT:GetDCSObject() +local DCSUnit=Unit.getByName(self.UnitName) +if DCSUnit then +return DCSUnit +end +return nil +end +function UNIT:ReSpawnAt(Coordinate,Heading) +self:T(self:Name()) +local SpawnGroupTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name())) +self:T(SpawnGroupTemplate) +local SpawnGroup=self:GetGroup() +self:T({SpawnGroup=SpawnGroup}) +if SpawnGroup then +local Vec3=SpawnGroup:GetVec3() +SpawnGroupTemplate.x=Coordinate.x +SpawnGroupTemplate.y=Coordinate.z +self:F(#SpawnGroupTemplate.units) +for UnitID,UnitData in pairs(SpawnGroup:GetUnits())do +local GroupUnit=UnitData +self:F(GroupUnit:GetName()) +if GroupUnit:IsAlive()then +local GroupUnitVec3=GroupUnit:GetVec3() +local GroupUnitHeading=GroupUnit:GetHeading() +SpawnGroupTemplate.units[UnitID].alt=GroupUnitVec3.y +SpawnGroupTemplate.units[UnitID].x=GroupUnitVec3.x +SpawnGroupTemplate.units[UnitID].y=GroupUnitVec3.z +SpawnGroupTemplate.units[UnitID].heading=GroupUnitHeading +self:F({UnitID,SpawnGroupTemplate.units[UnitID],SpawnGroupTemplate.units[UnitID]}) +end +end +end +for UnitTemplateID,UnitTemplateData in pairs(SpawnGroupTemplate.units)do +self:T({UnitTemplateData.name,self:Name()}) +SpawnGroupTemplate.units[UnitTemplateID].unitId=nil +if UnitTemplateData.name==self:Name()then +self:T("Adjusting") +SpawnGroupTemplate.units[UnitTemplateID].alt=Coordinate.y +SpawnGroupTemplate.units[UnitTemplateID].x=Coordinate.x +SpawnGroupTemplate.units[UnitTemplateID].y=Coordinate.z +SpawnGroupTemplate.units[UnitTemplateID].heading=Heading +self:F({UnitTemplateID,SpawnGroupTemplate.units[UnitTemplateID],SpawnGroupTemplate.units[UnitTemplateID]}) +else +self:F(SpawnGroupTemplate.units[UnitTemplateID].name) +local GroupUnit=UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name) +if GroupUnit and GroupUnit:IsAlive()then +local GroupUnitVec3=GroupUnit:GetVec3() +local GroupUnitHeading=GroupUnit:GetHeading() +UnitTemplateData.alt=GroupUnitVec3.y +UnitTemplateData.x=GroupUnitVec3.x +UnitTemplateData.y=GroupUnitVec3.z +UnitTemplateData.heading=GroupUnitHeading +else +if SpawnGroupTemplate.units[UnitTemplateID].name~=self:Name()then +self:T("nilling") +SpawnGroupTemplate.units[UnitTemplateID].delete=true +end +end +end +end +local i=1 +while i<=#SpawnGroupTemplate.units do +local UnitTemplateData=SpawnGroupTemplate.units[i] +self:T(UnitTemplateData.name) +if UnitTemplateData.delete then +table.remove(SpawnGroupTemplate.units,i) +else +i=i+1 +end +end +SpawnGroupTemplate.groupId=nil +self:T(SpawnGroupTemplate) +_DATABASE:Spawn(SpawnGroupTemplate) +end +function UNIT:IsActive() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitIsActive=DCSUnit:isActive() +return UnitIsActive +end +return nil +end +function UNIT:IsAlive() +self:F3(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitIsAlive=DCSUnit:isExist()and DCSUnit:isActive() +return UnitIsAlive +end +return nil +end +function UNIT:GetCallsign() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitCallSign=DCSUnit:getCallsign() +if UnitCallSign==""then +UnitCallSign=DCSUnit:getName() +end +return UnitCallSign +end +self:F(self.ClassName.." "..self.UnitName.." not found!") +return nil +end +function UNIT:IsPlayer() +local group=self:GetGroup() +local units=group:GetTemplate().units +for _,unit in pairs(units)do +if unit.name==self:GetName()and(unit.skill=="Client"or unit.skill=="Player")then +return true +end +end +return false +end +function UNIT:GetPlayerName() +self:F(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local PlayerName=DCSUnit:getPlayerName() +return PlayerName +end +return nil +end +function UNIT:IsClient() +if _DATABASE.CLIENTS[self.UnitName]then +return true +end +return false +end +function UNIT:GetClient() +local client=_DATABASE.CLIENTS[self.UnitName] +if client then +return client +end +return nil +end +function UNIT:GetNumber() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitNumber=DCSUnit:getNumber() +return UnitNumber +end +return nil +end +function UNIT:GetSpeedMax() +self:F2(self.UnitName) +local Desc=self:GetDesc() +if Desc then +local SpeedMax=Desc.speedMax +return SpeedMax*3.6 +end +return nil +end +function UNIT:GetRange() +self:F2(self.UnitName) +local Desc=self:GetDesc() +if Desc then +local Range=Desc.range +if Range then +Range=Range*1000 +else +Range=10000000 +end +return Range +end +return nil +end +function UNIT:IsRefuelable() +self:F2(self.UnitName) +local refuelable=self:HasAttribute("Refuelable") +local system=nil +local Desc=self:GetDesc() +if Desc and Desc.tankerType then +system=Desc.tankerType +end +return refuelable,system +end +function UNIT:IsTanker() +self:F2(self.UnitName) +local tanker=self:HasAttribute("Tankers") +local system=nil +if tanker then +local Desc=self:GetDesc() +if Desc and Desc.tankerType then +system=Desc.tankerType +end +local typename=self:GetTypeName() +if typename=="IL-78M"then +system=1 +elseif typename=="KC130"then +system=1 +elseif typename=="KC135BDA"then +system=1 +elseif typename=="KC135MPRS"then +system=1 +elseif typename=="S-3B Tanker"then +system=1 +end +end +return tanker,system +end +function UNIT:GetGroup() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitGroup=GROUP:FindByName(DCSUnit:getGroup():getName()) +return UnitGroup +end +return nil +end +function UNIT:GetPrefix() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitPrefix=string.match(self.UnitName,".*#"):sub(1,-2) +self:T3(UnitPrefix) +return UnitPrefix +end +return nil +end +function UNIT:GetAmmo() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitAmmo=DCSUnit:getAmmo() +return UnitAmmo +end +return nil +end +function UNIT:GetAmmunition() +local nammo=0 +local nshells=0 +local nrockets=0 +local nmissiles=0 +local nbombs=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 _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 +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 +end +end +end +end +nammo=nshells+nrockets+nmissiles+nbombs +return nammo,nshells,nrockets,nbombs,nmissiles +end +function UNIT:GetSensors() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitSensors=DCSUnit:getSensors() +return UnitSensors +end +return nil +end +function UNIT:HasSensors(...) +self:F2(arg) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local HasSensors=DCSUnit:hasSensors(unpack(arg)) +return HasSensors +end +return nil +end +function UNIT:HasSEAD() +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitSEADAttributes=DCSUnit:getDesc().attributes +local HasSEAD=false +if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"]==true or +UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"]==true then +HasSEAD=true +end +return HasSEAD +end +return nil +end +function UNIT:GetRadar() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitRadarOn,UnitRadarObject=DCSUnit:getRadar() +return UnitRadarOn,UnitRadarObject +end +return nil,nil +end +function UNIT:GetFuel() +self:F3(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitFuel=DCSUnit:getFuel() +return UnitFuel +end +return nil +end +function UNIT:SetEmission(Switch) +if Switch==nil then +Switch=true +end +local DCSUnit=self:GetDCSObject() +if DCSUnit then +DCSUnit:enableEmission(Switch) +end +return self +end +function UNIT:EnableEmission() +self:SetEmission(true) +return self +end +function UNIT:DisableEmission() +self:SetEmission(false) +return self +end +function UNIT:GetUnits() +self:F3({self.UnitName}) +local DCSUnit=self:GetDCSObject() +local Units={} +if DCSUnit then +Units[1]=UNIT:Find(DCSUnit) +self:T3(Units) +return Units +end +return nil +end +function UNIT:GetLife() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitLife=DCSUnit:getLife() +return UnitLife +end +return-1 +end +function UNIT:GetLife0() +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitLife0=DCSUnit:getLife0() +return UnitLife0 +end +return 0 +end +function UNIT:GetLifeRelative() +self:F2(self.UnitName) +if self and self:IsAlive()then +local life0=self:GetLife0() +local lifeN=self:GetLife() +return lifeN/life0 +end +return-1 +end +function UNIT:GetDamageRelative() +self:F2(self.UnitName) +if self and self:IsAlive()then +return 1-self:GetLifeRelative() +end +return 1 +end +function UNIT:GetUnitCategory() +self:F3(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +return DCSUnit:getDesc().category +end +return nil +end +function UNIT:GetCategoryName() +self:F3(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local CategoryNames={ +[Unit.Category.AIRPLANE]="Airplane", +[Unit.Category.HELICOPTER]="Helicopter", +[Unit.Category.GROUND_UNIT]="Ground Unit", +[Unit.Category.SHIP]="Ship", +[Unit.Category.STRUCTURE]="Structure", +} +local UnitCategory=DCSUnit:getDesc().category +self:T3(UnitCategory) +return CategoryNames[UnitCategory] +end +return nil +end +function UNIT:GetThreatLevel() +local ThreatLevel=0 +local ThreatText="" +local Descriptor=self:GetDesc() +if Descriptor then +local Attributes=Descriptor.attributes +if self:IsGround()then +local ThreatLevels={ +"Unarmed", +"Infantry", +"Old Tanks & APCs", +"Tanks & IFVs without ATGM", +"Tanks & IFV with ATGM", +"Modern Tanks", +"AAA", +"IR Guided SAMs", +"SR SAMs", +"MR SAMs", +"LR SAMs" +} +if Attributes["LR SAM"]then ThreatLevel=10 +elseif Attributes["MR SAM"]then ThreatLevel=9 +elseif Attributes["SR SAM"]and +not Attributes["IR Guided SAM"]then ThreatLevel=8 +elseif(Attributes["SR SAM"]or Attributes["MANPADS"])and +Attributes["IR Guided SAM"]then ThreatLevel=7 +elseif Attributes["AAA"]then ThreatLevel=6 +elseif Attributes["Modern Tanks"]then ThreatLevel=5 +elseif(Attributes["Tanks"]or Attributes["IFV"])and +Attributes["ATGM"]then ThreatLevel=4 +elseif(Attributes["Tanks"]or Attributes["IFV"])and +not Attributes["ATGM"]then ThreatLevel=3 +elseif Attributes["Old Tanks"]or Attributes["APC"]or Attributes["Artillery"]then ThreatLevel=2 +elseif Attributes["Infantry"]then ThreatLevel=1 +end +ThreatText=ThreatLevels[ThreatLevel+1] +end +if self:IsAir()then +local ThreatLevels={ +"Unarmed", +"Tanker", +"AWACS", +"Transport Helicopter", +"UAV", +"Bomber", +"Strategic Bomber", +"Attack Helicopter", +"Battleplane", +"Multirole Fighter", +"Fighter" +} +if Attributes["Fighters"]then ThreatLevel=10 +elseif Attributes["Multirole fighters"]then ThreatLevel=9 +elseif Attributes["Battleplanes"]then ThreatLevel=8 +elseif Attributes["Attack helicopters"]then ThreatLevel=7 +elseif Attributes["Strategic bombers"]then ThreatLevel=6 +elseif Attributes["Bombers"]then ThreatLevel=5 +elseif Attributes["UAVs"]then ThreatLevel=4 +elseif Attributes["Transport helicopters"]then ThreatLevel=3 +elseif Attributes["AWACS"]then ThreatLevel=2 +elseif Attributes["Tankers"]then ThreatLevel=1 +end +ThreatText=ThreatLevels[ThreatLevel+1] +end +if self:IsShip()then +local ThreatLevels={ +"Unarmed ship", +"Light armed ships", +"Corvettes", +"", +"Frigates", +"", +"Cruiser", +"", +"Destroyer", +"", +"Aircraft Carrier" +} +if Attributes["Aircraft Carriers"]then ThreatLevel=10 +elseif Attributes["Destroyers"]then ThreatLevel=8 +elseif Attributes["Cruisers"]then ThreatLevel=6 +elseif Attributes["Frigates"]then ThreatLevel=4 +elseif Attributes["Corvettes"]then ThreatLevel=2 +elseif Attributes["Light armed ships"]then ThreatLevel=1 +end +ThreatText=ThreatLevels[ThreatLevel+1] +end +end +return ThreatLevel,ThreatText +end +function UNIT:Explode(power,delay) +power=power or 100 +local DCSUnit=self:GetDCSObject() +if DCSUnit then +if delay and delay>0 then +SCHEDULER:New(nil,self.Explode,{self,power},delay) +else +self:GetCoordinate():Explosion(power) +end +return self +end +return nil +end +function UNIT:OtherUnitInRadius(AwaitUnit,Radius) +self:F2({self.UnitName,AwaitUnit.UnitName,Radius}) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitVec3=self:GetVec3() +local AwaitUnitVec3=AwaitUnit:GetVec3() +if(((UnitVec3.x-AwaitUnitVec3.x)^2+(UnitVec3.z-AwaitUnitVec3.z)^2)^0.5<=Radius)then +self:T3("true") +return true +else +self:T3("false") +return false +end +end +return nil +end +function UNIT:IsFriendly(FriendlyCoalition) +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitCoalition=DCSUnit:getCoalition() +self:T3({UnitCoalition,FriendlyCoalition}) +local IsFriendlyResult=(UnitCoalition==FriendlyCoalition) +self:F(IsFriendlyResult) +return IsFriendlyResult +end +return nil +end +function UNIT:IsShip() +self:F2() +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitDescriptor=DCSUnit:getDesc() +self:T3({UnitDescriptor.category,Unit.Category.SHIP}) +local IsShipResult=(UnitDescriptor.category==Unit.Category.SHIP) +self:T3(IsShipResult) +return IsShipResult +end +return nil +end +function UNIT:InAir(NoHeloCheck) +self:F2(self.UnitName) +local DCSUnit=self:GetDCSObject() +if DCSUnit then +local UnitInAir=DCSUnit:inAir() +local UnitCategory=DCSUnit:getDesc().category +if UnitInAir==true and UnitCategory==Unit.Category.HELICOPTER and(not NoHeloCheck)then +local VelocityVec3=DCSUnit:getVelocity() +local Velocity=UTILS.VecNorm(VelocityVec3) +local Coordinate=DCSUnit:getPoint() +local LandHeight=land.getHeight({x=Coordinate.x,y=Coordinate.z}) +local Height=Coordinate.y-LandHeight +if Velocity<1 and Height<=60 then +UnitInAir=false +end +end +self:T3(UnitInAir) +return UnitInAir +end +return nil +end +do +function UNIT:HandleEvent(EventID,EventFunction) +self:EventDispatcher():OnEventForUnit(self:GetName(),EventFunction,self,EventID) +return self +end +function UNIT:UnHandleEvent(EventID) +self:EventDispatcher():RemoveEvent(self,EventID) +return self +end +function UNIT:ResetEvents() +self:EventDispatcher():Reset(self) +return self +end +end +do +function UNIT:IsDetected(TargetUnit) +local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=self:IsTargetDetected(TargetUnit:GetDCSObject()) +return TargetIsDetected +end +function UNIT:IsLOS(TargetUnit) +local IsLOS=self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3()) +return IsLOS +end +function UNIT:KnowUnit(TargetUnit,TypeKnown,DistanceKnown) +if TypeKnown~=false then +TypeKnown=true +end +if DistanceKnown~=false then +DistanceKnown=true +end +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=DCSControllable:getController() +if Controller then +local object=TargetUnit:GetDCSObject() +if object then +self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s",self:GetName(),TargetUnit:GetName(),tostring(TypeKnown),tostring(DistanceKnown))) +Controller:knowTarget(object,TypeKnown,DistanceKnown) +end +end +end +end +end +function UNIT:GetTemplate() +local group=self:GetGroup() +local name=self:GetName() +if group then +local template=group:GetTemplate() +if template then +for _,unit in pairs(template.units)do +if unit.name==name then +return UTILS.DeepCopy(unit) +end +end +end +end +return nil +end +function UNIT:GetTemplatePayload() +local unit=self:GetTemplate() +if unit then +return unit.payload +end +return nil +end +function UNIT:GetTemplatePylons() +local payload=self:GetTemplatePayload() +if payload then +return payload.pylons +end +return nil +end +function UNIT:GetTemplateFuel() +local payload=self:GetTemplatePayload() +if payload then +return payload.fuel +end +return nil +end +function UNIT:EnableEmission(switch) +self:F2(self.UnitName) +local switch=switch or false +local DCSUnit=self:GetDCSObject() +if DCSUnit then +DCSUnit:enableEmission(switch) +end +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:FindByName(ClientName,ClientBriefing,Error) +local ClientFound=_DATABASE:FindClient(ClientName) +if ClientFound then +ClientFound:F({ClientName,ClientBriefing}) +ClientFound:AddBriefing(ClientBriefing) +ClientFound.MessageSwitch=true +return ClientFound +end +if not Error then +error("CLIENT not found for: "..ClientName) +end +end +function CLIENT:Register(ClientName) +local self=BASE:Inherit(self,UNIT:Register(ClientName)) +self.ClientName=ClientName +self.MessageSwitch=true +self.ClientAlive2=false +return self +end +function CLIENT:Transport() +self.ClientTransport=true +return self +end +function CLIENT:AddBriefing(ClientBriefing) +self.ClientBriefing=ClientBriefing +self.ClientBriefingShown=false +return self +end +function CLIENT:AddPlayer(PlayerName) +table.insert(self.Players,PlayerName) +return self +end +function CLIENT:GetPlayers() +return self.Players +end +function CLIENT:GetPlayer() +if#self.Players>0 then +return self.Players[1] +end +return nil +end +function CLIENT:RemovePlayer(PlayerName) +for i,playername in pairs(self.Players)do +if PlayerName==playername then +table.remove(self.Players,i) +break +end +end +return self +end +function CLIENT:RemovePlayers() +self.Players={} +return self +end +function CLIENT:ShowBriefing() +if not self.ClientBriefingShown then +self.ClientBriefingShown=true +local Briefing="" +if self.ClientBriefing and self.ClientBriefing~=""then +Briefing=Briefing..self.ClientBriefing +self:Message(Briefing,60,"Briefing") +end +end +return self +end +function CLIENT:ShowMissionBriefing(MissionBriefing) +self:F({self.ClientName}) +if MissionBriefing then +self:Message(MissionBriefing,60,"Mission Briefing") +end +return self +end +function CLIENT:Reset(ClientName) +self:F() +self._Menus={} +end +function CLIENT:IsMultiSeated() +self:F(self.ClientName) +local ClientMultiSeatedTypes={ +["Mi-8MT"]="Mi-8MT", +["UH-1H"]="UH-1H", +["P-51B"]="P-51B" +} +if self:IsAlive()then +local ClientTypeName=self:GetClientGroupUnit():GetTypeName() +if ClientMultiSeatedTypes[ClientTypeName]then +return true +end +end +return false +end +function CLIENT:Alive(CallBackFunction,...) +self:F() +self.ClientCallBack=CallBackFunction +self.ClientParameters=arg +self.AliveCheckScheduler=SCHEDULER:New(self,self._AliveCheckScheduler,{"Client Alive "..self.ClientName},0.1,5,0.5) +self.AliveCheckScheduler:NoTrace() +return self +end +function CLIENT:_AliveCheckScheduler(SchedulerName) +self:F3({SchedulerName,self.ClientName,self.ClientAlive2,self.ClientBriefingShown,self.ClientCallBack}) +if self:IsAlive()then +if self.ClientAlive2==false then +self:ShowBriefing() +if self.ClientCallBack then +self:T("Calling Callback function") +self.ClientCallBack(self,unpack(self.ClientParameters)) +end +self.ClientAlive2=true +end +else +if self.ClientAlive2==true then +self.ClientAlive2=false +end +end +return true +end +function CLIENT:GetDCSGroup() +self:F3() +local ClientUnit=Unit.getByName(self.ClientName) +local CoalitionsData={AlivePlayersRed=coalition.getPlayers(coalition.side.RED),AlivePlayersBlue=coalition.getPlayers(coalition.side.BLUE)} +for CoalitionId,CoalitionData in pairs(CoalitionsData)do +self:T3({"CoalitionData:",CoalitionData}) +for UnitId,UnitData in pairs(CoalitionData)do +self:T3({"UnitData:",UnitData}) +if UnitData and UnitData:isExist()then +if ClientUnit then +local ClientGroup=ClientUnit:getGroup() +if ClientGroup then +self:T3("ClientGroup = "..self.ClientName) +if ClientGroup:isExist()and UnitData:getGroup():isExist()then +if ClientGroup:getID()==UnitData:getGroup():getID()then +self:T3("Normal logic") +self:T3(self.ClientName.." : group found!") +self.ClientGroupID=ClientGroup:getID() +self.ClientGroupName=ClientGroup:getName() +return ClientGroup +end +else +self:T3("Bug 1.5 logic") +local ClientGroupTemplate=_DATABASE.Templates.Units[self.ClientName].GroupTemplate +self.ClientGroupID=ClientGroupTemplate.groupId +self.ClientGroupName=_DATABASE.Templates.Units[self.ClientName].GroupName +self:T3(self.ClientName.." : group found in bug 1.5 resolvement logic!") +return ClientGroup +end +end +else +end +end +end +end +if ClientUnit then +local ClientGroup=ClientUnit:getGroup() +if ClientGroup then +self:T3("ClientGroup = "..self.ClientName) +if ClientGroup:isExist()then +self:T3("Normal logic") +self:T3(self.ClientName.." : group found!") +return ClientGroup +end +end +end +self.ClientGroupID=nil +self.ClientGroupName=nil +return nil +end +function CLIENT:GetClientGroupID() +self:GetDCSGroup() +return self.ClientGroupID +end +function CLIENT:GetClientGroupName() +self:GetDCSGroup() +return self.ClientGroupName +end +function CLIENT:GetClientGroupUnit() +self:F2() +local ClientDCSUnit=Unit.getByName(self.ClientName) +self:T(self.ClientDCSUnit) +if ClientDCSUnit and ClientDCSUnit:isExist()then +local ClientUnit=_DATABASE:FindUnit(self.ClientName) +return ClientUnit +end +return nil +end +function CLIENT:GetClientGroupDCSUnit() +self:F2() +local ClientDCSUnit=Unit.getByName(self.ClientName) +if ClientDCSUnit and ClientDCSUnit:isExist()then +self:T2(ClientDCSUnit) +return ClientDCSUnit +end +end +function CLIENT:IsTransport() +self:F() +return self.ClientTransport +end +function CLIENT:ShowCargo() +self:F() +local CargoMsg="" +for CargoName,Cargo in pairs(CARGOS)do +if self==Cargo:IsLoadedInClient()then +CargoMsg=CargoMsg..Cargo.CargoName.." Type:"..Cargo.CargoType.." Weight: "..Cargo.CargoWeight.."\n" +end +end +if CargoMsg==""then +CargoMsg="empty" +end +self:Message(CargoMsg,15,"Co-Pilot: Cargo Status",30) +end +function CLIENT:Message(Message,MessageDuration,MessageCategory,MessageInterval,MessageID) +self:F({Message,MessageDuration,MessageCategory,MessageInterval}) +if self.MessageSwitch==true then +if MessageCategory==nil then +MessageCategory="Messages" +end +if MessageID~=nil then +if self.Messages[MessageID]==nil then +self.Messages[MessageID]={} +self.Messages[MessageID].MessageId=MessageID +self.Messages[MessageID].MessageTime=timer.getTime() +self.Messages[MessageID].MessageDuration=MessageDuration +if MessageInterval==nil then +self.Messages[MessageID].MessageInterval=600 +else +self.Messages[MessageID].MessageInterval=MessageInterval +end +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +else +if self:GetClientGroupDCSUnit()and not self:GetClientGroupDCSUnit():inAir()then +if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+10 then +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +self.Messages[MessageID].MessageTime=timer.getTime() +end +else +if timer.getTime()-self.Messages[MessageID].MessageTime>=self.Messages[MessageID].MessageDuration+self.Messages[MessageID].MessageInterval then +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +self.Messages[MessageID].MessageTime=timer.getTime() +end +end +end +else +MESSAGE:New(Message,MessageDuration,MessageCategory):ToClient(self) +end +end +end +STATIC={ +ClassName="STATIC", +} +function STATIC:Register(StaticName) +local self=BASE:Inherit(self,POSITIONABLE:New(StaticName)) +self.StaticName=StaticName +return self +end +function STATIC:Find(DCSStatic) +local StaticName=DCSStatic:getName() +local StaticFound=_DATABASE:FindStatic(StaticName) +return StaticFound +end +function STATIC:FindByName(StaticName,RaiseError) +local StaticFound=_DATABASE:FindStatic(StaticName) +self.StaticName=StaticName +if StaticFound then +return StaticFound +end +if RaiseError==nil or RaiseError==true then +error("STATIC not found for: "..StaticName) +end +return nil +end +function STATIC:Destroy(GenerateEvent) +self:F2(self.ObjectName) +local DCSObject=self:GetDCSObject() +if DCSObject then +local StaticName=DCSObject:getName() +self:F({StaticName=StaticName}) +if GenerateEvent and GenerateEvent==true then +if self:IsAir()then +self:CreateEventCrash(timer.getTime(),DCSObject) +else +self:CreateEventDead(timer.getTime(),DCSObject) +end +elseif GenerateEvent==false then +else +self:CreateEventRemoveUnit(timer.getTime(),DCSObject) +end +DCSObject:destroy() +return true +end +return nil +end +function STATIC:GetDCSObject() +local DCSStatic=StaticObject.getByName(self.StaticName) +if DCSStatic then +return DCSStatic +end +return nil +end +function STATIC:GetUnits() +self:F2({self.StaticName}) +local DCSStatic=self:GetDCSObject() +local Statics={} +if DCSStatic then +Statics[1]=STATIC:Find(DCSStatic) +self:T3(Statics) +return Statics +end +return nil +end +function STATIC:GetThreatLevel() +return 1,"Static" +end +function STATIC:SpawnAt(Coordinate,Heading,Delay) +Heading=Heading or 0 +if Delay and Delay>0 then +SCHEDULER:New(nil,self.SpawnAt,{self,Coordinate,Heading},Delay) +else +local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName) +SpawnStatic:SpawnFromPointVec2(Coordinate,Heading,self.StaticName) +end +return self +end +function STATIC:ReSpawn(CountryID,Delay) +if Delay and Delay>0 then +SCHEDULER:New(nil,self.ReSpawn,{self,CountryID},Delay) +else +CountryID=CountryID or self:GetCountry() +local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,CountryID) +SpawnStatic:Spawn(nil,self.StaticName) +end +return self +end +function STATIC:ReSpawnAt(Coordinate,Heading,Delay) +if Delay and Delay>0 then +SCHEDULER:New(nil,self.ReSpawnAt,{self,Coordinate,Heading},Delay) +else +local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName,self:GetCountry()) +SpawnStatic:SpawnFromCoordinate(Coordinate,Heading,self.StaticName) +end +return self +end +AIRBASE={ +ClassName="AIRBASE", +CategoryName={ +[Airbase.Category.AIRDROME]="Airdrome", +[Airbase.Category.HELIPAD]="Helipad", +[Airbase.Category.SHIP]="Ship", +}, +activerwyno=nil, +} +AIRBASE.Caucasus={ +["Gelendzhik"]="Gelendzhik", +["Krasnodar_Pashkovsky"]="Krasnodar-Pashkovsky", +["Sukhumi_Babushara"]="Sukhumi-Babushara", +["Gudauta"]="Gudauta", +["Batumi"]="Batumi", +["Senaki_Kolkhi"]="Senaki-Kolkhi", +["Kobuleti"]="Kobuleti", +["Kutaisi"]="Kutaisi", +["Tbilisi_Lochini"]="Tbilisi-Lochini", +["Soganlug"]="Soganlug", +["Vaziani"]="Vaziani", +["Anapa_Vityazevo"]="Anapa-Vityazevo", +["Krasnodar_Center"]="Krasnodar-Center", +["Novorossiysk"]="Novorossiysk", +["Krymsk"]="Krymsk", +["Maykop_Khanskaya"]="Maykop-Khanskaya", +["Sochi_Adler"]="Sochi-Adler", +["Mineralnye_Vody"]="Mineralnye Vody", +["Nalchik"]="Nalchik", +["Mozdok"]="Mozdok", +["Beslan"]="Beslan", +} +AIRBASE.Nevada={ +["Creech_AFB"]="Creech AFB", +["Groom_Lake_AFB"]="Groom Lake AFB", +["McCarran_International_Airport"]="McCarran International Airport", +["Nellis_AFB"]="Nellis AFB", +["Beatty_Airport"]="Beatty Airport", +["Boulder_City_Airport"]="Boulder City Airport", +["Echo_Bay"]="Echo Bay", +["Henderson_Executive_Airport"]="Henderson Executive Airport", +["Jean_Airport"]="Jean Airport", +["Laughlin_Airport"]="Laughlin Airport", +["Lincoln_County"]="Lincoln County", +["Mesquite"]="Mesquite", +["Mina_Airport_3Q0"]="Mina Airport 3Q0", +["North_Las_Vegas"]="North Las Vegas", +["Pahute_Mesa_Airstrip"]="Pahute Mesa Airstrip", +["Tonopah_Airport"]="Tonopah Airport", +["Tonopah_Test_Range_Airfield"]="Tonopah Test Range Airfield", +} +AIRBASE.Normandy={ +["Saint_Pierre_du_Mont"]="Saint Pierre du Mont", +["Lignerolles"]="Lignerolles", +["Cretteville"]="Cretteville", +["Maupertus"]="Maupertus", +["Brucheville"]="Brucheville", +["Meautis"]="Meautis", +["Cricqueville_en_Bessin"]="Cricqueville-en-Bessin", +["Lessay"]="Lessay", +["Sainte_Laurent_sur_Mer"]="Sainte-Laurent-sur-Mer", +["Biniville"]="Biniville", +["Cardonville"]="Cardonville", +["Deux_Jumeaux"]="Deux Jumeaux", +["Chippelle"]="Chippelle", +["Beuzeville"]="Beuzeville", +["Azeville"]="Azeville", +["Picauville"]="Picauville", +["Le_Molay"]="Le Molay", +["Longues_sur_Mer"]="Longues-sur-Mer", +["Carpiquet"]="Carpiquet", +["Bazenville"]="Bazenville", +["Sainte_Croix_sur_Mer"]="Sainte-Croix-sur-Mer", +["Beny_sur_Mer"]="Beny-sur-Mer", +["Rucqueville"]="Rucqueville", +["Sommervieu"]="Sommervieu", +["Lantheuil"]="Lantheuil", +["Evreux"]="Evreux", +["Chailey"]="Chailey", +["Needs_Oar_Point"]="Needs Oar Point", +["Funtington"]="Funtington", +["Tangmere"]="Tangmere", +["Ford_AF"]="Ford_AF", +["Goulet"]="Goulet", +["Argentan"]="Argentan", +["Vrigny"]="Vrigny", +["Essay"]="Essay", +["Hauterive"]="Hauterive", +["Barville"]="Barville", +["Conches"]="Conches", +} +AIRBASE.PersianGulf={ +["Abu_Dhabi_International_Airport"]="Abu Dhabi Intl", +["Abu_Musa_Island_Airport"]="Abu Musa Island", +["Al_Ain_International_Airport"]="Al Ain Intl", +["Al_Bateen_Airport"]="Al-Bateen", +["Al_Dhafra_AB"]="Al Dhafra AFB", +["Al_Maktoum_Intl"]="Al Maktoum Intl", +["Al_Minhad_AB"]="Al Minhad AFB", +["Bandar_Abbas_Intl"]="Bandar Abbas Intl", +["Bandar_Lengeh"]="Bandar Lengeh", +["Bandar_e_Jask_airfield"]="Bandar-e-Jask", +["Dubai_Intl"]="Dubai Intl", +["Fujairah_Intl"]="Fujairah Intl", +["Havadarya"]="Havadarya", +["Jiroft_Airport"]="Jiroft", +["Kerman_Airport"]="Kerman", +["Khasab"]="Khasab", +["Kish_International_Airport"]="Kish Intl", +["Lar_Airbase"]="Lar", +["Lavan_Island_Airport"]="Lavan Island", +["Liwa_Airbase"]="Liwa AFB", +["Qeshm_Island"]="Qeshm Island", +["Ras_Al_Khaimah"]="Ras Al Khaimah Intl", +["Sas_Al_Nakheel_Airport"]="Sas Al Nakheel", +["Sharjah_Intl"]="Sharjah Intl", +["Shiraz_International_Airport"]="Shiraz Intl", +["Sir_Abu_Nuayr"]="Sir Abu Nuayr", +["Sirri_Island"]="Sirri Island", +["Tunb_Island_AFB"]="Tunb Island AFB", +["Tunb_Kochak"]="Tunb Kochak", +} +AIRBASE.TheChannel={ +["Abbeville_Drucat"]="Abbeville Drucat", +["Merville_Calonne"]="Merville Calonne", +["Saint_Omer_Longuenesse"]="Saint Omer Longuenesse", +["Dunkirk_Mardyck"]="Dunkirk Mardyck", +["Manston"]="Manston", +["Hawkinge"]="Hawkinge", +["Lympne"]="Lympne", +["Detling"]="Detling", +["High_Halden"]="High Halden", +} +AIRBASE.Syria={ +["Kuweires"]="Kuweires", +["Marj_Ruhayyil"]="Marj Ruhayyil", +["Kiryat_Shmona"]="Kiryat Shmona", +["Marj_as_Sultan_North"]="Marj as Sultan North", +["Eyn_Shemer"]="Eyn Shemer", +["Incirlik"]="Incirlik", +["Damascus"]="Damascus", +["Bassel_Al_Assad"]="Bassel Al-Assad", +["Aleppo"]="Aleppo", +["Qabr_as_Sitt"]="Qabr as Sitt", +["Wujah_Al_Hajar"]="Wujah Al Hajar", +["Al_Dumayr"]="Al-Dumayr", +["Hatay"]="Hatay", +["Haifa"]="Haifa", +["Khalkhalah"]="Khalkhalah", +["Megiddo"]="Megiddo", +["Rayak"]="Rayak", +["Mezzeh"]="Mezzeh", +["King_Hussein_Air_College"]="King Hussein Air College", +["Jirah"]="Jirah", +["Taftanaz"]="Taftanaz", +["Rene_Mouawad"]="Rene Mouawad", +["Ramat_David"]="Ramat David", +["Minakh"]="Minakh", +["Adana_Sakirpasa"]="Adana Sakirpasa", +["Marj_as_Sultan_South"]="Marj as Sultan South", +["Hama"]="Hama", +["Al_Qusayr"]="Al Qusayr", +["Palmyra"]="Palmyra", +["Tabqa"]="Tabqa", +["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", +["An_Nasiriyah"]="An Nasiriyah", +["Abu_al_Duhur"]="Abu al-Duhur", +["H4"]="H4", +["Gaziantep"]="Gaziantep", +["Rosh_Pina"]="Rosh Pina", +["Sayqal"]="Sayqal", +["Shayrat"]="Shayrat", +["Tiyas"]="Tiyas", +["Tha_lah"]="Tha'lah", +["Naqoura"]="Naqoura", +} +AIRBASE.TerminalType={ +Runway=16, +HelicopterOnly=40, +Shelter=68, +OpenMed=72, +OpenBig=104, +OpenMedOrBig=176, +HelicopterUsable=216, +FighterAircraft=244, +} +function AIRBASE:Register(AirbaseName) +local self=BASE:Inherit(self,POSITIONABLE:New(AirbaseName)) +self.AirbaseName=AirbaseName +self.AirbaseID=self:GetID(true) +self.descriptors=self:GetDesc() +self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME +if self.category==Airbase.Category.AIRDROME then +self.isAirdrome=true +elseif self.category==Airbase.Category.HELIPAD then +self.isHelipad=true +elseif self.category==Airbase.Category.SHIP then +self.isShip=true +else +self:E("ERROR: Unknown airbase category!") +end +self:_InitParkingSpots() +local vec2=self:GetVec2() +self:GetCoordinate() +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 +return self +end +function AIRBASE:Find(DCSAirbase) +local AirbaseName=DCSAirbase:getName() +local AirbaseFound=_DATABASE:FindAirbase(AirbaseName) +return AirbaseFound +end +function AIRBASE:FindByName(AirbaseName) +local AirbaseFound=_DATABASE:FindAirbase(AirbaseName) +return AirbaseFound +end +function AIRBASE:FindByID(id) +for name,_airbase in pairs(_DATABASE.AIRBASES)do +local airbase=_airbase +local aid=tonumber(airbase:GetID(true)) +if aid==id then +return airbase +end +end +return nil +end +function AIRBASE:GetDCSObject() +local DCSAirbase=Airbase.getByName(self.AirbaseName) +if DCSAirbase then +return DCSAirbase +end +return nil +end +function AIRBASE:GetZone() +return self.AirbaseZone +end +function AIRBASE.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: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 +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 +self.NparkingTotal=self.NparkingTotal+1 +for _,terminalType in pairs(AIRBASE.TerminalType)do +if self._CheckTerminalType(terminalType,park.TerminalType)then +self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 +end +end +self.parkingByID[park.TerminalID]=park +table.insert(self.parking,park) +end +return self +end +function AIRBASE:_GetParkingSpotByID(TerminalID) +return self.parkingByID[TerminalID] +end +function AIRBASE:GetParkingSpotsTable(termtype) +local parkingdata=self:GetParkingData(false) +local parkingfree=self:GetParkingData(true) +local function _isfree(_tocheck) +for _,_spot in pairs(parkingfree)do +if _spot.Term_Index==_tocheck.Term_Index then +return true +end +end +return false +end +local spots={} +for _,_spot in pairs(parkingdata)do +if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)then +local spot=self:_GetParkingSpotByID(_spot.Term_Index) +if spot then +spot.Free=_isfree(_spot) +spot.TOAC=_spot.TO_AC +table.insert(spots,spot) +else +self:E(string.format("ERROR: Parking spot %s is nil!",tostring(_spot.Term_Index))) +end +end +end +return spots +end +function AIRBASE:GetFreeParkingSpotsTable(termtype,allowTOAC) +local parkingfree=self:GetParkingData(true) +local freespots={} +for _,_spot in pairs(parkingfree)do +if AIRBASE._CheckTerminalType(_spot.Term_Type,termtype)and _spot.Term_Index>0 then +if(allowTOAC and allowTOAC==true)or _spot.TO_AC==false then +local spot=self:_GetParkingSpotByID(_spot.Term_Index) +spot.Free=true +spot.TOAC=_spot.TO_AC +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 termial 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=group:GetUnit(1) +local _aircraftsize,ax,ay,az=aircraft:GetObjectSize() +local _nspots=nspots or group:GetSize() +self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial 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:I(string.format("%s: Parking spot id %d occupied.",airport,_termid)) +else +self:I(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:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.",airport,_termid,nvalid,_nspots)) +end +end +if nvalid>=_nspots then +return validspots +end +end +end +return validspots +end +function AIRBASE:_CheckParkingLists(TerminalID) +if self.parkingBlacklist and#self.parkingBlacklist>0 then +for _,terminalID in pairs(self.parkingBlacklist or{})do +if terminalID==TerminalID then +return false +end +end +end +if self.parkingWhitelist and#self.parkingWhitelist>0 then +for _,terminalID in pairs(self.parkingWhitelist or{})do +if terminalID==TerminalID then +return true +end +end +return false +end +return true +end +function AIRBASE._CheckTerminalType(Term_Type,termtype) +if Term_Type==nil then +return false +end +if termtype==nil then +if Term_Type==AIRBASE.TerminalType.Runway then +return false +else +return true +end +end +local match=false +if Term_Type==termtype then +match=true +end +if termtype==AIRBASE.TerminalType.OpenMedOrBig then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then +match=true +end +elseif termtype==AIRBASE.TerminalType.HelicopterUsable then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.HelicopterOnly then +match=true +end +elseif termtype==AIRBASE.TerminalType.FighterAircraft then +if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then +match=true +end +end +return match +end +function AIRBASE: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 then +exception=1 +elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and +name~=AIRBASE.Syria.Minakh and +name~=AIRBASE.Syria.Damascus and +name~=AIRBASE.Syria.Khalkhalah and +name~=AIRBASE.Syria.Marj_Ruhayyil and +name~=AIRBASE.Syria.Beirut_Rafic_Hariri then +exception=2 +end +local function f(i) +local j +if exception==1 then +j=N-(i-1) +elseif exception==2 then +if i<=N2 then +j=i+N2 +else +j=i-N2 +end +else +if i%2==0 then +j=i-1 +else +j=i+1 +end +end +if name==AIRBASE.Syria.Beirut_Rafic_Hariri then +if i==1 then +j=3 +elseif i==2 then +j=6 +elseif i==3 then +j=1 +elseif i==4 then +j=5 +elseif i==5 then +j=4 +elseif i==6 then +j=2 +end +end +if name==AIRBASE.Syria.Ramat_David then +if i==1 then +j=4 +elseif i==2 then +j=6 +elseif i==3 then +j=5 +elseif i==4 then +j=1 +elseif i==5 then +j=3 +elseif i==6 then +j=2 +end +end +return j +end +for i=1,N do +local j=f(i) +local c1=runwaycoords[i] +local c2=runwaycoords[j] +local hdg=c1:HeadingTo(c2) +local idx=string.format("%02d",UTILS.Round((hdg-magvar)/10,0)) +local runway={} +runway.heading=hdg +runway.idx=idx +runway.length=c1:Get2DDistance(c2) +runway.position=c1 +runway.endpoint=c2 +if mark then +runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m, i=%d, j=%d",runway.idx,runway.heading,magvar,runway.length,i,j)) +end +table.insert(runways,runway) +end +return runways +end +function AIRBASE:SetActiveRunway(iactive) +self.activerwyno=iactive +end +function AIRBASE:GetActiveRunway(magvar) +local runways=self:GetRunwayData(magvar) +if self.activerwyno then +return runways[self.activerwyno] +end +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 +local alpha=math.rad(runway.heading) +local Vrunway={x=math.cos(alpha),y=0,z=math.sin(alpha)} +local dot=UTILS.VecDot(Vwind,Vrunway) +if dotmin==nil or dot radius %.1f m. Despawn = %s.",self:GetName(),unit:GetName(),group:GetName(),_i,dist,radius,tostring(despawn))) +end +end +else +self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(),unit:GetName(),group:GetName())) +end +end +else +self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(),group:GetName())) +end +return false +end +SCENERY={ +ClassName="SCENERY", +} +function SCENERY:Register(SceneryName,SceneryObject) +local self=BASE:Inherit(self,POSITIONABLE:New(SceneryName)) +self.SceneryName=SceneryName +self.SceneryObject=SceneryObject +return self +end +function SCENERY:GetDCSObject() +return self.SceneryObject +end +function SCENERY:GetThreatLevel() +return 0,"Scenery" +end +function SCENERY:FindByName(name) +local findAirbase=function() +local airbases=AIRBASE.GetAllAirbases() +for index,airbase in pairs(airbases)do +local surftype=airbase:GetCoordinate():GetSurfaceType() +if surftype~=land.SurfaceType.SHALLOW_WATER and surftype~=land.SurfaceType.WATER then +return airbase:GetCoordinate() +end +end +return nil +end +local sceneryScan=function(scancoord) +if scancoord~=nil then +local _,_,sceneryfound,_,_,scenerylist=scancoord:ScanObjects(200,false,false,true) +if sceneryfound==true then +scenerylist[1].id_=name +SCENERY.SceneryObject=SCENERY:Register(scenerylist[1].id_,scenerylist[1]) +return SCENERY.SceneryObject +end +end +return nil +end +if SCENERY.SceneryObject then +SCENERY.SceneryObject.SceneryObject.id_=name +SCENERY.SceneryObject.SceneryName=name +return SCENERY:Register(SCENERY.SceneryObject.SceneryObject.id_,SCENERY.SceneryObject.SceneryObject) +else +return sceneryScan(findAirbase()) +end +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.0" +function MARKER:New(Coordinate,Text) +local self=BASE:Inherit(self,FSM:New()) +self.coordinate=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: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.tocoaliton=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.tocoaliton=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.tocoaliton=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.tocoaliton 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 +self:T3(self.lid..string.format("Captured event MarkAdded 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 +self:Changed(EventData) +self:TextChanged(tostring(EventData.MarkText)) +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 +CARGOS={} +do +CARGO={ +ClassName="CARGO", +Type=nil, +Name=nil, +Weight=nil, +CargoObject=nil, +CargoCarrier=nil, +Representable=false, +Slingloadable=false, +Moveable=false, +Containable=false, +Reported={}, +} +function CARGO:New(Type,Name,Weight,LoadRadius,NearRadius) +local self=BASE:Inherit(self,FSM:New()) +self:F({Type,Name,Weight,LoadRadius,NearRadius}) +self:SetStartState("UnLoaded") +self:AddTransition({"UnLoaded","Boarding"},"Board","Boarding") +self:AddTransition("Boarding","Boarding","Boarding") +self:AddTransition("Boarding","CancelBoarding","UnLoaded") +self:AddTransition("Boarding","Load","Loaded") +self:AddTransition("UnLoaded","Load","Loaded") +self:AddTransition("Loaded","UnBoard","UnBoarding") +self:AddTransition("UnBoarding","UnBoarding","UnBoarding") +self:AddTransition("UnBoarding","UnLoad","UnLoaded") +self:AddTransition("Loaded","UnLoad","UnLoaded") +self:AddTransition("*","Damaged","Damaged") +self:AddTransition("*","Destroyed","Destroyed") +self:AddTransition("*","Respawn","UnLoaded") +self:AddTransition("*","Reset","UnLoaded") +self.Type=Type +self.Name=Name +self.Weight=Weight or 0 +self.CargoObject=nil +self.CargoCarrier=nil +self.Representable=false +self.Slingloadable=false +self.Moveable=false +self.Containable=false +self.CargoLimit=0 +self.LoadRadius=LoadRadius or 500 +self:SetDeployed(false) +self.CargoScheduler=SCHEDULER:New() +CARGOS[self.Name]=self +return self +end +function CARGO:FindByName(CargoName) +local CargoFound=_DATABASE:FindCargo(CargoName) +return CargoFound +end +function CARGO:GetX() +if self:IsLoaded()then +return self.CargoCarrier:GetCoordinate().x +else +return self.CargoObject:GetCoordinate().x +end +end +function CARGO:GetY() +if self:IsLoaded()then +return self.CargoCarrier:GetCoordinate().z +else +return self.CargoObject:GetCoordinate().z +end +end +function CARGO:GetHeading() +if self:IsLoaded()then +return self.CargoCarrier:GetHeading() +else +return self.CargoObject:GetHeading() +end +end +function CARGO:CanSlingload() +return false +end +function CARGO:CanBoard() +return true +end +function CARGO:CanUnboard() +return true +end +function CARGO:CanLoad() +return true +end +function CARGO:CanUnload() +return true +end +function CARGO:Destroy() +if self.CargoObject then +self.CargoObject:Destroy() +end +self:Destroyed() +end +function CARGO:GetName() +return self.Name +end +function CARGO:GetObject() +if self:IsLoaded()then +return self.CargoCarrier +else +return self.CargoObject +end +end +function CARGO:GetObjectName() +if self:IsLoaded()then +return self.CargoCarrier:GetName() +else +return self.CargoObject:GetName() +end +end +function CARGO:GetCount() +return 1 +end +function CARGO:GetType() +return self.Type +end +function CARGO:GetTransportationMethod() +return self.TransportationMethod +end +function CARGO:GetCoalition() +if self:IsLoaded()then +return self.CargoCarrier:GetCoalition() +else +return self.CargoObject:GetCoalition() +end +end +function CARGO:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO:IsDestroyed() +return self:Is("Destroyed") +end +function CARGO:IsLoaded() +return self:Is("Loaded") +end +function CARGO:IsLoadedInCarrier(Carrier) +return self.CargoCarrier and self.CargoCarrier:GetName()==Carrier:GetName() +end +function CARGO:IsUnLoaded() +return self:Is("UnLoaded") +end +function CARGO:IsBoarding() +return self:Is("Boarding") +end +function CARGO:IsUnboarding() +return self:Is("UnBoarding") +end +function CARGO:IsAlive() +if self:IsLoaded()then +return self.CargoCarrier:IsAlive() +else +return self.CargoObject:IsAlive() +end +end +function CARGO:SetDeployed(Deployed) +self.Deployed=Deployed +end +function CARGO:IsDeployed() +return self.Deployed +end +function CARGO:Spawn(PointVec2) +self:F() +end +function CARGO:Flare(FlareColor) +if self:IsUnLoaded()then +trigger.action.signalFlare(self.CargoObject:GetVec3(),FlareColor,0) +end +end +function CARGO:FlareWhite() +self:Flare(trigger.flareColor.White) +end +function CARGO:FlareYellow() +self:Flare(trigger.flareColor.Yellow) +end +function CARGO:FlareGreen() +self:Flare(trigger.flareColor.Green) +end +function CARGO:FlareRed() +self:Flare(trigger.flareColor.Red) +end +function CARGO:Smoke(SmokeColor,Radius) +if self:IsUnLoaded()then +if Radius then +trigger.action.smoke(self.CargoObject:GetRandomVec3(Radius),SmokeColor) +else +trigger.action.smoke(self.CargoObject:GetVec3(),SmokeColor) +end +end +end +function CARGO:SmokeGreen() +self:Smoke(trigger.smokeColor.Green,Range) +end +function CARGO:SmokeRed() +self:Smoke(trigger.smokeColor.Red,Range) +end +function CARGO:SmokeWhite() +self:Smoke(trigger.smokeColor.White,Range) +end +function CARGO:SmokeOrange() +self:Smoke(trigger.smokeColor.Orange,Range) +end +function CARGO:SmokeBlue() +self:Smoke(trigger.smokeColor.Blue,Range) +end +function CARGO:SetLoadRadius(LoadRadius) +self.LoadRadius=LoadRadius or 150 +end +function CARGO:GetLoadRadius() +return self.LoadRadius +end +function CARGO:IsInLoadRadius(Coordinate) +self:F({Coordinate,LoadRadius=self.LoadRadius}) +local Distance=0 +if self:IsUnLoaded()then +local CargoCoordinate=self.CargoObject:GetCoordinate() +Distance=Coordinate:Get2DDistance(CargoCoordinate) +self:T(Distance) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO:IsInReportRadius(Coordinate) +self:F({Coordinate}) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +self:T(Distance) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO:IsNear(Coordinate,NearRadius) +if self.CargoObject:IsAlive()then +local Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=NearRadius then +return true +end +end +return false +end +function CARGO:IsInZone(Zone) +if self:IsLoaded()then +return Zone:IsPointVec2InZone(self.CargoCarrier:GetPointVec2()) +else +if self.CargoObject:GetSize()~=0 then +return Zone:IsPointVec2InZone(self.CargoObject:GetPointVec2()) +else +return false +end +end +return nil +end +function CARGO:GetPointVec2() +return self.CargoObject:GetPointVec2() +end +function CARGO:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO:GetWeight() +return self.Weight +end +function CARGO:SetWeight(Weight) +self.Weight=Weight +return self +end +function CARGO:GetVolume() +return self.Volume +end +function CARGO:SetVolume(Volume) +self.Volume=Volume +return self +end +function CARGO:MessageToGroup(Message,CarrierGroup,Name) +MESSAGE:New(Message,20,"Cargo "..self:GetName()):ToGroup(CarrierGroup) +end +function CARGO:Report(ReportText,Action,CarrierGroup) +if not self.Reported[CarrierGroup]or not self.Reported[CarrierGroup][Action]then +self.Reported[CarrierGroup]={} +self.Reported[CarrierGroup][Action]=true +self:MessageToGroup(ReportText,CarrierGroup) +if self.ReportFlareColor then +if not self.Reported[CarrierGroup]["Flaring"]then +self:Flare(self.ReportFlareColor) +self.Reported[CarrierGroup]["Flaring"]=true +end +end +if self.ReportSmokeColor then +if not self.Reported[CarrierGroup]["Smoking"]then +self:Smoke(self.ReportSmokeColor) +self.Reported[CarrierGroup]["Smoking"]=true +end +end +end +end +function CARGO:ReportFlare(FlareColor) +self.ReportFlareColor=FlareColor +end +function CARGO:ReportSmoke(SmokeColor) +self.ReportSmokeColor=SmokeColor +end +function CARGO:ReportReset(Action,CarrierGroup) +self.Reported[CarrierGroup][Action]=nil +end +function CARGO:ReportResetAll(CarrierGroup) +self.Reported[CarrierGroup]=nil +end +function CARGO:RespawnOnDestroyed(RespawnDestroyed) +if RespawnDestroyed then +self.onenterDestroyed=function(self) +self:Respawn() +end +else +self.onenterDestroyed=nil +end +end +end +do +CARGO_REPRESENTABLE={ +ClassName="CARGO_REPRESENTABLE" +} +function CARGO_REPRESENTABLE:New(CargoObject,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO:New(Type,Name,0,LoadRadius,NearRadius)) +self:F({Type,Name,LoadRadius,NearRadius}) +local Desc=CargoObject:GetDesc() +self:T({Desc=Desc}) +local Weight=math.random(80,120) +if Desc then +if Desc.typeName=="2B11 mortar"then +Weight=210 +else +Weight=Desc.massEmpty +end +end +self:SetWeight(Weight) +return self +end +function CARGO_REPRESENTABLE:Destroy() +self:F({CargoName=self:GetName()}) +return self +end +function CARGO_REPRESENTABLE:RouteTo(ToPointVec2,Speed) +self:F2(ToPointVec2) +local Points={} +local PointStartVec2=self.CargoObject:GetPointVec2() +Points[#Points+1]=PointStartVec2:WaypointGround(Speed) +Points[#Points+1]=ToPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,2) +return self +end +function CARGO_REPRESENTABLE:MessageToGroup(Message,TaskGroup,Name) +local CoordinateZone=ZONE_RADIUS:New("Zone",self:GetCoordinate():GetVec2(),500) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:F({NearUnit=NearUnit}) +local NearUnitCoalition=NearUnit:GetCoalition() +local CargoCoalition=self:GetCoalition() +if NearUnitCoalition==CargoCoalition then +local Attributes=NearUnit:GetDesc() +self:F({Desc=Attributes}) +if NearUnit:HasAttribute("Trucks")then +MESSAGE:New(Message,20,NearUnit:GetCallsign().." reporting - Cargo "..self:GetName()):ToGroup(TaskGroup) +break +end +end +end +end +end +do +CARGO_REPORTABLE={ +ClassName="CARGO_REPORTABLE" +} +function CARGO_REPORTABLE:New(Type,Name,Weight,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO:New(Type,Name,Weight,LoadRadius,NearRadius)) +self:F({Type,Name,Weight,LoadRadius,NearRadius}) +return self +end +function CARGO_REPORTABLE:MessageToGroup(Message,TaskGroup,Name) +MESSAGE:New(Message,20,"Cargo "..self:GetName().." reporting"):ToGroup(TaskGroup) +end +end +do +CARGO_PACKAGE={ +ClassName="CARGO_PACKAGE" +} +function CARGO_PACKAGE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoCarrier,Type,Name,Weight,LoadRadius,NearRadius)) +self:F({Type,Name,Weight,LoadRadius,NearRadius}) +self:T(CargoCarrier) +self.CargoCarrier=CargoCarrier +return self +end +function CARGO_PACKAGE:onafterOnBoard(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +self:F() +self.CargoInAir=self.CargoCarrier:InAir() +self:T(self.CargoInAir) +if not self.CargoInAir then +local Points={} +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +self:T({CargoCarrierHeading,CargoDeployHeading}) +local CargoDeployPointVec2=CargoCarrier:GetPointVec2():Translate(BoardDistance,CargoDeployHeading) +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoCarrier:TaskRoute(Points) +self.CargoCarrier:SetTask(TaskRoute,1) +end +self:Boarded(CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +end +function CARGO_PACKAGE:IsNear(CargoCarrier) +self:F() +local CargoCarrierPoint=CargoCarrier:GetCoordinate() +local Distance=CargoCarrierPoint:Get2DDistance(self.CargoCarrier:GetCoordinate()) +self:T(Distance) +if Distance<=self.NearRadius then +return true +else +return false +end +end +function CARGO_PACKAGE:onafterOnBoarded(From,Event,To,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +self:F() +if self:IsNear(CargoCarrier)then +self:__Load(1,CargoCarrier,Speed,LoadDistance,Angle) +else +self:__Boarded(1,CargoCarrier,Speed,BoardDistance,LoadDistance,Angle) +end +end +function CARGO_PACKAGE:onafterUnBoard(From,Event,To,CargoCarrier,Speed,UnLoadDistance,UnBoardDistance,Radius,Angle) +self:F() +self.CargoInAir=self.CargoCarrier:InAir() +self:T(self.CargoInAir) +if not self.CargoInAir then +self:_Next(self.FsmP.UnLoad,UnLoadDistance,Angle) +local Points={} +local StartPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +self:T({CargoCarrierHeading,CargoDeployHeading}) +local CargoDeployPointVec2=StartPointVec2:Translate(UnBoardDistance,CargoDeployHeading) +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=CargoCarrier:TaskRoute(Points) +CargoCarrier:SetTask(TaskRoute,1) +end +self:__UnBoarded(1,CargoCarrier,Speed) +end +function CARGO_PACKAGE:onafterUnBoarded(From,Event,To,CargoCarrier,Speed) +self:F() +if self:IsNear(CargoCarrier)then +self:__UnLoad(1,CargoCarrier,Speed) +else +self:__UnBoarded(1,CargoCarrier,Speed) +end +end +function CARGO_PACKAGE:onafterLoad(From,Event,To,CargoCarrier,Speed,LoadDistance,Angle) +self:F() +self.CargoCarrier=CargoCarrier +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=StartPointVec2:Translate(LoadDistance,CargoDeployHeading) +local Points={} +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoCarrier:TaskRoute(Points) +self.CargoCarrier:SetTask(TaskRoute,1) +end +function CARGO_PACKAGE:onafterUnLoad(From,Event,To,CargoCarrier,Speed,Distance,Angle) +self:F() +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=StartPointVec2:Translate(Distance,CargoDeployHeading) +self.CargoCarrier=CargoCarrier +local Points={} +Points[#Points+1]=StartPointVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoCarrier:TaskRoute(Points) +self.CargoCarrier:SetTask(TaskRoute,1) +end +end +do +CARGO_UNIT={ +ClassName="CARGO_UNIT" +} +function CARGO_UNIT:New(CargoUnit,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoUnit,Type,Name,LoadRadius,NearRadius)) +self:T({Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) +self.CargoObject=CargoUnit +self:SetEventPriority(5) +return self +end +function CARGO_UNIT:onenterUnBoarding(From,Event,To,ToPointVec2,NearRadius) +self:F({From,Event,To,ToPointVec2,NearRadius}) +local Angle=180 +local Speed=60 +local DeployDistance=9 +local RouteDistance=60 +if From=="Loaded"then +if not self:IsDestroyed()then +local CargoCarrier=self.CargoCarrier +if CargoCarrier:IsAlive()then +local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoRoutePointVec2=CargoCarrierPointVec2:Translate(RouteDistance,CargoDeployHeading) +local FromDirectionVec3=CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2 or CargoRoutePointVec2) +local FromAngle=CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3) +local FromPointVec2=CargoCarrierPointVec2:Translate(DeployDistance,FromAngle) +ToPointVec2=ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius(NearRadius,DeployDistance) +if self.CargoObject then +if CargoCarrier:IsShip()then +self.CargoObject:ReSpawnAt(ToPointVec2,CargoDeployHeading) +else +self.CargoObject:ReSpawnAt(FromPointVec2,CargoDeployHeading) +end +self:F({"CargoUnits:",self.CargoObject:GetGroup():GetName()}) +self.CargoCarrier=nil +local Points={} +Points[#Points+1]=FromPointVec2:WaypointGround(Speed,"Vee") +Points[#Points+1]=ToPointVec2:WaypointGround(Speed,"Vee") +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,1) +self:__UnBoarding(1,ToPointVec2,NearRadius) +end +else +self:Destroyed() +end +end +end +end +function CARGO_UNIT:onleaveUnBoarding(From,Event,To,ToPointVec2,NearRadius) +self:F({From,Event,To,ToPointVec2,NearRadius}) +local Angle=180 +local Speed=10 +local Distance=5 +if From=="UnBoarding"then +return true +end +end +function CARGO_UNIT:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius) +self:F({From,Event,To,ToPointVec2,NearRadius}) +self.CargoInAir=self.CargoObject:InAir() +self:T(self.CargoInAir) +if not self.CargoInAir then +end +self:__UnLoad(1,ToPointVec2,NearRadius) +end +function CARGO_UNIT:onenterUnLoaded(From,Event,To,ToPointVec2) +self:F({ToPointVec2,From,Event,To}) +local Angle=180 +local Speed=10 +local Distance=5 +if From=="Loaded"then +local StartPointVec2=self.CargoCarrier:GetPointVec2() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployCoord=StartPointVec2:Translate(Distance,CargoDeployHeading) +ToPointVec2=ToPointVec2 or COORDINATE:New(CargoDeployCoord.x,CargoDeployCoord.z) +if self.CargoObject then +self.CargoObject:ReSpawnAt(ToPointVec2,0) +self.CargoCarrier=nil +end +end +if self.OnUnLoadedCallBack then +self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters)) +self.OnUnLoadedCallBack=nil +end +end +function CARGO_UNIT:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...) +self:F({From,Event,To,CargoCarrier,NearRadius=NearRadius}) +self.CargoInAir=self.CargoObject:InAir() +local Desc=self.CargoObject:GetDesc() +local MaxSpeed=Desc.speedMaxOffRoad +local TypeName=Desc.typeName +if not self.CargoInAir then +local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius()+5 +if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then +self:Load(CargoCarrier,NearRadius,...) +else +if MaxSpeed and MaxSpeed==0 or TypeName and TypeName=="Stinger comm"then +self:Load(CargoCarrier,NearRadius,...) +else +local Speed=90 +local Angle=180 +local Distance=0 +local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading) +self.CargoObject:OptionAlarmStateGreen() +local Points={} +local PointStartVec2=self.CargoObject:GetPointVec2() +Points[#Points+1]=PointStartVec2:WaypointGround(Speed) +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed) +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,2) +self:__Boarding(-5,CargoCarrier,NearRadius,...) +self.RunCount=0 +end +end +end +end +function CARGO_UNIT:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...) +self:F({From,Event,To,CargoCarrier:GetName(),NearRadius=NearRadius}) +self:F({IsAlive=self.CargoObject:IsAlive()}) +if CargoCarrier and CargoCarrier:IsAlive()then +if(CargoCarrier:IsAir()and not CargoCarrier:InAir())or true then +local NearRadius=NearRadius or CargoCarrier:GetBoundingRadius(NearRadius)+5 +if self:IsNear(CargoCarrier:GetPointVec2(),NearRadius)then +self:__Load(-1,CargoCarrier,...) +else +if self:IsNear(CargoCarrier:GetPointVec2(),20)then +self:__Boarding(-1,CargoCarrier,NearRadius,...) +self.RunCount=self.RunCount+1 +else +self:__Boarding(-2,CargoCarrier,NearRadius,...) +self.RunCount=self.RunCount+2 +end +if self.RunCount>=40 then +self.RunCount=0 +local Speed=90 +local Angle=180 +local Distance=0 +local CargoCarrierPointVec2=CargoCarrier:GetPointVec2() +local CargoCarrierHeading=CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployPointVec2=CargoCarrierPointVec2:Translate(Distance,CargoDeployHeading) +self.CargoObject:OptionAlarmStateGreen() +local Points={} +local PointStartVec2=self.CargoObject:GetPointVec2() +Points[#Points+1]=PointStartVec2:WaypointGround(Speed,"Off road") +Points[#Points+1]=CargoDeployPointVec2:WaypointGround(Speed,"Off road") +local TaskRoute=self.CargoObject:TaskRoute(Points) +self.CargoObject:SetTask(TaskRoute,0.2) +end +end +else +self.CargoObject:MessageToGroup("Cancelling Boarding... Get back on the ground!",5,CargoCarrier:GetGroup(),self:GetName()) +self:CancelBoarding(CargoCarrier,NearRadius,...) +self.CargoObject:SetCommand(self.CargoObject:CommandStopRoute(true)) +end +else +self:E("Something is wrong") +end +end +function CARGO_UNIT:onenterLoaded(From,Event,To,CargoCarrier) +self:F({From,Event,To,CargoCarrier}) +self.CargoCarrier=CargoCarrier +if self.CargoObject then +self.CargoObject:Destroy(false) +end +end +function CARGO_UNIT:GetTransportationMethod() +if self:IsLoaded()then +return"for unboarding" +else +if self:IsUnLoaded()then +return"for boarding" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +do +CARGO_SLINGLOAD={ +ClassName="CARGO_SLINGLOAD" +} +function CARGO_SLINGLOAD:New(CargoStatic,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius)) +self:F({Type,Name,NearRadius}) +self.CargoObject=CargoStatic +_EVENTDISPATCHER:CreateEventNewCargo(self) +self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) +self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) +self:SetEventPriority(4) +self.NearRadius=NearRadius or 25 +return self +end +function CARGO_SLINGLOAD:OnEventCargoDead(EventData) +local Destroyed=false +if self:IsDestroyed()or self:IsUnLoaded()then +if self.CargoObject:GetName()==EventData.IniUnitName then +if not self.NoDestroy then +Destroyed=true +end +end +end +if Destroyed then +self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()}) +self:Destroyed() +end +end +function CARGO_SLINGLOAD:CanSlingload() +return true +end +function CARGO_SLINGLOAD:CanBoard() +return false +end +function CARGO_SLINGLOAD:CanUnboard() +return false +end +function CARGO_SLINGLOAD:CanLoad() +return false +end +function CARGO_SLINGLOAD:CanUnload() +return false +end +function CARGO_SLINGLOAD:IsInReportRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO_SLINGLOAD:IsInLoadRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.NearRadius then +return true +end +end +return false +end +function CARGO_SLINGLOAD:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO_SLINGLOAD:IsAlive() +local Alive=true +if self:IsLoaded()then +Alive=Alive==true and self.CargoCarrier:IsAlive() +else +Alive=Alive==true and self.CargoObject:IsAlive() +end +return Alive +end +function CARGO_SLINGLOAD:RouteTo(Coordinate) +end +function CARGO_SLINGLOAD:IsNear(CargoCarrier,NearRadius) +return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius) +end +function CARGO_SLINGLOAD:Respawn() +if self.CargoObject then +self.CargoObject:ReSpawn() +self:__Reset(-0.1) +end +end +function CARGO_SLINGLOAD:onafterReset() +if self.CargoObject then +self:SetDeployed(false) +self:SetStartState("UnLoaded") +self.CargoCarrier=nil +_EVENTDISPATCHER:CreateEventNewCargo(self) +end +end +function CARGO_SLINGLOAD:GetTransportationMethod() +if self:IsLoaded()then +return"for sling loading" +else +if self:IsUnLoaded()then +return"for sling loading" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +do +CARGO_CRATE={ +ClassName="CARGO_CRATE" +} +function CARGO_CRATE:New(CargoStatic,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPRESENTABLE:New(CargoStatic,Type,Name,nil,LoadRadius,NearRadius)) +self:F({Type,Name,NearRadius}) +self.CargoObject=CargoStatic +_EVENTDISPATCHER:CreateEventNewCargo(self) +self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) +self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) +self:SetEventPriority(4) +self.NearRadius=NearRadius or 25 +return self +end +function CARGO_CRATE:OnEventCargoDead(EventData) +local Destroyed=false +if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()then +if self.CargoObject:GetName()==EventData.IniUnitName then +if not self.NoDestroy then +Destroyed=true +end +end +else +if self:IsLoaded()then +local CarrierName=self.CargoCarrier:GetName() +if CarrierName==EventData.IniDCSUnitName then +MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll() +Destroyed=true +self.CargoCarrier:ClearCargo() +end +end +end +if Destroyed then +self:I({"Cargo crate destroyed: "..self.CargoObject:GetName()}) +self:Destroyed() +end +end +function CARGO_CRATE:onenterUnLoaded(From,Event,To,ToPointVec2) +local Angle=180 +local Speed=10 +local Distance=10 +if From=="Loaded"then +local StartCoordinate=self.CargoCarrier:GetCoordinate() +local CargoCarrierHeading=self.CargoCarrier:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +local CargoDeployCoord=StartCoordinate:Translate(Distance,CargoDeployHeading) +ToPointVec2=ToPointVec2 or COORDINATE:NewFromVec2({x=CargoDeployCoord.x,y=CargoDeployCoord.z}) +if self.CargoObject then +self.CargoObject:ReSpawnAt(ToPointVec2,0) +self.CargoCarrier=nil +end +end +if self.OnUnLoadedCallBack then +self.OnUnLoadedCallBack(self,unpack(self.OnUnLoadedParameters)) +self.OnUnLoadedCallBack=nil +end +end +function CARGO_CRATE:onenterLoaded(From,Event,To,CargoCarrier) +self.CargoCarrier=CargoCarrier +if self.CargoObject then +self:T("Destroying") +self.NoDestroy=true +self.CargoObject:Destroy(false) +end +end +function CARGO_CRATE:CanBoard() +return false +end +function CARGO_CRATE:CanUnboard() +return false +end +function CARGO_CRATE:CanSlingload() +return false +end +function CARGO_CRATE:IsInReportRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.LoadRadius then +return true +end +end +return false +end +function CARGO_CRATE:IsInLoadRadius(Coordinate) +local Distance=0 +if self:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(self.CargoObject:GetCoordinate()) +if Distance<=self.NearRadius then +return true +end +end +return false +end +function CARGO_CRATE:GetCoordinate() +return self.CargoObject:GetCoordinate() +end +function CARGO_CRATE:IsAlive() +local Alive=true +if self:IsLoaded()then +Alive=Alive==true and self.CargoCarrier:IsAlive() +else +Alive=Alive==true and self.CargoObject:IsAlive() +end +return Alive +end +function CARGO_CRATE:RouteTo(Coordinate) +self:F({Coordinate=Coordinate}) +end +function CARGO_CRATE:IsNear(CargoCarrier,NearRadius) +self:F({NearRadius=NearRadius}) +return self:IsNear(CargoCarrier:GetCoordinate(),NearRadius) +end +function CARGO_CRATE:Respawn() +self:F({"Respawning crate "..self:GetName()}) +if self.CargoObject then +self.CargoObject:ReSpawn() +self:__Reset(-0.1) +end +end +function CARGO_CRATE:onafterReset() +self:F({"Reset crate "..self:GetName()}) +if self.CargoObject then +self:SetDeployed(false) +self:SetStartState("UnLoaded") +self.CargoCarrier=nil +_EVENTDISPATCHER:CreateEventNewCargo(self) +end +end +function CARGO_CRATE:GetTransportationMethod() +if self:IsLoaded()then +return"for unloading" +else +if self:IsUnLoaded()then +return"for loading" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +do +CARGO_GROUP={ +ClassName="CARGO_GROUP", +} +function CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius) +local self=BASE:Inherit(self,CARGO_REPORTABLE:New(Type,Name,0,LoadRadius,NearRadius)) +self:F({Type,Name,LoadRadius}) +self.CargoSet=SET_CARGO:New() +self.CargoGroup=CargoGroup +self.Grouped=true +self.CargoUnitTemplate={} +self.NearRadius=NearRadius +self:SetDeployed(false) +local WeightGroup=0 +local VolumeGroup=0 +self.CargoGroup:Destroy() +local GroupName=CargoGroup:GetName() +self.CargoName=Name +self.CargoTemplate=UTILS.DeepCopy(_DATABASE:GetGroupTemplate(GroupName)) +self.CargoTemplate.lateActivation=false +self.GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) +self.GroupTemplate.name=self.CargoName.."#CARGO" +self.GroupTemplate.groupId=nil +self.GroupTemplate.units={} +for UnitID,UnitTemplate in pairs(self.CargoTemplate.units)do +UnitTemplate.name=UnitTemplate.name.."#CARGO" +local CargoUnitName=UnitTemplate.name +self.CargoUnitTemplate[CargoUnitName]=UnitTemplate +self.GroupTemplate.units[#self.GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName] +self.GroupTemplate.units[#self.GroupTemplate.units].unitId=nil +local Unit=UNIT:Register(CargoUnitName) +end +self.CargoGroup=GROUP:NewTemplate(self.GroupTemplate,self.GroupTemplate.CoalitionID,self.GroupTemplate.CategoryID,self.GroupTemplate.CountryID) +self.CargoObject=_DATABASE:Spawn(self.GroupTemplate) +for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do +local CargoUnitName=CargoUnit:GetName() +local Cargo=CARGO_UNIT:New(CargoUnit,Type,CargoUnitName,LoadRadius,NearRadius) +self.CargoSet:Add(CargoUnitName,Cargo) +WeightGroup=WeightGroup+Cargo:GetWeight() +end +self:SetWeight(WeightGroup) +self:T({"Weight Cargo",WeightGroup}) +_EVENTDISPATCHER:CreateEventNewCargo(self) +self:HandleEvent(EVENTS.Dead,self.OnEventCargoDead) +self:HandleEvent(EVENTS.Crash,self.OnEventCargoDead) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventCargoDead) +self:SetEventPriority(4) +return self +end +function CARGO_GROUP:Respawn() +self:F({"Respawning"}) +for CargoID,CargoData in pairs(self.CargoSet:GetSet())do +local Cargo=CargoData +Cargo:Destroy() +Cargo:SetStartState("UnLoaded") +end +_DATABASE:Spawn(self.GroupTemplate) +for CargoUnitID,CargoUnit in pairs(self.CargoObject:GetUnits())do +local CargoUnitName=CargoUnit:GetName() +local Cargo=CARGO_UNIT:New(CargoUnit,self.Type,CargoUnitName,self.LoadRadius) +self.CargoSet:Add(CargoUnitName,Cargo) +end +self:SetDeployed(false) +self:SetStartState("UnLoaded") +end +function CARGO_GROUP:Ungroup() +if self.Grouped==true then +self.Grouped=false +self.CargoGroup:Destroy() +for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do +local CargoUnit=CargoUnit +if CargoUnit:IsUnLoaded()then +local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) +GroupTemplate.name=self.CargoName.."#CARGO#"..CargoUnitName +GroupTemplate.groupId=nil +if CargoUnit:IsUnLoaded()then +GroupTemplate.units={} +GroupTemplate.units[1]=self.CargoUnitTemplate[CargoUnitName] +GroupTemplate.units[#GroupTemplate.units].unitId=nil +GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX() +GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY() +GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading() +end +local CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID) +_DATABASE:Spawn(GroupTemplate) +end +end +self.CargoObject=nil +end +end +function CARGO_GROUP:Regroup() +self:F("Regroup") +if self.Grouped==false then +self.Grouped=true +local GroupTemplate=UTILS.DeepCopy(self.CargoTemplate) +GroupTemplate.name=self.CargoName.."#CARGO" +GroupTemplate.groupId=nil +GroupTemplate.units={} +for CargoUnitName,CargoUnit in pairs(self.CargoSet:GetSet())do +local CargoUnit=CargoUnit +self:F({CargoUnit:GetName(),UnLoaded=CargoUnit:IsUnLoaded()}) +if CargoUnit:IsUnLoaded()then +CargoUnit.CargoObject:Destroy() +GroupTemplate.units[#GroupTemplate.units+1]=self.CargoUnitTemplate[CargoUnitName] +GroupTemplate.units[#GroupTemplate.units].unitId=nil +GroupTemplate.units[#GroupTemplate.units].x=CargoUnit:GetX() +GroupTemplate.units[#GroupTemplate.units].y=CargoUnit:GetY() +GroupTemplate.units[#GroupTemplate.units].heading=CargoUnit:GetHeading() +end +end +self.CargoGroup=GROUP:NewTemplate(GroupTemplate,GroupTemplate.CoalitionID,GroupTemplate.CategoryID,GroupTemplate.CountryID) +self:F({"Regroup",GroupTemplate}) +self.CargoObject=_DATABASE:Spawn(GroupTemplate) +end +end +function CARGO_GROUP:OnEventCargoDead(EventData) +self:E(EventData) +local Destroyed=false +if self:IsDestroyed()or self:IsUnLoaded()or self:IsBoarding()or self:IsUnboarding()then +Destroyed=true +for CargoID,CargoData in pairs(self.CargoSet:GetSet())do +local Cargo=CargoData +if Cargo:IsAlive()then +Destroyed=false +else +Cargo:Destroyed() +end +end +else +local CarrierName=self.CargoCarrier:GetName() +if CarrierName==EventData.IniDCSUnitName then +MESSAGE:New("Cargo is lost from carrier "..CarrierName,15):ToAll() +Destroyed=true +self.CargoCarrier:ClearCargo() +end +end +if Destroyed then +self:Destroyed() +self:E({"Cargo group destroyed"}) +end +end +function CARGO_GROUP:onafterBoard(From,Event,To,CargoCarrier,NearRadius,...) +self:F({CargoCarrier.UnitName,From,Event,To,NearRadius=NearRadius}) +NearRadius=NearRadius or self.NearRadius +self.CargoSet:ForEach( +function(Cargo,...) +self:F({"Board Unit",Cargo:GetName(),Cargo:IsDestroyed(),Cargo.CargoObject:IsAlive()}) +local CargoGroup=Cargo.CargoObject +CargoGroup:OptionAlarmStateGreen() +Cargo:__Board(1,CargoCarrier,NearRadius,...) +end,... +) +self:__Boarding(-1,CargoCarrier,NearRadius,...) +end +function CARGO_GROUP:onafterLoad(From,Event,To,CargoCarrier,...) +if From=="UnLoaded"then +for CargoID,Cargo in pairs(self.CargoSet:GetSet())do +if not Cargo:IsDestroyed()then +Cargo:Load(CargoCarrier) +end +end +end +self.CargoCarrier=CargoCarrier +self.CargoCarrier:AddCargo(self) +end +function CARGO_GROUP:onafterBoarding(From,Event,To,CargoCarrier,NearRadius,...) +local Boarded=true +local Cancelled=false +local Dead=true +self.CargoSet:Flush() +for CargoID,Cargo in pairs(self.CargoSet:GetSet())do +if not Cargo:is("Loaded") +and(not Cargo:is("Destroyed"))then +Boarded=false +end +if Cargo:is("UnLoaded")then +Cancelled=true +end +if not Cargo:is("Destroyed")then +Dead=false +end +end +if not Dead then +if not Cancelled then +if not Boarded then +self:__Boarding(-5,CargoCarrier,NearRadius,...) +else +self:F("Group Cargo is loaded") +self:__Load(1,CargoCarrier,...) +end +else +self:__CancelBoarding(1,CargoCarrier,NearRadius,...) +end +else +self:__Destroyed(1,CargoCarrier,NearRadius,...) +end +end +function CARGO_GROUP:onafterUnBoard(From,Event,To,ToPointVec2,NearRadius,...) +self:F({From,Event,To,ToPointVec2,NearRadius}) +NearRadius=NearRadius or 25 +local Timer=1 +if From=="Loaded"then +if self.CargoObject then +self.CargoObject:Destroy() +end +self.CargoSet:ForEach( +function(Cargo,NearRadius) +if not Cargo:IsDestroyed()then +local ToVec=nil +if ToPointVec2==nil then +ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius,NearRadius) +else +ToVec=ToPointVec2 +end +Cargo:__UnBoard(Timer,ToVec,NearRadius) +Timer=Timer+1 +end +end,{NearRadius} +) +self:__UnBoarding(1,ToPointVec2,NearRadius,...) +end +end +function CARGO_GROUP:onafterUnBoarding(From,Event,To,ToPointVec2,NearRadius,...) +local Angle=180 +local Speed=10 +local Distance=5 +if From=="UnBoarding"then +local UnBoarded=true +for CargoID,Cargo in pairs(self.CargoSet:GetSet())do +self:T({Cargo:GetName(),Cargo.current}) +if not Cargo:is("UnLoaded")and not Cargo:IsDestroyed()then +UnBoarded=false +end +end +if UnBoarded then +self:__UnLoad(1,ToPointVec2,...) +else +self:__UnBoarding(1,ToPointVec2,NearRadius,...) +end +return false +end +end +function CARGO_GROUP:onafterUnLoad(From,Event,To,ToPointVec2,...) +if From=="Loaded"then +self.CargoSet:ForEach( +function(Cargo) +local RandomVec2=nil +if ToPointVec2 then +RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20,10) +end +Cargo:UnBoard(RandomVec2) +end +) +end +self.CargoCarrier:RemoveCargo(self) +self.CargoCarrier=nil +end +function CARGO_GROUP:GetCoordinate() +local Cargo=self:GetFirstAlive() +if Cargo then +return Cargo.CargoObject:GetCoordinate() +end +return nil +end +function CARGO:GetX() +local Cargo=self:GetFirstAlive() +if Cargo then +return Cargo:GetCoordinate().x +end +return nil +end +function CARGO:GetY() +local Cargo=self:GetFirstAlive() +if Cargo then +return Cargo:GetCoordinate().z +end +return nil +end +function CARGO_GROUP:IsAlive() +local Cargo=self:GetFirstAlive() +return Cargo~=nil +end +function CARGO_GROUP:GetFirstAlive() +local CargoFirstAlive=nil +for _,Cargo in pairs(self.CargoSet:GetSet())do +if not Cargo:IsDestroyed()then +CargoFirstAlive=Cargo +break +end +end +return CargoFirstAlive +end +function CARGO_GROUP:GetCount() +return self.CargoSet:Count() +end +function CARGO_GROUP:GetGroup(Cargo) +local Cargo=Cargo or self:GetFirstAlive() +return Cargo.CargoObject:GetGroup() +end +function CARGO_GROUP:RouteTo(Coordinate) +self.CargoSet:ForEach( +function(Cargo) +Cargo.CargoObject:RouteGroundTo(Coordinate,10,"vee",0) +end +) +end +function CARGO_GROUP:IsNear(CargoCarrier,NearRadius) +self:F({NearRadius=NearRadius}) +for _,Cargo in pairs(self.CargoSet:GetSet())do +local Cargo=Cargo +if Cargo:IsAlive()then +if Cargo:IsNear(CargoCarrier:GetCoordinate(),NearRadius)then +self:F("Near") +return true +end +end +end +return nil +end +function CARGO_GROUP:IsInLoadRadius(Coordinate) +local Cargo=self:GetFirstAlive() +if Cargo then +local Distance=0 +local CargoCoordinate +if Cargo:IsLoaded()then +CargoCoordinate=Cargo.CargoCarrier:GetCoordinate() +else +CargoCoordinate=Cargo.CargoObject:GetCoordinate() +end +if CargoCoordinate then +Distance=Coordinate:Get2DDistance(CargoCoordinate) +else +return false +end +self:F({Distance=Distance,LoadRadius=self.LoadRadius}) +if Distance<=self.LoadRadius then +return true +else +return false +end +end +return nil +end +function CARGO_GROUP:IsInReportRadius(Coordinate) +local Cargo=self:GetFirstAlive() +if Cargo then +self:F({Cargo}) +local Distance=0 +if Cargo:IsUnLoaded()then +Distance=Coordinate:Get2DDistance(Cargo.CargoObject:GetCoordinate()) +if Distance<=self.LoadRadius then +return true +end +end +end +return nil +end +function CARGO_GROUP:Flare(FlareColor) +local Cargo=self.CargoSet:GetFirst() +if Cargo then +Cargo:Flare(FlareColor) +end +end +function CARGO_GROUP:Smoke(SmokeColor,Radius) +local Cargo=self.CargoSet:GetFirst() +if Cargo then +Cargo:Smoke(SmokeColor,Radius) +end +end +function CARGO_GROUP:IsInZone(Zone) +local Cargo=self.CargoSet:GetFirst() +if Cargo then +return Cargo:IsInZone(Zone) +end +return nil +end +function CARGO_GROUP:GetTransportationMethod() +if self:IsLoaded()then +return"for unboarding" +else +if self:IsUnLoaded()then +return"for boarding" +else +if self:IsDeployed()then +return"delivered" +end +end +end +return"" +end +end +SCORING={ +ClassName="SCORING", +ClassID=0, +Players={}, +} +local _SCORINGCoalition= +{ +[1]="Red", +[2]="Blue", +} +local _SCORINGCategory= +{ +[Unit.Category.AIRPLANE]="Plane", +[Unit.Category.HELICOPTER]="Helicopter", +[Unit.Category.GROUND_UNIT]="Vehicle", +[Unit.Category.SHIP]="Ship", +[Unit.Category.STRUCTURE]="Structure", +} +function SCORING:New(GameName) +local self=BASE:Inherit(self,BASE:New()) +if GameName then +self.GameName=GameName +else +error("A game name must be given to register the scoring results") +end +self.ScoringObjects={} +self.ScoringZones={} +self:SetMessagesToAll() +self:SetMessagesHit(false) +self:SetMessagesDestroy(true) +self:SetMessagesScore(true) +self:SetMessagesZone(true) +self:SetScaleDestroyScore(10) +self:SetScaleDestroyPenalty(30) +self:SetFratricide(self.ScaleDestroyPenalty*3) +self:SetCoalitionChangePenalty(self.ScaleDestroyPenalty) +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:OpenCSV(GameName) +return self +end +function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) +self.DisplayMessagePrefix=DisplayMessagePrefix or"" +return self +end +function SCORING:SetScaleDestroyScore(Scale) +self.ScaleDestroyScore=Scale +return self +end +function SCORING:SetScaleDestroyPenalty(Scale) +self.ScaleDestroyPenalty=Scale +return self +end +function SCORING:AddUnitScore(ScoreUnit,Score) +local UnitName=ScoreUnit:GetName() +self.ScoringObjects[UnitName]=Score +return self +end +function SCORING:RemoveUnitScore(ScoreUnit) +local UnitName=ScoreUnit:GetName() +self.ScoringObjects[UnitName]=nil +return self +end +function SCORING:AddStaticScore(ScoreStatic,Score) +local StaticName=ScoreStatic:GetName() +self.ScoringObjects[StaticName]=Score +return self +end +function SCORING:RemoveStaticScore(ScoreStatic) +local StaticName=ScoreStatic:GetName() +self.ScoringObjects[StaticName]=nil +return self +end +function SCORING:AddScoreGroup(ScoreGroup,Score) +local ScoreUnits=ScoreGroup:GetUnits() +for ScoreUnitID,ScoreUnit in pairs(ScoreUnits)do +local UnitName=ScoreUnit:GetName() +self.ScoringObjects[UnitName]=Score +end +return self +end +function SCORING:AddZoneScore(ScoreZone,Score) +local ZoneName=ScoreZone:GetName() +self.ScoringZones[ZoneName]={} +self.ScoringZones[ZoneName].ScoreZone=ScoreZone +self.ScoringZones[ZoneName].Score=Score +return self +end +function SCORING:RemoveZoneScore(ScoreZone) +local ZoneName=ScoreZone:GetName() +self.ScoringZones[ZoneName]=nil +return self +end +function SCORING:SetMessagesHit(OnOff) +self.MessagesHit=OnOff +return self +end +function SCORING: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: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 then +self.Players[PlayerName].Penalty=self.Players[PlayerName].Penalty+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). 50 Penalty points added.", +MESSAGE.Type.Information +):ToAll() +self:ScoreCSV(PlayerName,"","COALITION_PENALTY",1,-50,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 +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 +MESSAGE:NewType(self.DisplayMessagePrefix..Text,MESSAGE.Type.Information):ToAll() +self:ScoreCSV(PlayerName,"","GOAL_"..string.upper(GoalTag),1,Score,nil) +end +end +function SCORING:AddGoalScore(PlayerUnit,GoalTag,Text,Score) +local PlayerName=PlayerUnit:GetPlayerName() +self:F({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 +MESSAGE:NewType(self.DisplayMessagePrefix..Text,MESSAGE.Type.Information):ToAll() +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 +MESSAGE:NewType(self.DisplayMessagePrefix..Mission:GetText().." : "..Text.." Score: "..Score,MESSAGE.Type.Information):ToAll() +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 +MESSAGE:NewType(string.format("%s%s: %s! Player %s receives %d score!",self.DisplayMessagePrefix,Mission:GetText(),Text,PlayerName,Score),MESSAGE.Type.Information):ToAll() +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 +MESSAGE:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' has "..Text.." in "..Mission:GetText()..". ".. +Score.." mission score!", +MESSAGE.Type.Information):ToAll() +self:ScoreCSV(PlayerName,"","MISSION_"..MissionName:gsub(' ','_'),1,Score) +end +end +end +function SCORING:OnEventBirth(Event) +if Event.IniUnit then +if Event.IniObjectCategory==1 then +local PlayerName=Event.IniUnit:GetPlayerName() +if PlayerName then +self:_AddPlayerFromUnit(Event.IniUnit) +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 +PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() +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 +Player.Penalty=Player.Penalty+10 +PlayerHit.Penalty=PlayerHit.Penalty+10 +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: -"..PlayerHit.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: -"..PlayerHit.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+1 +PlayerHit.Score=PlayerHit.Score+1 +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 +PlayerHit.ThreatLevel,PlayerHit.ThreatType=PlayerHit.UNIT:GetThreatLevel() +if timer.getTime()-PlayerHit.TimeStamp>1 then +PlayerHit.TimeStamp=timer.getTime() +local Score=0 +if InitCoalition then +if InitCoalition==TargetCoalition then +Player.Penalty=Player.Penalty+10 +PlayerHit.Penalty=PlayerHit.Penalty+10 +PlayerHit.PenaltyHit=PlayerHit.PenaltyHit+1 +MESSAGE +:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit friendly target ".. +TargetUnitCategory.." ( "..TargetType.." ) ".. +"Penalty: -"..PlayerHit.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+1 +PlayerHit.Score=PlayerHit.Score+1 +PlayerHit.ScoreHit=PlayerHit.ScoreHit+1 +MESSAGE +:NewType(self.DisplayMessagePrefix.."Player '"..Event.WeaponPlayerName.."' hit enemy target ".. +TargetUnitCategory.." ( "..TargetType.." ) ".. +"Score: +"..PlayerHit.Score.." = "..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 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 +MESSAGE +:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly player '"..TargetPlayerName.."' ".. +TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Penalty: -"..TargetDestroy.Penalty.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information +) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +else +MESSAGE +:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed friendly target ".. +TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Penalty: -"..TargetDestroy.Penalty.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information +) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +end +Destroyed=true +self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_PENALTY",1,ThreatPenalty,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +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 +MESSAGE +:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy player '"..TargetPlayerName.."' ".. +TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Score: +"..TargetDestroy.Score.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information +) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +else +MESSAGE +:NewType(self.DisplayMessagePrefix.."Player '"..PlayerName.."' destroyed enemy ".. +TargetUnitCategory.." ( "..ThreatTypeTarget.." ) ".. +"Score: +"..TargetDestroy.Score.." = "..Player.Score-Player.Penalty, +MESSAGE.Type.Information +) +:ToAllIf(self:IfMessagesDestroy()and self:IfMessagesToAll()) +:ToCoalitionIf(InitCoalition,self:IfMessagesDestroy()and self:IfMessagesToCoalition()) +end +Destroyed=true +self:ScoreCSV(PlayerName,TargetPlayerName,"DESTROY_SCORE",1,ThreatScore,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,TargetUnitCoalition,TargetUnitCategory,TargetUnitType) +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()) +Destroyed=true +self:ScoreCSV(PlayerName,"","DESTROY_SCORE",1,Score,InitUnitName,InitUnitCoalition,InitUnitCategory,InitUnitType,TargetUnitName,"","Scenery",TargetUnitType) +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+ScoreGoals+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+ScoreGoals+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 +nHours=string.format("%02.f",math.floor(nSeconds/3600)); +nMins=string.format("%02.f",math.floor(nSeconds/60-(nHours*60))); +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 then +if ScoringCSV then +self.ScoringCSV=ScoringCSV +local fdir=lfs.writedir()..[[Logs\]]..self.ScoringCSV.." "..os.date("%Y-%m-%d %H-%M-%S")..".csv" +self.CSVFile,self.err=io.open(fdir,"w+") +if not self.CSVFile then +error("Error: Cannot open CSV file in "..lfs.writedir()) +end +self.CSVFile:write('"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoaltion","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 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 then +self.CSVFile:close() +end +end +CLEANUP_AIRBASE={ +ClassName="CLEANUP_AIRBASE", +TimeInterval=0.2, +CleanUpList={}, +} +CLEANUP_AIRBASE.__={} +CLEANUP_AIRBASE.__.Airbases={} +function CLEANUP_AIRBASE:New(AirbaseNames) +local self=BASE:Inherit(self,BASE:New()) +self:F({AirbaseNames}) +if type(AirbaseNames)=='table'then +for AirbaseID,AirbaseName in pairs(AirbaseNames)do +self:AddAirbase(AirbaseName) +end +else +local AirbaseName=AirbaseNames +self:AddAirbase(AirbaseName) +end +self:HandleEvent(EVENTS.Birth,self.__.OnEventBirth) +self.__.CleanUpScheduler=SCHEDULER:New(self,self.__.CleanUpSchedule,{},1,self.TimeInterval) +self:HandleEvent(EVENTS.EngineShutdown,self.__.EventAddForCleanUp) +self:HandleEvent(EVENTS.EngineStartup,self.__.EventAddForCleanUp) +self:HandleEvent(EVENTS.Hit,self.__.EventAddForCleanUp) +self:HandleEvent(EVENTS.PilotDead,self.__.OnEventCrash) +self:HandleEvent(EVENTS.Dead,self.__.OnEventCrash) +self:HandleEvent(EVENTS.Crash,self.__.OnEventCrash) +for UnitName,Unit in pairs(_DATABASE.UNITS)do +local Unit=Unit +if Unit:IsAlive()~=nil then +if self:IsInAirbase(Unit:GetVec2())then +self:F({UnitName=UnitName}) +self.CleanUpList[UnitName]={} +self.CleanUpList[UnitName].CleanUpUnit=Unit +self.CleanUpList[UnitName].CleanUpGroup=Unit:GetGroup() +self.CleanUpList[UnitName].CleanUpGroupName=Unit:GetGroup():GetName() +self.CleanUpList[UnitName].CleanUpUnitName=Unit:GetName() +end +end +end +return self +end +function CLEANUP_AIRBASE:AddAirbase(AirbaseName) +self.__.Airbases[AirbaseName]=AIRBASE:FindByName(AirbaseName) +self:F({"Airbase:",AirbaseName,self.__.Airbases[AirbaseName]:GetDesc()}) +return self +end +function CLEANUP_AIRBASE:RemoveAirbase(AirbaseName) +self.__.Airbases[AirbaseName]=nil +return self +end +function CLEANUP_AIRBASE:SetCleanMissiles(CleanMissiles) +if CleanMissiles then +self:HandleEvent(EVENTS.Shot,self.__.OnEventShot) +else +self:UnHandleEvent(EVENTS.Shot) +end +end +function CLEANUP_AIRBASE.__:IsInAirbase(Vec2) +local InAirbase=false +for AirbaseName,Airbase in pairs(self.__.Airbases)do +local Airbase=Airbase +if Airbase:GetZone():IsVec2InZone(Vec2)then +InAirbase=true +break; +end +end +return InAirbase +end +function CLEANUP_AIRBASE.__:DestroyUnit(CleanUpUnit) +self:F({CleanUpUnit}) +if CleanUpUnit then +local CleanUpUnitName=CleanUpUnit:GetName() +local CleanUpGroup=CleanUpUnit:GetGroup() +if CleanUpGroup:IsAlive()then +local CleanUpGroupUnits=CleanUpGroup:GetUnits() +if#CleanUpGroupUnits==1 then +local CleanUpGroupName=CleanUpGroup:GetName() +CleanUpGroup:Destroy() +else +CleanUpUnit:Destroy() +end +self.CleanUpList[CleanUpUnitName]=nil +end +end +end +function CLEANUP_AIRBASE.__:DestroyMissile(MissileObject) +self:F({MissileObject}) +if MissileObject and MissileObject:isExist()then +MissileObject:destroy() +self:T("MissileObject Destroyed") +end +end +function CLEANUP_AIRBASE.__:OnEventBirth(EventData) +self:F({EventData}) +if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()~=nil then +if self:IsInAirbase(EventData.IniUnit:GetVec2())then +self.CleanUpList[EventData.IniDCSUnitName]={} +self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit=EventData.IniUnit +self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup=EventData.IniGroup +self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName=EventData.IniDCSGroupName +self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName=EventData.IniDCSUnitName +end +end +end +function CLEANUP_AIRBASE.__:OnEventCrash(Event) +self:F({Event}) +if Event.IniDCSUnitName and Event.IniCategory==Object.Category.UNIT then +self.CleanUpList[Event.IniDCSUnitName]={} +self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit=Event.IniUnit +self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup=Event.IniGroup +self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName=Event.IniDCSGroupName +self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName=Event.IniDCSUnitName +end +end +function CLEANUP_AIRBASE.__:OnEventShot(Event) +self:F({Event}) +if self:IsInAirbase(Event.IniUnit:GetVec2())then +self:DestroyMissile(Event.Weapon) +end +end +function CLEANUP_AIRBASE.__:OnEventHit(Event) +self:F({Event}) +if Event.IniUnit then +if self:IsInAirbase(Event.IniUnit:GetVec2())then +self:T({"Life: ",Event.IniDCSUnitName,' = ',Event.IniUnit:GetLife(),"/",Event.IniUnit:GetLife0()}) +if Event.IniUnit:GetLife()0 then +local MoveProbability=(self.MoveMaximum*100)/self.AliveUnits +self:T('Move Probability = '..MoveProbability) +for MovementUnitName,MovementGroupName in pairs(self.MoveUnits)do +local MovementGroup=Group.getByName(MovementGroupName) +if MovementGroup and MovementGroup:isExist()then +local MoveOrStop=math.random(1,100) +self:T('MoveOrStop = '..MoveOrStop) +if MoveOrStop<=MoveProbability then +self:T('Group continues moving = '..MovementGroupName) +trigger.action.groupContinueMoving(MovementGroup) +else +self:T('Group stops moving = '..MovementGroupName) +trigger.action.groupStopMoving(MovementGroup) +end +else +self.MoveUnits[MovementUnitName]=nil +end +end +end +return true +end +SEAD={ +ClassName="SEAD", +TargetSkill={ +Average={Evade=30,DelayOn={40,60}}, +Good={Evade=20,DelayOn={30,50}}, +High={Evade=15,DelayOn={20,40}}, +Excellent={Evade=10,DelayOn={10,30}} +}, +SEADGroupPrefixes={}, +SuppressedGroups={}, +EngagementRange=75 +} +SEAD.Harms={ +["AGM_88"]="AGM_88", +["AGM_45"]="AGM_45", +["AGM_122"]="AGM_122", +["AGM_84"]="AGM_84", +["AGM_45"]="AGM_45", +["ALARN"]="ALARM", +["LD-10"]="LD-10", +["X_58"]="X_58", +["X_28"]="X_28", +["X_25"]="X_25", +["X_31"]="X_31", +["Kh25"]="Kh25", +} +function SEAD:New(SEADGroupPrefixes) +local self=BASE:Inherit(self,BASE:New()) +self:F(SEADGroupPrefixes) +if type(SEADGroupPrefixes)=='table'then +for SEADGroupPrefixID,SEADGroupPrefix in pairs(SEADGroupPrefixes)do +self.SEADGroupPrefixes[SEADGroupPrefix]=SEADGroupPrefix +end +else +self.SEADGroupPrefixes[SEADGroupPrefixes]=SEADGroupPrefixes +end +self:HandleEvent(EVENTS.Shot) +self:I("*** SEAD - Started Version 0.2.7") +return self +end +function SEAD:UpdateSet(SEADGroupPrefixes) +self:F(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:F({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:_CheckHarms(WeaponName) +self:F({WeaponName}) +local hit=false +for _,_name in pairs(SEAD.Harms)do +if string.find(WeaponName,_name,1)then hit=true end +end +return hit +end +function SEAD:OnEventShot(EventData) +self:T({EventData}) +local SEADUnit=EventData.IniDCSUnit +local SEADUnitName=EventData.IniDCSUnitName +local SEADWeapon=EventData.Weapon +local SEADWeaponName=EventData.WeaponName +self:T("*** SEAD - Missile Launched = "..SEADWeaponName) +self:T({SEADWeapon}) +if self:_CheckHarms(SEADWeaponName)then +local _targetskill="Random" +local _targetMimgroupName="none" +local _evade=math.random(1,100) +local _targetMim=EventData.Weapon:getTarget() +local _targetUnit=UNIT:Find(_targetMim) +if _targetUnit and _targetUnit:IsAlive()then +local _targetMimgroup=_targetUnit:GetGroup() +local _targetMimgroupName=_targetMimgroup:GetName() +self:T(self.SEADGroupPrefixes) +self:T(_targetMimgroupName) +end +local SEADGroupFound=false +for SEADGroupPrefixID,SEADGroupPrefix in pairs(self.SEADGroupPrefixes)do +if string.find(_targetMimgroupName,SEADGroupPrefix,1,true)then +SEADGroupFound=true +self:T('*** SEAD - Group Found') +break +end +end +if SEADGroupFound==true then +if _targetskill=="Random"then +local Skills={"Average","Good","High","Excellent"} +_targetskill=Skills[math.random(1,4)] +end +self:T(_targetskill) +if self.TargetSkill[_targetskill]then +if(_evade>self.TargetSkill[_targetskill].Evade)then +self:T(string.format("*** SEAD - Evading, target skill "..string.format(_targetskill))) +local _targetMimgroup=Unit.getGroup(Weapon.getTarget(SEADWeapon)) +local _targetMimcont=_targetMimgroup:getController() +routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) +local id={ +groupName=_targetMimgroup, +ctrl=_targetMimcont +} +local function SuppressionEnd(id) +local range=self.EngagementRange +self:T(string.format("*** SEAD - Engagement Range is %d",range)) +id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) +id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) +self.SuppressedGroups[id.groupName]=nil +end +local delay=math.random(self.TargetSkill[_targetskill].DelayOn[1],self.TargetSkill[_targetskill].DelayOn[2]) +local SuppressionEndTime=timer.getTime()+delay +if self.SuppressedGroups[id.groupName]==nil then +self.SuppressedGroups[id.groupName]={ +SuppressionEndTime=delay +} +Controller.setOption(_targetMimcont,AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) +timer.scheduleFunction(SuppressionEnd,id,SuppressionEndTime) +end +end +end +end +end +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=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 +routines.removeFunction(self.ReportTargetsScheduler) +self.ReportTargetsScheduler=nil +end +end +function ESCORT:_ScanTargets(ScanDuration) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroup:IsHelicopter()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(200,20), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +elseif EscortGroup:IsAirPlane()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(1000,500), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +end +EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortClient) +if self.EscortMode==ESCORT.MODE.FOLLOW then +self.FollowScheduler:Start(self.FollowSchedule) +end +end +function _Resume(EscortGroup) +env.info('_Resume') +local Escort=EscortGroup:GetState(EscortGroup,"Escort") +env.info("EscortMode = "..Escort.EscortMode) +if Escort.EscortMode==ESCORT.MODE.FOLLOW then +Escort:JoinUpAndFollow(EscortGroup,Escort.EscortClient,Escort.Distance) +end +end +function ESCORT:_AttackTarget(DetectedItem) +local EscortGroup=self.EscortGroup +self:F(EscortGroup) +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroup:IsAir()then +EscortGroup:OptionROEOpenFire() +EscortGroup:OptionROTPassiveDefense() +EscortGroup:SetState(EscortGroup,"Escort",self) +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit) +end +end,Tasks +) +Tasks[#Tasks+1]=EscortGroup:TaskFunction("_Resume",{"''"}) +EscortGroup:SetTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +else +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroup:SetTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +end +EscortGroup:MessageToClient("Engaging Designated Unit!",10,EscortClient) +end +function ESCORT:_AssistTarget(EscortGroupAttack,DetectedItem) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroupAttack:IsAir()then +EscortGroupAttack:OptionROEOpenFire() +EscortGroupAttack:OptionROTVertical() +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroupAttack:TaskAttackUnit(DetectedUnit) +end +end,Tasks +) +Tasks[#Tasks+1]=EscortGroupAttack:TaskOrbitCircle(500,350) +EscortGroupAttack:SetTask( +EscortGroupAttack:TaskCombo( +Tasks +),1 +) +else +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroupAttack:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroupAttack:SetTask( +EscortGroupAttack:TaskCombo( +Tasks +),1 +) +end +EscortGroupAttack:MessageToClient("Assisting with the destroying the enemy unit!",10,EscortClient) +end +function ESCORT:_ROE(EscortROEFunction,EscortROEMessage) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +pcall(function()EscortROEFunction()end) +EscortGroup:MessageToClient(EscortROEMessage,10,EscortClient) +end +function ESCORT:_ROT(EscortROTFunction,EscortROTMessage) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +pcall(function()EscortROTFunction()end) +EscortGroup:MessageToClient(EscortROTMessage,10,EscortClient) +end +function ESCORT:_ResumeMission(WayPoint) +local EscortGroup=self.EscortGroup +local EscortClient=self.EscortClient +self.FollowScheduler:Stop(self.FollowSchedule) +local WayPoints=EscortGroup:GetTaskRoute() +self:T(WayPoint,WayPoints) +for WayPointIgnore=1,WayPoint do +table.remove(WayPoints,1) +end +SCHEDULER:New(EscortGroup,EscortGroup.SetTask,{EscortGroup:TaskRoute(WayPoints)},1) +EscortGroup:MessageToClient("Resuming mission from waypoint "..WayPoint..".",10,EscortClient) +end +function ESCORT:RegisterRoute() +self:F() +local EscortGroup=self.EscortGroup +local TaskPoints=EscortGroup:GetTaskRoute() +self:T(TaskPoints) +return TaskPoints +end +function ESCORT:_FollowScheduler() +self:F({self.FollowDistance}) +self:T({self.EscortClient.UnitName,self.EscortGroup.GroupName}) +if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then +local ClientUnit=self.EscortClient:GetClientGroupUnit() +local GroupUnit=self.EscortGroup:GetUnit(1) +local FollowDistance=self.FollowDistance +self:T({ClientUnit.UnitName,GroupUnit.UnitName}) +if self.CT1==0 and self.GT1==0 then +self.CV1=ClientUnit:GetVec3() +self:T({"self.CV1",self.CV1}) +self.CT1=timer.getTime() +self.GV1=GroupUnit:GetVec3() +self.GT1=timer.getTime() +else +local CT1=self.CT1 +local CT2=timer.getTime() +local CV1=self.CV1 +local CV2=ClientUnit:GetVec3() +self.CT1=CT2 +self.CV1=CV2 +local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5 +local CT=CT2-CT1 +local CS=(3600/CT)*(CD/1000) +self:T2({"Client:",CS,CD,CT,CV2,CV1,CT2,CT1}) +local GT1=self.GT1 +local GT2=timer.getTime() +local GV1=self.GV1 +local GV2=GroupUnit:GetVec3() +self.GT1=GT2 +self.GV1=GV2 +local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5 +local GT=GT2-GT1 +local GS=(3600/GT)*(GD/1000) +self:T2({"Group:",GS,GD,GT,GV2,GV1,GT2,GT1}) +local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z} +local GH2={x=GV2.x,y=CV2.y,z=GV2.z} +local alpha=math.atan2(GV.z,GV.x) +local CVI={x=CV2.x+FollowDistance*math.cos(alpha), +y=GH2.y, +z=CV2.z+FollowDistance*math.sin(alpha), +} +local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z} +local DVu={x=DV.x/FollowDistance,y=DV.y/FollowDistance,z=DV.z/FollowDistance} +local GDV={x=DVu.x*CS*8+CVI.x,y=CVI.y,z=DVu.z*CS*8+CVI.z} +if self.SmokeDirectionVector==true then +trigger.action.smoke(GDV,trigger.smokeColor.Red) +end +self:T2({"CV2:",CV2}) +self:T2({"CVI:",CVI}) +self:T2({"GDV:",GDV}) +local CatchUpDistance=((GDV.x-GV2.x)^2+(GDV.y-GV2.y)^2+(GDV.z-GV2.z)^2)^0.5 +local Time=10 +local CatchUpSpeed=(CatchUpDistance-(CS*8.4))/Time +local Speed=CS+CatchUpSpeed +if Speed<0 then +Speed=0 +end +self:T({"Client Speed, Escort Speed, Speed, FollowDistance, Time:",CS,GS,Speed,FollowDistance,Time}) +self.EscortGroup:RouteToVec3(GDV,Speed/3.6) +end +return true +end +return false +end +function ESCORT:_ReportTargetsScheduler() +self:F(self.EscortGroup:GetName()) +if self.EscortGroup:IsAlive()and self.EscortClient:IsAlive()then +if true then +local EscortGroupName=self.EscortGroup:GetName() +self.EscortMenuAttackNearbyTargets:RemoveSubMenus() +if self.EscortMenuTargetAssistance then +self.EscortMenuTargetAssistance:RemoveSubMenus() +end +local DetectedItems=self.Detection:GetDetectedItems() +self:F(DetectedItems) +local DetectedTargets=false +local DetectedMsgs={} +for ClientEscortGroupName,EscortGroupData in pairs(self.EscortClient._EscortGroups)do +local ClientEscortTargets=EscortGroupData.Detection +for DetectedItemIndex,DetectedItem in pairs(DetectedItems)do +self:F({DetectedItemIndex,DetectedItem}) +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroupData.EscortGroup,_DATABASE:GetPlayerSettings(self.EscortClient:GetPlayerName())) +if ClientEscortGroupName==EscortGroupName then +local DetectedMsg=DetectedItemReportSummary:Text("\n") +DetectedMsgs[#DetectedMsgs+1]=DetectedMsg +self:T(DetectedMsg) +MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(), +DetectedMsg, +self.EscortMenuAttackNearbyTargets, +ESCORT._AttackTarget, +self, +DetectedItem +) +else +if self.EscortMenuTargetAssistance then +local DetectedMsg=DetectedItemReportSummary:Text("\n") +self:T(DetectedMsg) +local MenuTargetAssistance=MENU_GROUP:New(self.EscortClient:GetGroup(),EscortGroupData.EscortName,self.EscortMenuTargetAssistance) +MENU_GROUP_COMMAND:New(self.EscortClient:GetGroup(), +DetectedMsg, +MenuTargetAssistance, +ESCORT._AssistTarget, +self, +EscortGroupData.EscortGroup, +DetectedItem +) +end +end +DetectedTargets=true +end +end +self:F(DetectedMsgs) +if DetectedTargets then +self.EscortGroup:MessageToClient("Reporting detected targets:\n"..table.concat(DetectedMsgs,"\n"),20,self.EscortClient) +else +self.EscortGroup:MessageToClient("No targets detected.",10,self.EscortClient) +end +return true +else +end +end +return false +end +MISSILETRAINER={ +ClassName="MISSILETRAINER", +TrackingMissiles={}, +} +function MISSILETRAINER._Alive(Client,self) +if self.Briefing then +Client:Message(self.Briefing,15,"Trainer") +end +if self.MenusOnOff==true then +Client:Message("Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).",15,"Trainer") +Client.MainMenu=MENU_GROUP:New(Client:GetGroup(),"Missile Trainer",nil) +Client.MenuMessages=MENU_GROUP:New(Client:GetGroup(),"Messages",Client.MainMenu) +Client.MenuOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages On",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=true}) +Client.MenuOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Messages Off",Client.MenuMessages,self._MenuMessages,{MenuSelf=self,MessagesOnOff=false}) +Client.MenuTracking=MENU_GROUP:New(Client:GetGroup(),"Tracking",Client.MainMenu) +Client.MenuTrackingToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=true}) +Client.MenuTrackingToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingToAll=false}) +Client.MenuTrackOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking On",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=true}) +Client.MenuTrackOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Tracking Off",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingOnOff=false}) +Client.MenuTrackIncrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Increase",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=-1}) +Client.MenuTrackDecrease=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Frequency Decrease",Client.MenuTracking,self._MenuMessages,{MenuSelf=self,TrackingFrequency=1}) +Client.MenuAlerts=MENU_GROUP:New(Client:GetGroup(),"Alerts",Client.MainMenu) +Client.MenuAlertsToAll=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To All",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=true}) +Client.MenuAlertsToTarget=MENU_GROUP_COMMAND:New(Client:GetGroup(),"To Target",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsToAll=false}) +Client.MenuHitsOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=true}) +Client.MenuHitsOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Hits Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsHitsOnOff=false}) +Client.MenuLaunchesOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches On",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=true}) +Client.MenuLaunchesOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Launches Off",Client.MenuAlerts,self._MenuMessages,{MenuSelf=self,AlertsLaunchesOnOff=false}) +Client.MenuDetails=MENU_GROUP:New(Client:GetGroup(),"Details",Client.MainMenu) +Client.MenuDetailsDistanceOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=true}) +Client.MenuDetailsDistanceOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Range Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsRangeOnOff=false}) +Client.MenuDetailsBearingOn=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing On",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=true}) +Client.MenuDetailsBearingOff=MENU_GROUP_COMMAND:New(Client:GetGroup(),"Bearing Off",Client.MenuDetails,self._MenuMessages,{MenuSelf=self,DetailsBearingOnOff=false}) +Client.MenuDistance=MENU_GROUP:New(Client:GetGroup(),"Set distance to plane",Client.MainMenu) +Client.MenuDistance50=MENU_GROUP_COMMAND:New(Client:GetGroup(),"50 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=50/1000}) +Client.MenuDistance100=MENU_GROUP_COMMAND:New(Client:GetGroup(),"100 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=100/1000}) +Client.MenuDistance150=MENU_GROUP_COMMAND:New(Client:GetGroup(),"150 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=150/1000}) +Client.MenuDistance200=MENU_GROUP_COMMAND:New(Client:GetGroup(),"200 meter",Client.MenuDistance,self._MenuMessages,{MenuSelf=self,Distance=200/1000}) +else +if Client.MainMenu then +Client.MainMenu:Remove() +end +end +local ClientID=Client:GetID() +self:T(ClientID) +if not self.TrackingMissiles[ClientID]then +self.TrackingMissiles[ClientID]={} +end +self.TrackingMissiles[ClientID].Client=Client +if not self.TrackingMissiles[ClientID].MissileData then +self.TrackingMissiles[ClientID].MissileData={} +end +end +function MISSILETRAINER:New(Distance,Briefing) +local self=BASE:Inherit(self,BASE:New()) +self:F(Distance) +if Briefing then +self.Briefing=Briefing +end +self.Schedulers={} +self.SchedulerID=0 +self.MessageInterval=2 +self.MessageLastTime=timer.getTime() +self.Distance=Distance/1000 +self:HandleEvent(EVENTS.Shot) +self.DBClients=SET_CLIENT:New():FilterStart() +self.DBClients:ForEachClient( +function(Client) +self:F("ForEach:"..Client.UnitName) +Client:Alive(self._Alive,self) +end +) +self.MessagesOnOff=true +self.TrackingToAll=false +self.TrackingOnOff=true +self.TrackingFrequency=3 +self.AlertsToAll=true +self.AlertsHitsOnOff=true +self.AlertsLaunchesOnOff=true +self.DetailsRangeOnOff=true +self.DetailsBearingOnOff=true +self.MenusOnOff=true +self.TrackingMissiles={} +self.TrackingScheduler=SCHEDULER:New(self,self._TrackMissiles,{},0.5,0.05,0) +return self +end +function MISSILETRAINER:InitMessagesOnOff(MessagesOnOff) +self:F(MessagesOnOff) +self.MessagesOnOff=MessagesOnOff +if self.MessagesOnOff==true then +MESSAGE:New("Messages ON",15,"Menu"):ToAll() +else +MESSAGE:New("Messages OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitTrackingToAll(TrackingToAll) +self:F(TrackingToAll) +self.TrackingToAll=TrackingToAll +if self.TrackingToAll==true then +MESSAGE:New("Missile tracking to all players ON",15,"Menu"):ToAll() +else +MESSAGE:New("Missile tracking to all players OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitTrackingOnOff(TrackingOnOff) +self:F(TrackingOnOff) +self.TrackingOnOff=TrackingOnOff +if self.TrackingOnOff==true then +MESSAGE:New("Missile tracking ON",15,"Menu"):ToAll() +else +MESSAGE:New("Missile tracking OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitTrackingFrequency(TrackingFrequency) +self:F(TrackingFrequency) +self.TrackingFrequency=self.TrackingFrequency+TrackingFrequency +if self.TrackingFrequency<0.5 then +self.TrackingFrequency=0.5 +end +if self.TrackingFrequency then +MESSAGE:New("Missile tracking frequency is "..self.TrackingFrequency.." seconds.",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitAlertsToAll(AlertsToAll) +self:F(AlertsToAll) +self.AlertsToAll=AlertsToAll +if self.AlertsToAll==true then +MESSAGE:New("Alerts to all players ON",15,"Menu"):ToAll() +else +MESSAGE:New("Alerts to all players OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitAlertsHitsOnOff(AlertsHitsOnOff) +self:F(AlertsHitsOnOff) +self.AlertsHitsOnOff=AlertsHitsOnOff +if self.AlertsHitsOnOff==true then +MESSAGE:New("Alerts Hits ON",15,"Menu"):ToAll() +else +MESSAGE:New("Alerts Hits OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitAlertsLaunchesOnOff(AlertsLaunchesOnOff) +self:F(AlertsLaunchesOnOff) +self.AlertsLaunchesOnOff=AlertsLaunchesOnOff +if self.AlertsLaunchesOnOff==true then +MESSAGE:New("Alerts Launches ON",15,"Menu"):ToAll() +else +MESSAGE:New("Alerts Launches OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitRangeOnOff(DetailsRangeOnOff) +self:F(DetailsRangeOnOff) +self.DetailsRangeOnOff=DetailsRangeOnOff +if self.DetailsRangeOnOff==true then +MESSAGE:New("Range display ON",15,"Menu"):ToAll() +else +MESSAGE:New("Range display OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitBearingOnOff(DetailsBearingOnOff) +self:F(DetailsBearingOnOff) +self.DetailsBearingOnOff=DetailsBearingOnOff +if self.DetailsBearingOnOff==true then +MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll() +else +MESSAGE:New("Bearing display OFF",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER:InitMenusOnOff(MenusOnOff) +self:F(MenusOnOff) +self.MenusOnOff=MenusOnOff +if self.MenusOnOff==true then +MESSAGE:New("Menus are ENABLED (only when a player rejoins a slot)",15,"Menu"):ToAll() +else +MESSAGE:New("Menus are DISABLED",15,"Menu"):ToAll() +end +return self +end +function MISSILETRAINER._MenuMessages(MenuParameters) +local self=MenuParameters.MenuSelf +if MenuParameters.MessagesOnOff~=nil then +self:InitMessagesOnOff(MenuParameters.MessagesOnOff) +end +if MenuParameters.TrackingToAll~=nil then +self:InitTrackingToAll(MenuParameters.TrackingToAll) +end +if MenuParameters.TrackingOnOff~=nil then +self:InitTrackingOnOff(MenuParameters.TrackingOnOff) +end +if MenuParameters.TrackingFrequency~=nil then +self:InitTrackingFrequency(MenuParameters.TrackingFrequency) +end +if MenuParameters.AlertsToAll~=nil then +self:InitAlertsToAll(MenuParameters.AlertsToAll) +end +if MenuParameters.AlertsHitsOnOff~=nil then +self:InitAlertsHitsOnOff(MenuParameters.AlertsHitsOnOff) +end +if MenuParameters.AlertsLaunchesOnOff~=nil then +self:InitAlertsLaunchesOnOff(MenuParameters.AlertsLaunchesOnOff) +end +if MenuParameters.DetailsRangeOnOff~=nil then +self:InitRangeOnOff(MenuParameters.DetailsRangeOnOff) +end +if MenuParameters.DetailsBearingOnOff~=nil then +self:InitBearingOnOff(MenuParameters.DetailsBearingOnOff) +end +if MenuParameters.Distance~=nil then +self.Distance=MenuParameters.Distance +MESSAGE:New("Hit detection distance set to "..(self.Distance*1000).." meters",15,"Menu"):ToAll() +end +end +function MISSILETRAINER:OnEventShot(EVentData) +self:F({EVentData}) +local TrainerSourceDCSUnit=EVentData.IniDCSUnit +local TrainerSourceDCSUnitName=EVentData.IniDCSUnitName +local TrainerWeapon=EVentData.Weapon +local TrainerWeaponName=EVentData.WeaponName +self:T("Missile Launched = "..TrainerWeaponName) +local TrainerTargetDCSUnit=TrainerWeapon:getTarget() +if TrainerTargetDCSUnit then +local TrainerTargetDCSUnitName=Unit.getName(TrainerTargetDCSUnit) +local TrainerTargetSkill=_DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill +self:T(TrainerTargetDCSUnitName) +local Client=self.DBClients:FindClient(TrainerTargetDCSUnitName) +if Client then +local TrainerSourceUnit=UNIT:Find(TrainerSourceDCSUnit) +local TrainerTargetUnit=UNIT:Find(TrainerTargetDCSUnit) +if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then +local Message=MESSAGE:New( +string.format("%s launched a %s", +TrainerSourceUnit:GetTypeName(), +TrainerWeaponName +)..self:_AddRange(Client,TrainerWeapon)..self:_AddBearing(Client,TrainerWeapon),5,"Launch Alert") +if self.AlertsToAll then +Message:ToAll() +else +Message:ToClient(Client) +end +end +local ClientID=Client:GetID() +self:T(ClientID) +local MissileData={} +MissileData.TrainerSourceUnit=TrainerSourceUnit +MissileData.TrainerWeapon=TrainerWeapon +MissileData.TrainerTargetUnit=TrainerTargetUnit +MissileData.TrainerWeaponTypeName=TrainerWeapon:getTypeName() +MissileData.TrainerWeaponLaunched=true +table.insert(self.TrackingMissiles[ClientID].MissileData,MissileData) +end +else +if(TrainerWeapon:getTypeName()=="9M311")then +SCHEDULER:New(TrainerWeapon,TrainerWeapon.destroy,{},1) +else +end +end +end +function MISSILETRAINER:_AddRange(Client,TrainerWeapon) +local RangeText="" +if self.DetailsRangeOnOff then +local PositionMissile=TrainerWeapon:getPoint() +local TargetVec3=Client:GetVec3() +local Range=((PositionMissile.x-TargetVec3.x)^2+ +(PositionMissile.y-TargetVec3.y)^2+ +(PositionMissile.z-TargetVec3.z)^2 +)^0.5/1000 +RangeText=string.format(", at %4.2fkm",Range) +end +return RangeText +end +function MISSILETRAINER:_AddBearing(Client,TrainerWeapon) +local BearingText="" +if self.DetailsBearingOnOff then +local PositionMissile=TrainerWeapon:getPoint() +local TargetVec3=Client:GetVec3() +self:T2({TargetVec3,PositionMissile}) +local DirectionVector={x=PositionMissile.x-TargetVec3.x,y=PositionMissile.y-TargetVec3.y,z=PositionMissile.z-TargetVec3.z} +local DirectionRadians=math.atan2(DirectionVector.z,DirectionVector.x) +if DirectionRadians<0 then +DirectionRadians=DirectionRadians+2*math.pi +end +local DirectionDegrees=DirectionRadians*180/math.pi +BearingText=string.format(", %d degrees",DirectionDegrees) +end +return BearingText +end +function MISSILETRAINER:_TrackMissiles() +self:F2() +local ShowMessages=false +if self.MessagesOnOff and self.MessageLastTime+self.TrackingFrequency<=timer.getTime()then +self.MessageLastTime=timer.getTime() +ShowMessages=true +end +for ClientDataID,ClientData in pairs(self.TrackingMissiles)do +local Client=ClientData.Client +if Client and Client:IsAlive()then +for MissileDataID,MissileData in pairs(ClientData.MissileData)do +self:T3(MissileDataID) +local TrainerSourceUnit=MissileData.TrainerSourceUnit +local TrainerWeapon=MissileData.TrainerWeapon +local TrainerTargetUnit=MissileData.TrainerTargetUnit +local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName +local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched +if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then +local PositionMissile=TrainerWeapon:getPosition().p +local TargetVec3=Client:GetVec3() +local Distance=((PositionMissile.x-TargetVec3.x)^2+ +(PositionMissile.y-TargetVec3.y)^2+ +(PositionMissile.z-TargetVec3.z)^2 +)^0.5/1000 +if Distance<=self.Distance then +TrainerWeapon:destroy() +if self.MessagesOnOff==true and self.AlertsHitsOnOff==true then +self:T("killed") +local Message=MESSAGE:New( +string.format("%s launched by %s killed %s", +TrainerWeapon:getTypeName(), +TrainerSourceUnit:GetTypeName(), +TrainerTargetUnit:GetPlayerName() +),15,"Hit Alert") +if self.AlertsToAll==true then +Message:ToAll() +else +Message:ToClient(Client) +end +MissileData=nil +table.remove(ClientData.MissileData,MissileDataID) +self:T(ClientData.MissileData) +end +end +else +if not(TrainerWeapon and TrainerWeapon:isExist())then +if self.MessagesOnOff==true and self.AlertsLaunchesOnOff==true then +local Message=MESSAGE:New( +string.format("%s launched by %s self destructed!", +TrainerWeaponTypeName, +TrainerSourceUnit:GetTypeName() +),5,"Tracking") +if self.AlertsToAll==true then +Message:ToAll() +else +Message:ToClient(Client) +end +end +MissileData=nil +table.remove(ClientData.MissileData,MissileDataID) +self:T(ClientData.MissileData) +end +end +end +else +self.TrackingMissiles[ClientDataID]=nil +end +end +if ShowMessages==true and self.MessagesOnOff==true and self.TrackingOnOff==true then +for ClientDataID,ClientData in pairs(self.TrackingMissiles)do +local Client=ClientData.Client +ClientData.MessageToClient="" +ClientData.MessageToAll="" +for TrackingDataID,TrackingData in pairs(self.TrackingMissiles)do +for MissileDataID,MissileData in pairs(TrackingData.MissileData)do +local TrainerSourceUnit=MissileData.TrainerSourceUnit +local TrainerWeapon=MissileData.TrainerWeapon +local TrainerTargetUnit=MissileData.TrainerTargetUnit +local TrainerWeaponTypeName=MissileData.TrainerWeaponTypeName +local TrainerWeaponLaunched=MissileData.TrainerWeaponLaunched +if Client and Client:IsAlive()and TrainerSourceUnit and TrainerSourceUnit:IsAlive()and TrainerWeapon and TrainerWeapon:isExist()and TrainerTargetUnit and TrainerTargetUnit:IsAlive()then +if ShowMessages==true then +local TrackingTo +TrackingTo=string.format(" -> %s", +TrainerWeaponTypeName +) +if ClientDataID==TrackingDataID then +if ClientData.MessageToClient==""then +ClientData.MessageToClient="Missiles to You:\n" +end +ClientData.MessageToClient=ClientData.MessageToClient..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).."\n" +else +if self.TrackingToAll==true then +if ClientData.MessageToAll==""then +ClientData.MessageToAll="Missiles to other Players:\n" +end +ClientData.MessageToAll=ClientData.MessageToAll..TrackingTo..self:_AddRange(ClientData.Client,TrainerWeapon)..self:_AddBearing(ClientData.Client,TrainerWeapon).." ( "..TrainerTargetUnit:GetPlayerName().." )\n" +end +end +end +end +end +end +if ClientData.MessageToClient~=""or ClientData.MessageToAll~=""then +local Message=MESSAGE:New(ClientData.MessageToClient..ClientData.MessageToAll,1,"Tracking"):ToClient(Client) +end +end +end +return true +end +ATC_GROUND={ +ClassName="ATC_GROUND", +SetClient=nil, +Airbases=nil, +AirbaseNames=nil, +} +function ATC_GROUND:New(Airbases,AirbaseList) +local self=BASE:Inherit(self,BASE:New()) +self:E({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 +Airbase.ZoneBoundary=_DATABASE:FindAirbase(AirbaseID):GetZone() +Airbase.ZoneRunways={} +for PointsRunwayID,PointsRunway in pairs(Airbase.PointsRunways)do +Airbase.ZoneRunways[PointsRunwayID]=ZONE_POLYGON_BASE:New("Runway "..PointsRunwayID,PointsRunway) +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:E(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:E(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_CAUCASUS={ +ClassName="ATC_GROUND_CAUCASUS", +Airbases={ +[AIRBASE.Caucasus.Anapa_Vityazevo]={ +PointsRunways={ +[1]={ +[1]={["y"]=242140.57142858,["x"]=-6478.8571428583,}, +[2]={["y"]=242188.57142858,["x"]=-6522.0000000011,}, +[3]={["y"]=244124.2857143,["x"]=-4344.0000000011,}, +[4]={["y"]=244068.2857143,["x"]=-4296.5714285726,}, +[5]={["y"]=242140.57142858,["x"]=-6480.0000000011,} +}, +}, +}, +[AIRBASE.Caucasus.Batumi]={ +PointsRunways={ +[1]={ +[1]={["y"]=616442.28571429,["x"]=-355090.28571429,}, +[2]={["y"]=618450.57142857,["x"]=-356522,}, +[3]={["y"]=618407.71428571,["x"]=-356584.85714286,}, +[4]={["y"]=618361.99999999,["x"]=-356554.85714286,}, +[5]={["y"]=618324.85714285,["x"]=-356599.14285715,}, +[6]={["y"]=618250.57142856,["x"]=-356543.42857143,}, +[7]={["y"]=618257.7142857,["x"]=-356496.28571429,}, +[8]={["y"]=618237.7142857,["x"]=-356459.14285715,}, +[9]={["y"]=616555.71428571,["x"]=-355258.85714286,}, +[10]={["y"]=616486.28571428,["x"]=-355280.57142858,}, +[11]={["y"]=616410.57142856,["x"]=-355227.71428572,}, +[12]={["y"]=616441.99999999,["x"]=-355179.14285715,}, +[13]={["y"]=616401.99999999,["x"]=-355147.71428572,}, +[14]={["y"]=616441.42857142,["x"]=-355092.57142858,}, +}, +}, +}, +[AIRBASE.Caucasus.Beslan]={ +PointsRunways={ +[1]={ +[1]={["y"]=842104.57142857,["x"]=-148460.57142857,}, +[2]={["y"]=845225.71428572,["x"]=-148656,}, +[3]={["y"]=845220.57142858,["x"]=-148750,}, +[4]={["y"]=842098.85714286,["x"]=-148556.28571429,}, +[5]={["y"]=842104,["x"]=-148460.28571429,}, +}, +}, +}, +[AIRBASE.Caucasus.Gelendzhik]={ +PointsRunways={ +[1]={ +[1]={["y"]=297834.00000001,["x"]=-51107.428571429,}, +[2]={["y"]=297786.57142858,["x"]=-51068.857142858,}, +[3]={["y"]=298946.57142858,["x"]=-49686.000000001,}, +[4]={["y"]=298993.14285715,["x"]=-49725.714285715,}, +[5]={["y"]=297835.14285715,["x"]=-51107.714285715,}, +}, +}, +}, +[AIRBASE.Caucasus.Gudauta]={ +PointsRunways={ +[1]={ +[1]={["y"]=517096.57142857,["x"]=-197804.57142857,}, +[2]={["y"]=515880.85714285,["x"]=-195590.28571429,}, +[3]={["y"]=515812.28571428,["x"]=-195628.85714286,}, +[4]={["y"]=517036.57142857,["x"]=-197834.57142857,}, +[5]={["y"]=517097.99999999,["x"]=-197807.42857143,}, +}, +}, +}, +[AIRBASE.Caucasus.Kobuleti]={ +PointsRunways={ +[1]={ +[1]={["y"]=634509.71428571,["x"]=-318339.42857144,}, +[2]={["y"]=636767.42857143,["x"]=-317516.57142858,}, +[3]={["y"]=636790,["x"]=-317575.71428572,}, +[4]={["y"]=634531.42857143,["x"]=-318398.00000001,}, +[5]={["y"]=634510.28571429,["x"]=-318339.71428572,}, +}, +}, +}, +[AIRBASE.Caucasus.Krasnodar_Center]={ +PointsRunways={ +[1]={ +[1]={["y"]=369205.42857144,["x"]=11789.142857142,}, +[2]={["y"]=369209.71428572,["x"]=11714.857142856,}, +[3]={["y"]=366699.71428572,["x"]=11581.714285713,}, +[4]={["y"]=366698.28571429,["x"]=11659.142857142,}, +[5]={["y"]=369208.85714286,["x"]=11788.57142857,}, +}, +}, +}, +[AIRBASE.Caucasus.Krasnodar_Pashkovsky]={ +PointsRunways={ +[1]={ +[1]={["y"]=385891.14285715,["x"]=8416.5714285703,}, +[2]={["y"]=385842.28571429,["x"]=8467.9999999989,}, +[3]={["y"]=384180.85714286,["x"]=6917.1428571417,}, +[4]={["y"]=384228.57142858,["x"]=6867.7142857132,}, +[5]={["y"]=385891.14285715,["x"]=8416.5714285703,}, +}, +[2]={ +[1]={["y"]=386714.85714286,["x"]=6674.857142856,}, +[2]={["y"]=386757.71428572,["x"]=6627.7142857132,}, +[3]={["y"]=389028.57142858,["x"]=8741.4285714275,}, +[4]={["y"]=388981.71428572,["x"]=8790.5714285703,}, +[5]={["y"]=386714.57142858,["x"]=6674.5714285703,}, +}, +}, +}, +[AIRBASE.Caucasus.Krymsk]={ +PointsRunways={ +[1]={ +[1]={["y"]=293522.00000001,["x"]=-7567.4285714297,}, +[2]={["y"]=293578.57142858,["x"]=-7616.0000000011,}, +[3]={["y"]=295246.00000001,["x"]=-5591.142857144,}, +[4]={["y"]=295187.71428573,["x"]=-5546.0000000011,}, +[5]={["y"]=293523.14285715,["x"]=-7568.2857142868,}, +}, +}, +}, +[AIRBASE.Caucasus.Kutaisi]={ +PointsRunways={ +[1]={ +[1]={["y"]=682638,["x"]=-285202.28571429,}, +[2]={["y"]=685050.28571429,["x"]=-284507.42857144,}, +[3]={["y"]=685068.85714286,["x"]=-284578.85714286,}, +[4]={["y"]=682657.42857143,["x"]=-285264.28571429,}, +[5]={["y"]=682638.28571429,["x"]=-285202.85714286,}, +}, +}, +}, +[AIRBASE.Caucasus.Maykop_Khanskaya]={ +PointsRunways={ +[1]={ +[1]={["y"]=457005.42857143,["x"]=-27668.000000001,}, +[2]={["y"]=459028.85714286,["x"]=-25168.857142858,}, +[3]={["y"]=459082.57142857,["x"]=-25216.857142858,}, +[4]={["y"]=457060,["x"]=-27714.285714287,}, +[5]={["y"]=457004.57142857,["x"]=-27669.714285715,}, +}, +}, +}, +[AIRBASE.Caucasus.Mineralnye_Vody]={ +PointsRunways={ +[1]={ +[1]={["y"]=703904,["x"]=-50352.571428573,}, +[2]={["y"]=707596.28571429,["x"]=-52094.571428573,}, +[3]={["y"]=707560.57142858,["x"]=-52161.714285716,}, +[4]={["y"]=703871.71428572,["x"]=-50420.571428573,}, +[5]={["y"]=703902,["x"]=-50352.000000002,}, +}, +}, +}, +[AIRBASE.Caucasus.Mozdok]={ +PointsRunways={ +[1]={ +[1]={["y"]=832201.14285715,["x"]=-83699.428571431,}, +[2]={["y"]=832212.57142857,["x"]=-83780.571428574,}, +[3]={["y"]=835730.28571429,["x"]=-83335.714285717,}, +[4]={["y"]=835718.85714286,["x"]=-83246.571428574,}, +[5]={["y"]=832200.57142857,["x"]=-83700.000000002,}, +}, +}, +}, +[AIRBASE.Caucasus.Nalchik]={ +PointsRunways={ +[1]={ +[1]={["y"]=759454.28571429,["x"]=-125551.42857143,}, +[2]={["y"]=759492.85714286,["x"]=-125610.85714286,}, +[3]={["y"]=761406.28571429,["x"]=-124304.28571429,}, +[4]={["y"]=761361.14285714,["x"]=-124239.71428572,}, +[5]={["y"]=759456,["x"]=-125552.57142857,}, +}, +}, +}, +[AIRBASE.Caucasus.Novorossiysk]={ +PointsRunways={ +[1]={ +[1]={["y"]=278673.14285716,["x"]=-41615.142857144,}, +[2]={["y"]=278625.42857144,["x"]=-41570.571428572,}, +[3]={["y"]=279835.42857144,["x"]=-40226.000000001,}, +[4]={["y"]=279882.2857143,["x"]=-40270.000000001,}, +[5]={["y"]=278672.00000001,["x"]=-41614.857142858,}, +}, +}, +}, +[AIRBASE.Caucasus.Senaki_Kolkhi]={ +PointsRunways={ +[1]={ +[1]={["y"]=646060.85714285,["x"]=-281736,}, +[2]={["y"]=646056.57142857,["x"]=-281631.71428571,}, +[3]={["y"]=648442.28571428,["x"]=-281840.28571428,}, +[4]={["y"]=648432.28571428,["x"]=-281918.85714286,}, +[5]={["y"]=646063.71428571,["x"]=-281738.85714286,}, +}, +}, +}, +[AIRBASE.Caucasus.Sochi_Adler]={ +PointsRunways={ +[1]={ +[1]={["y"]=460831.42857143,["x"]=-165180,}, +[2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, +[3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, +[4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, +[5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, +}, +[2]={ +[1]={["y"]=460831.42857143,["x"]=-165180,}, +[2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, +[3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, +[4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, +[5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, +}, +}, +}, +[AIRBASE.Caucasus.Soganlug]={ +PointsRunways={ +[1]={ +[1]={["y"]=894525.71428571,["x"]=-316964,}, +[2]={["y"]=896363.14285714,["x"]=-318634.28571428,}, +[3]={["y"]=896299.14285714,["x"]=-318702.85714286,}, +[4]={["y"]=894464,["x"]=-317031.71428571,}, +[5]={["y"]=894524.57142857,["x"]=-316963.71428571,}, +}, +}, +}, +[AIRBASE.Caucasus.Sukhumi_Babushara]={ +PointsRunways={ +[1]={ +[1]={["y"]=562684,["x"]=-219779.71428571,}, +[2]={["y"]=562717.71428571,["x"]=-219718,}, +[3]={["y"]=566046.85714286,["x"]=-221376.57142857,}, +[4]={["y"]=566012.28571428,["x"]=-221446.57142857,}, +[5]={["y"]=562684.57142857,["x"]=-219782.57142857,}, +}, +}, +}, +[AIRBASE.Caucasus.Tbilisi_Lochini]={ +PointsRunways={ +[1]={ +[1]={["y"]=895261.14285715,["x"]=-314652.28571428,}, +[2]={["y"]=897654.57142857,["x"]=-316523.14285714,}, +[3]={["y"]=897711.71428571,["x"]=-316450.28571429,}, +[4]={["y"]=895327.42857143,["x"]=-314568.85714286,}, +[5]={["y"]=895261.71428572,["x"]=-314656,}, +}, +[2]={ +[1]={["y"]=895605.71428572,["x"]=-314724.57142857,}, +[2]={["y"]=897639.71428572,["x"]=-316148,}, +[3]={["y"]=897683.42857143,["x"]=-316087.14285714,}, +[4]={["y"]=895650,["x"]=-314660,}, +[5]={["y"]=895606,["x"]=-314724.85714286,} +}, +}, +}, +[AIRBASE.Caucasus.Vaziani]={ +PointsRunways={ +[1]={ +[1]={["y"]=902239.14285714,["x"]=-318190.85714286,}, +[2]={["y"]=904014.28571428,["x"]=-319994.57142857,}, +[3]={["y"]=904064.85714285,["x"]=-319945.14285715,}, +[4]={["y"]=902294.57142857,["x"]=-318146,}, +[5]={["y"]=902247.71428571,["x"]=-318190.85714286,}, +}, +}, +}, +}, +} +function ATC_GROUND_CAUCASUS:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND:New(self.Airbases,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,2,RepeatScanSeconds) +end +ATC_GROUND_NEVADA={ +ClassName="ATC_GROUND_NEVADA", +Airbases={ +[AIRBASE.Nevada.Beatty_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-174950.05857143,["x"]=-329679.65,}, +[2]={["y"]=-174946.53828571,["x"]=-331394.03885715,}, +[3]={["y"]=-174967.10971429,["x"]=-331394.32457143,}, +[4]={["y"]=-174971.01828571,["x"]=-329682.59171429,}, +}, +}, +}, +[AIRBASE.Nevada.Boulder_City_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-1317.841714286,["x"]=-429014.92857142,}, +[2]={["y"]=-951.26228571458,["x"]=-430310.21142856,}, +[3]={["y"]=-978.11942857172,["x"]=-430317.06857142,}, +[4]={["y"]=-1347.5088571432,["x"]=-429023.98485713,}, +}, +[2]={ +[1]={["y"]=-1879.955714286,["x"]=-429783.83742856,}, +[2]={["y"]=-256.25257142886,["x"]=-430023.63542856,}, +[3]={["y"]=-260.25257142886,["x"]=-430048.77828571,}, +[4]={["y"]=-1883.955714286,["x"]=-429807.83742856,}, +}, +}, +}, +[AIRBASE.Nevada.Creech_AFB]={ +PointsRunways={ +[1]={ +[1]={["y"]=-74234.729142857,["x"]=-360501.80857143,}, +[2]={["y"]=-77606.122285714,["x"]=-360417.86542857,}, +[3]={["y"]=-77608.578,["x"]=-360486.13428571,}, +[4]={["y"]=-74237.930571428,["x"]=-360586.25628571,}, +}, +[2]={ +[1]={["y"]=-75807.571428572,["x"]=-359073.42857142,}, +[2]={["y"]=-74770.142857144,["x"]=-360581.71428571,}, +[3]={["y"]=-74641.285714287,["x"]=-360585.42857142,}, +[4]={["y"]=-75734.142857144,["x"]=-359023.14285714,}, +}, +}, +}, +[AIRBASE.Nevada.Echo_Bay]={ +PointsRunways={ +[1]={ +[1]={["y"]=33182.919428572,["x"]=-388698.21657142,}, +[2]={["y"]=34202.543142857,["x"]=-388469.55485714,}, +[3]={["y"]=34207.686,["x"]=-388488.69771428,}, +[4]={["y"]=33185.422285715,["x"]=-388717.82228571,}, +}, +}, +}, +[AIRBASE.Nevada.Groom_Lake_AFB]={ +PointsRunways={ +[1]={ +[1]={["y"]=-85971.465428571,["x"]=-290567.77,}, +[2]={["y"]=-87691.155428571,["x"]=-286637.75428571,}, +[3]={["y"]=-87756.714285715,["x"]=-286663.99999999,}, +[4]={["y"]=-86035.940285714,["x"]=-290598.81314286,}, +}, +[2]={ +[1]={["y"]=-86741.547142857,["x"]=-290353.31971428,}, +[2]={["y"]=-89672.714285714,["x"]=-283546.57142855,}, +[3]={["y"]=-89772.142857143,["x"]=-283587.71428569,}, +[4]={["y"]=-86799.623714285,["x"]=-290374.16771428,}, +}, +}, +}, +[AIRBASE.Nevada.Henderson_Executive_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-25837.500571429,["x"]=-426404.25257142,}, +[2]={["y"]=-25843.509428571,["x"]=-428752.67942856,}, +[3]={["y"]=-25902.343714286,["x"]=-428749.96399999,}, +[4]={["y"]=-25934.667142857,["x"]=-426411.45657142,}, +}, +[2]={ +[1]={["y"]=-25650.296285714,["x"]=-426510.17971428,}, +[2]={["y"]=-25632.443428571,["x"]=-428297.11428571,}, +[3]={["y"]=-25686.690285714,["x"]=-428299.37457142,}, +[4]={["y"]=-25708.296285714,["x"]=-426515.15114285,}, +}, +}, +}, +[AIRBASE.Nevada.Jean_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-42549.187142857,["x"]=-449663.23257143,}, +[2]={["y"]=-43367.466285714,["x"]=-451044.77657143,}, +[3]={["y"]=-43395.180571429,["x"]=-451028.20514286,}, +[4]={["y"]=-42579.893142857,["x"]=-449648.18371428,}, +}, +[2]={ +[1]={["y"]=-42588.359428572,["x"]=-449900.14342857,}, +[2]={["y"]=-43349.698285714,["x"]=-451185.46857143,}, +[3]={["y"]=-43369.624571429,["x"]=-451173.49342857,}, +[4]={["y"]=-42609.216571429,["x"]=-449891.28628571,}, +}, +}, +}, +[AIRBASE.Nevada.Laughlin_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=28231.600857143,["x"]=-515555.94114286,}, +[2]={["y"]=28453.728285714,["x"]=-518170.78885714,}, +[3]={["y"]=28370.788285714,["x"]=-518176.25742857,}, +[4]={["y"]=28138.022857143,["x"]=-515573.07514286,}, +}, +[2]={ +[1]={["y"]=28231.600857143,["x"]=-515555.94114286,}, +[2]={["y"]=28453.728285714,["x"]=-518170.78885714,}, +[3]={["y"]=28370.788285714,["x"]=-518176.25742857,}, +[4]={["y"]=28138.022857143,["x"]=-515573.07514286,}, +}, +}, +}, +[AIRBASE.Nevada.Lincoln_County]={ +PointsRunways={ +[1]={ +[1]={["y"]=33222.34171429,["x"]=-223959.40171429,}, +[2]={["y"]=33200.040000004,["x"]=-225369.36828572,}, +[3]={["y"]=33177.634571428,["x"]=-225369.21485715,}, +[4]={["y"]=33201.198857147,["x"]=-223960.54457143,}, +}, +}, +}, +[AIRBASE.Nevada.McCarran_International_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-29406.035714286,["x"]=-416102.48199999,}, +[2]={["y"]=-24680.714285715,["x"]=-416003.14285713,}, +[3]={["y"]=-24681.857142858,["x"]=-415926.57142856,}, +[4]={["y"]=-29408.42857143,["x"]=-416016.57142856,}, +}, +[2]={ +[1]={["y"]=-28567.221714286,["x"]=-416378.61799999,}, +[2]={["y"]=-25109.912285714,["x"]=-416309.92914285,}, +[3]={["y"]=-25112.508,["x"]=-416240.78714285,}, +[4]={["y"]=-28576.247428571,["x"]=-416308.49514285,}, +}, +[3]={ +[1]={["y"]=-29255.953142857,["x"]=-416307.10657142,}, +[2]={["y"]=-28005.571428572,["x"]=-413449.7142857,}, +[3]={["y"]=-28068.714285715,["x"]=-413422.85714284,}, +[4]={["y"]=-29331.000000001,["x"]=-416275.7142857,}, +}, +[4]={ +[1]={["y"]=-28994.901714286,["x"]=-416423.0522857,}, +[2]={["y"]=-27697.571428572,["x"]=-413464.57142856,}, +[3]={["y"]=-27767.857142858,["x"]=-413434.28571427,}, +[4]={["y"]=-29073.000000001,["x"]=-416386.85714284,}, +}, +}, +}, +[AIRBASE.Nevada.Mesquite]={ +PointsRunways={ +[1]={ +[1]={["y"]=68188.340285714,["x"]=-330302.54742857,}, +[2]={["y"]=68911.303428571,["x"]=-328920.76571429,}, +[3]={["y"]=68936.927142857,["x"]=-328933.888,}, +[4]={["y"]=68212.460285714,["x"]=-330317.19171429,}, +}, +}, +}, +[AIRBASE.Nevada.Mina_Airport_3Q0]={ +PointsRunways={ +[1]={ +[1]={["y"]=-290054.57371429,["x"]=-160930.02228572,}, +[2]={["y"]=-289469.77457143,["x"]=-162048.73571429,}, +[3]={["y"]=-289520.06028572,["x"]=-162074.73571429,}, +[4]={["y"]=-290104.69085714,["x"]=-160956.19457143,}, +}, +}, +}, +[AIRBASE.Nevada.Nellis_AFB]={ +PointsRunways={ +[1]={ +[1]={["y"]=-18614.218571428,["x"]=-399437.91085714,}, +[2]={["y"]=-16217.857142857,["x"]=-396596.85714286,}, +[3]={["y"]=-16300.142857143,["x"]=-396530,}, +[4]={["y"]=-18692.543428571,["x"]=-399381.31114286,}, +}, +[2]={ +[1]={["y"]=-18388.948857143,["x"]=-399630.51828571,}, +[2]={["y"]=-16011,["x"]=-396806.85714286,}, +[3]={["y"]=-16074.714285714,["x"]=-396751.71428572,}, +[4]={["y"]=-18451.571428572,["x"]=-399580.85714285,}, +}, +}, +}, +[AIRBASE.Nevada.Pahute_Mesa_Airstrip]={ +PointsRunways={ +[1]={ +[1]={["y"]=-132690.40942857,["x"]=-302733.53085714,}, +[2]={["y"]=-133112.43228571,["x"]=-304499.70742857,}, +[3]={["y"]=-133179.91685714,["x"]=-304485.544,}, +[4]={["y"]=-132759.988,["x"]=-302723.326,}, +}, +}, +}, +[AIRBASE.Nevada.Tonopah_Test_Range_Airfield]={ +PointsRunways={ +[1]={ +[1]={["y"]=-175389.162,["x"]=-224778.07685715,}, +[2]={["y"]=-173942.15485714,["x"]=-228210.27571429,}, +[3]={["y"]=-174001.77085714,["x"]=-228233.60371429,}, +[4]={["y"]=-175452.38685714,["x"]=-224806.84200001,}, +}, +}, +}, +[AIRBASE.Nevada.Tonopah_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-202128.25228571,["x"]=-196701.34314286,}, +[2]={["y"]=-201562.40828571,["x"]=-198814.99714286,}, +[3]={["y"]=-201591.44828571,["x"]=-198820.93714286,}, +[4]={["y"]=-202156.06828571,["x"]=-196707.68714286,}, +}, +[2]={ +[1]={["y"]=-202084.57171428,["x"]=-196722.02228572,}, +[2]={["y"]=-200592.75485714,["x"]=-197768.05571429,}, +[3]={["y"]=-200605.37285714,["x"]=-197783.49228572,}, +[4]={["y"]=-202097.14314285,["x"]=-196739.16514286,}, +}, +}, +}, +[AIRBASE.Nevada.North_Las_Vegas]={ +PointsRunways={ +[1]={ +[1]={["y"]=-32599.017714286,["x"]=-400913.26485714,}, +[2]={["y"]=-30881.068857143,["x"]=-400837.94628571,}, +[3]={["y"]=-30879.354571428,["x"]=-400873.08914285,}, +[4]={["y"]=-32595.966285714,["x"]=-400947.13571428,}, +}, +[2]={ +[1]={["y"]=-32499.448571428,["x"]=-400690.99514285,}, +[2]={["y"]=-31247.514857143,["x"]=-401868.95571428,}, +[3]={["y"]=-31271.802857143,["x"]=-401894.97857142,}, +[4]={["y"]=-32520.02,["x"]=-400716.99514285,}, +}, +[3]={ +[1]={["y"]=-31865.254857143,["x"]=-400999.74057143,}, +[2]={["y"]=-30893.604,["x"]=-401908.85742857,}, +[3]={["y"]=-30915.578857143,["x"]=-401936.03685714,}, +[4]={["y"]=-31884.969142858,["x"]=-401020.59771429,}, +}, +}, +}, +}, +} +function ATC_GROUND_NEVADA:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND:New(self.Airbases,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,2,RepeatScanSeconds) +end +ATC_GROUND_NORMANDY={ +ClassName="ATC_GROUND_NORMANDY", +Airbases={ +[AIRBASE.Normandy.Azeville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-74194.387714285,["x"]=-2691.1399999998,}, +[2]={["y"]=-73160.282571428,["x"]=-2310.0274285712,}, +[3]={["y"]=-73141.711142857,["x"]=-2357.7417142855,}, +[4]={["y"]=-74176.959142857,["x"]=-2741.997142857,}, +}, +}, +}, +[AIRBASE.Normandy.Bazenville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-19246.209999999,["x"]=-21246.748,}, +[2]={["y"]=-17883.70142857,["x"]=-20219.009714285,}, +[3]={["y"]=-17855.415714285,["x"]=-20256.438285714,}, +[4]={["y"]=-19217.791999999,["x"]=-21283.597714285,}, +}, +}, +}, +[AIRBASE.Normandy.Beny_sur_Mer]={ +PointsRunways={ +[1]={ +[1]={["y"]=-8592.7442857133,["x"]=-20386.15542857,}, +[2]={["y"]=-8404.4931428561,["x"]=-21744.113142856,}, +[3]={["y"]=-8267.9917142847,["x"]=-21724.97742857,}, +[4]={["y"]=-8451.0482857133,["x"]=-20368.87542857,}, +}, +}, +}, +[AIRBASE.Normandy.Beuzeville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-71552.573428571,["x"]=-8744.3688571427,}, +[2]={["y"]=-72577.765714285,["x"]=-9638.5682857141,}, +[3]={["y"]=-72609.304285714,["x"]=-9601.2954285712,}, +[4]={["y"]=-71585.849428571,["x"]=-8709.9648571426,}, +}, +}, +}, +[AIRBASE.Normandy.Biniville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-84757.320285714,["x"]=-7377.1354285713,}, +[2]={["y"]=-84271.482,["x"]=-7956.4859999999,}, +[3]={["y"]=-84299.482,["x"]=-7981.6288571427,}, +[4]={["y"]=-84784.969714286,["x"]=-7402.0588571427,}, +}, +}, +}, +[AIRBASE.Normandy.Brucheville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-65546.792857142,["x"]=-14615.640857143,}, +[2]={["y"]=-66914.692,["x"]=-15232.713714285,}, +[3]={["y"]=-66896.527714285,["x"]=-15271.948571428,}, +[4]={["y"]=-65528.393714285,["x"]=-14657.995714286,}, +}, +}, +}, +[AIRBASE.Normandy.Cardonville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-54280.445428571,["x"]=-15843.749142857,}, +[2]={["y"]=-53646.998571428,["x"]=-17143.012285714,}, +[3]={["y"]=-53683.93,["x"]=-17161.317428571,}, +[4]={["y"]=-54323.354571428,["x"]=-15855.004,}, +}, +}, +}, +[AIRBASE.Normandy.Carpiquet]={ +PointsRunways={ +[1]={ +[1]={["y"]=-10751.325714285,["x"]=-34229.494,}, +[2]={["y"]=-9283.5279999993,["x"]=-35192.352857142,}, +[3]={["y"]=-9325.2005714274,["x"]=-35260.967714285,}, +[4]={["y"]=-10794.90942857,["x"]=-34287.041428571,}, +}, +}, +}, +[AIRBASE.Normandy.Chailey]={ +PointsRunways={ +[1]={ +[1]={["y"]=12895.585714292,["x"]=164683.05657144,}, +[2]={["y"]=11410.727142863,["x"]=163606.54485715,}, +[3]={["y"]=11363.012857149,["x"]=163671.97342858,}, +[4]={["y"]=12797.537142863,["x"]=164711.01857144,}, +[5]={["y"]=12862.902857149,["x"]=164726.99685715,}, +}, +[2]={ +[1]={["y"]=11805.316000006,["x"]=164502.90971429,}, +[2]={["y"]=11997.280857149,["x"]=163032.65542858,}, +[3]={["y"]=11918.640857149,["x"]=163023.04657144,}, +[4]={["y"]=11726.973428578,["x"]=164489.94257143,}, +}, +}, +}, +[AIRBASE.Normandy.Chippelle]={ +PointsRunways={ +[1]={ +[1]={["y"]=-48540.313999999,["x"]=-28884.795999999,}, +[2]={["y"]=-47251.820285713,["x"]=-28140.128571427,}, +[3]={["y"]=-47274.551714285,["x"]=-28103.758285713,}, +[4]={["y"]=-48555.657714285,["x"]=-28839.90142857,}, +}, +}, +}, +[AIRBASE.Normandy.Cretteville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-78351.723142857,["x"]=-18177.725428571,}, +[2]={["y"]=-77220.322285714,["x"]=-19125.687714286,}, +[3]={["y"]=-77247.899428571,["x"]=-19158.49,}, +[4]={["y"]=-78380.008857143,["x"]=-18208.011142857,}, +}, +}, +}, +[AIRBASE.Normandy.Cricqueville_en_Bessin]={ +PointsRunways={ +[1]={ +[1]={["y"]=-50875.034571428,["x"]=-14322.404571428,}, +[2]={["y"]=-50681.148571428,["x"]=-15825.258,}, +[3]={["y"]=-50717.434285713,["x"]=-15829.829428571,}, +[4]={["y"]=-50910.569428571,["x"]=-14327.562857142,}, +}, +}, +}, +[AIRBASE.Normandy.Deux_Jumeaux]={ +PointsRunways={ +[1]={ +[1]={["y"]=-49575.410857142,["x"]=-16575.161142857,}, +[2]={["y"]=-48149.077999999,["x"]=-16952.193428571,}, +[3]={["y"]=-48159.935142856,["x"]=-16996.764857142,}, +[4]={["y"]=-49584.839428571,["x"]=-16617.732571428,}, +}, +}, +}, +[AIRBASE.Normandy.Evreux]={ +PointsRunways={ +[1]={ +[1]={["y"]=112906.84828572,["x"]=-45585.824857142,}, +[2]={["y"]=112050.38228572,["x"]=-46811.871999999,}, +[3]={["y"]=111980.05371429,["x"]=-46762.173142856,}, +[4]={["y"]=112833.54542857,["x"]=-45540.010571428,}, +}, +[2]={ +[1]={["y"]=112046.02085714,["x"]=-45091.056571428,}, +[2]={["y"]=112488.668,["x"]=-46623.617999999,}, +[3]={["y"]=112405.66914286,["x"]=-46647.419142856,}, +[4]={["y"]=111966.03657143,["x"]=-45112.604285713,}, +}, +}, +}, +[AIRBASE.Normandy.Ford_AF]={ +PointsRunways={ +[1]={ +[1]={["y"]=-26506.13971428,["x"]=147514.39971429,}, +[2]={["y"]=-25012.977428565,["x"]=147566.14485715,}, +[3]={["y"]=-25009.851428565,["x"]=147482.63600001,}, +[4]={["y"]=-26503.693999994,["x"]=147427.33228572,}, +}, +[2]={ +[1]={["y"]=-25169.701999994,["x"]=148421.09257143,}, +[2]={["y"]=-26092.421999994,["x"]=147190.89628572,}, +[3]={["y"]=-26158.136285708,["x"]=147240.89628572,}, +[4]={["y"]=-25252.357999994,["x"]=148448.64457143,}, +}, +}, +}, +[AIRBASE.Normandy.Funtington]={ +PointsRunways={ +[1]={ +[1]={["y"]=-44698.388571423,["x"]=152952.17257143,}, +[2]={["y"]=-46452.993142851,["x"]=152388.77885714,}, +[3]={["y"]=-46476.361142851,["x"]=152470.05885714,}, +[4]={["y"]=-44787.256571423,["x"]=153009.52,}, +[5]={["y"]=-44715.581428566,["x"]=153002.08714286,}, +}, +[2]={ +[1]={["y"]=-45792.665999994,["x"]=153123.894,}, +[2]={["y"]=-46068.084857137,["x"]=151665.98342857,}, +[3]={["y"]=-46148.632285708,["x"]=151681.58685714,}, +[4]={["y"]=-45871.25971428,["x"]=153136.82714286,}, +}, +}, +}, +[AIRBASE.Normandy.Lantheuil]={ +PointsRunways={ +[1]={ +[1]={["y"]=-17158.84542857,["x"]=-24602.999428571,}, +[2]={["y"]=-15978.59342857,["x"]=-23922.978571428,}, +[3]={["y"]=-15932.021999999,["x"]=-24004.121428571,}, +[4]={["y"]=-17090.734857142,["x"]=-24673.248,}, +}, +}, +}, +[AIRBASE.Normandy.Lessay]={ +PointsRunways={ +[1]={ +[1]={["y"]=-87667.304571429,["x"]=-33220.165714286,}, +[2]={["y"]=-86146.607714286,["x"]=-34248.483142857,}, +[3]={["y"]=-86191.538285714,["x"]=-34316.991142857,}, +[4]={["y"]=-87712.212,["x"]=-33291.774857143,}, +}, +[2]={ +[1]={["y"]=-87125.123142857,["x"]=-34183.682571429,}, +[2]={["y"]=-85803.278285715,["x"]=-33498.428857143,}, +[3]={["y"]=-85768.408285715,["x"]=-33570.13,}, +[4]={["y"]=-87087.688571429,["x"]=-34258.272285715,}, +}, +}, +}, +[AIRBASE.Normandy.Lignerolles]={ +PointsRunways={ +[1]={ +[1]={["y"]=-35279.611714285,["x"]=-35232.026857142,}, +[2]={["y"]=-33804.948857142,["x"]=-35770.713999999,}, +[3]={["y"]=-33789.876285713,["x"]=-35726.655714284,}, +[4]={["y"]=-35263.548285713,["x"]=-35192.75542857,}, +}, +}, +}, +[AIRBASE.Normandy.Longues_sur_Mer]={ +PointsRunways={ +[1]={ +[1]={["y"]=-29444.070285713,["x"]=-16334.105428571,}, +[2]={["y"]=-28265.52942857,["x"]=-17011.557999999,}, +[3]={["y"]=-28344.74742857,["x"]=-17143.587999999,}, +[4]={["y"]=-29529.616285713,["x"]=-16477.766571428,}, +}, +}, +}, +[AIRBASE.Normandy.Maupertus]={ +PointsRunways={ +[1]={ +[1]={["y"]=-85605.340857143,["x"]=16175.267714286,}, +[2]={["y"]=-84132.567142857,["x"]=15895.905714286,}, +[3]={["y"]=-84139.995142857,["x"]=15847.623714286,}, +[4]={["y"]=-85613.626571429,["x"]=16132.410571429,}, +}, +}, +}, +[AIRBASE.Normandy.Meautis]={ +PointsRunways={ +[1]={ +[1]={["y"]=-72642.527714286,["x"]=-24593.622285714,}, +[2]={["y"]=-71298.672571429,["x"]=-24352.651142857,}, +[3]={["y"]=-71290.101142857,["x"]=-24398.365428571,}, +[4]={["y"]=-72631.715714286,["x"]=-24639.966857143,}, +}, +}, +}, +[AIRBASE.Normandy.Le_Molay]={ +PointsRunways={ +[1]={ +[1]={["y"]=-41876.526857142,["x"]=-26701.052285713,}, +[2]={["y"]=-40979.545714285,["x"]=-25675.045999999,}, +[3]={["y"]=-41017.687428571,["x"]=-25644.272571427,}, +[4]={["y"]=-41913.638285713,["x"]=-26665.137999999,}, +}, +}, +}, +[AIRBASE.Normandy.Needs_Oar_Point]={ +PointsRunways={ +[1]={ +[1]={["y"]=-83882.441142851,["x"]=141429.83314286,}, +[2]={["y"]=-85138.159428566,["x"]=140187.52828572,}, +[3]={["y"]=-85208.323428566,["x"]=140161.04371429,}, +[4]={["y"]=-85245.751999994,["x"]=140201.61514286,}, +[5]={["y"]=-83939.966571423,["x"]=141485.22085714,}, +}, +[2]={ +[1]={["y"]=-84528.76571428,["x"]=141988.01428572,}, +[2]={["y"]=-84116.98971428,["x"]=140565.78685714,}, +[3]={["y"]=-84199.35771428,["x"]=140541.14685714,}, +[4]={["y"]=-84605.051428566,["x"]=141966.01428572,}, +}, +}, +}, +[AIRBASE.Normandy.Picauville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-80808.838571429,["x"]=-11834.554571428,}, +[2]={["y"]=-79531.574285714,["x"]=-12311.274,}, +[3]={["y"]=-79549.355428571,["x"]=-12356.928285714,}, +[4]={["y"]=-80827.815142857,["x"]=-11901.835142857,}, +}, +}, +}, +[AIRBASE.Normandy.Rucqueville]={ +PointsRunways={ +[1]={ +[1]={["y"]=-20023.988857141,["x"]=-26569.565428571,}, +[2]={["y"]=-18688.92542857,["x"]=-26571.086571428,}, +[3]={["y"]=-18688.012571427,["x"]=-26611.252285713,}, +[4]={["y"]=-20022.218857141,["x"]=-26608.505428571,}, +}, +}, +}, +[AIRBASE.Normandy.Saint_Pierre_du_Mont]={ +PointsRunways={ +[1]={ +[1]={["y"]=-48015.384571428,["x"]=-11886.631714285,}, +[2]={["y"]=-46540.412285713,["x"]=-11945.226571428,}, +[3]={["y"]=-46541.349999999,["x"]=-11991.174571428,}, +[4]={["y"]=-48016.837142856,["x"]=-11929.371142857,}, +}, +}, +}, +[AIRBASE.Normandy.Sainte_Croix_sur_Mer]={ +PointsRunways={ +[1]={ +[1]={["y"]=-15877.817999999,["x"]=-18812.579999999,}, +[2]={["y"]=-14464.377142856,["x"]=-18807.46,}, +[3]={["y"]=-14463.879714285,["x"]=-18759.706857142,}, +[4]={["y"]=-15878.229142856,["x"]=-18764.071428571,}, +}, +}, +}, +[AIRBASE.Normandy.Sainte_Laurent_sur_Mer]={ +PointsRunways={ +[1]={ +[1]={["y"]=-41676.834857142,["x"]=-14475.109428571,}, +[2]={["y"]=-40566.11142857,["x"]=-14817.319999999,}, +[3]={["y"]=-40579.543999999,["x"]=-14860.059999999,}, +[4]={["y"]=-41687.120571427,["x"]=-14509.680857142,}, +}, +}, +}, +[AIRBASE.Normandy.Sommervieu]={ +PointsRunways={ +[1]={ +[1]={["y"]=-26821.913714284,["x"]=-21390.466571427,}, +[2]={["y"]=-25465.308857142,["x"]=-21296.859999999,}, +[3]={["y"]=-25462.451714284,["x"]=-21343.717142856,}, +[4]={["y"]=-26818.002285713,["x"]=-21440.532857142,}, +}, +}, +}, +[AIRBASE.Normandy.Tangmere]={ +PointsRunways={ +[1]={ +[1]={["y"]=-34684.581142851,["x"]=150459.61657143,}, +[2]={["y"]=-33250.625428566,["x"]=149954.17,}, +[3]={["y"]=-33275.724285708,["x"]=149874.69028572,}, +[4]={["y"]=-34709.020571423,["x"]=150377.93742857,}, +}, +[2]={ +[1]={["y"]=-33103.438857137,["x"]=150812.72542857,}, +[2]={["y"]=-34410.246285708,["x"]=150009.73142857,}, +[3]={["y"]=-34453.535142851,["x"]=150082.02685714,}, +[4]={["y"]=-33176.545999994,["x"]=150870.22542857,}, +}, +}, +}, +[AIRBASE.Normandy.Argentan]={ +PointsRunways={ +[1]={ +[1]={["y"]=22322.280338032,["x"]=-78607.309765269,}, +[2]={["y"]=23032.778713963,["x"]=-78967.17709893,}, +[3]={["y"]=23015.27074041,["x"]=-79008.02903722,}, +[4]={["y"]=22299.944963827,["x"]=-78650.366148928,}, +}, +}, +}, +[AIRBASE.Normandy.Goulet]={ +PointsRunways={ +[1]={ +[1]={["y"]=24901.788373185,["x"]=-89139.367511763,}, +[2]={["y"]=25459.965967043,["x"]=-89709.67940114,}, +[3]={["y"]=25422.459962713,["x"]=-89741.669816598,}, +[4]={["y"]=24857.663662208,["x"]=-89173.56416277,}, +}, +}, +}, +[AIRBASE.Normandy.Essay]={ +PointsRunways={ +[1]={ +[1]={["y"]=44610.072022849,["x"]=-105469.21149064,}, +[2]={["y"]=45417.939023956,["x"]=-105536.08535277,}, +[3]={["y"]=45412.558368383,["x"]=-105585.27991801,}, +[4]={["y"]=44602.38537203,["x"]=-105516.10006064,}, +}, +}, +}, +[AIRBASE.Normandy.Hauterive]={ +PointsRunways={ +[1]={ +[1]={["y"]=40617.185360953,["x"]=-107657.10147517,}, +[2]={["y"]=41114.628372034,["x"]=-108298.77015609,}, +[3]={["y"]=41080.006684855,["x"]=-108319.06562788,}, +[4]={["y"]=40584.558402807,["x"]=-107692.29370481,}, +}, +}, +}, +[AIRBASE.Normandy.Vrigny]={ +PointsRunways={ +[1]={ +[1]={["y"]=24892.131051827,["x"]=-89131.628297486,}, +[2]={["y"]=25469.738000575,["x"]=-89709.235246234,}, +[3]={["y"]=25418.869206793,["x"]=-89738.771965204,}, +[4]={["y"]=24859.312475193,["x"]=-89171.010589446,}, +}, +}, +}, +[AIRBASE.Normandy.Barville]={ +PointsRunways={ +[1]={ +[1]={["y"]=49027.850333166,["x"]=-109217.05049066,}, +[2]={["y"]=49755.022185805,["x"]=-110346.63783457,}, +[3]={["y"]=49682.657996586,["x"]=-110401.35222154,}, +[4]={["y"]=48921.951519675,["x"]=-109285.88471943,}, +}, +[2]={ +[1]={["y"]=48429.522036941,["x"]=-109818.90874734,}, +[2]={["y"]=49746.197284681,["x"]=-109954.81222465,}, +[3]={["y"]=49735.607403332,["x"]=-110032.47135455,}, +[4]={["y"]=48420.697135816,["x"]=-109900.09783768,}, +}, +}, +}, +[AIRBASE.Normandy.Conches]={ +PointsRunways={ +[1]={ +[1]={["y"]=95099.187473266,["x"]=-56389.619005858,}, +[2]={["y"]=95181.545025963,["x"]=-56465.440244849,}, +[3]={["y"]=94071.678958666,["x"]=-57627.596821795,}, +[4]={["y"]=94005.008558864,["x"]=-57558.31189651,}, +}, +}, +}, +}, +} +function ATC_GROUND_NORMANDY:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND:New(self.Airbases,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,2,RepeatScanSeconds) +end +ATC_GROUND_PERSIANGULF={ +ClassName="ATC_GROUND_PERSIANGULF", +Airbases={ +[AIRBASE.PersianGulf.Abu_Musa_Island_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-122813.71002344,["x"]=-31689.936027827,}, +[2]={["y"]=-122827.82488722,["x"]=-31590.105445836,}, +[3]={["y"]=-122769.5689949,["x"]=-31583.176330891,}, +[4]={["y"]=-122726.96776968,["x"]=-31614.998932862,}, +[5]={["y"]=-121293.92414543,["x"]=-31467.947715689,}, +[6]={["y"]=-121296.4904843,["x"]=-31432.018971528,}, +[7]={["y"]=-121236.18152088,["x"]=-31424.576588809,}, +[8]={["y"]=-121190.50068902,["x"]=-31458.452261875,}, +[9]={["y"]=-119839.83654246,["x"]=-31319.356695194,}, +[10]={["y"]=-119824.69514313,["x"]=-31423.293419374,}, +[11]={["y"]=-119886.80054375,["x"]=-31430.22253432,}, +[12]={["y"]=-119932.22474173,["x"]=-31395.320325706,}, +[13]={["y"]=-122813.9472789,["x"]=-31689.81193251,}, +}, +}, +}, +[AIRBASE.PersianGulf.Al_Dhafra_AB]={ +PointsRunways={ +[1]={ +[1]={["y"]=-174672.06004916,["x"]=-209880.97145616,}, +[2]={["y"]=-174705.15693282,["x"]=-209923.15131918,}, +[3]={["y"]=-171819.05380065,["x"]=-212172.84298281,}, +[4]={["y"]=-171785.09826475,["x"]=-212129.87417284,}, +[5]={["y"]=-174671.96413454,["x"]=-209880.52453983,}, +}, +[2]={ +[1]={["y"]=-174351.95872272,["x"]=-211813.88516693,}, +[2]={["y"]=-174381.29169939,["x"]=-211851.81242636,}, +[3]={["y"]=-171493.65648904,["x"]=-214102.92235002,}, +[4]={["y"]=-171464.99693831,["x"]=-214062.78788361,}, +[5]={["y"]=-174351.8628081,["x"]=-211813.4382506,}, +}, +}, +}, +[AIRBASE.PersianGulf.Al_Maktoum_Intl]={ +PointsRunways={ +[1]={ +[1]={["y"]=-111879.49046471,["x"]=-138953.80105841,}, +[2]={["y"]=-111917.23447224,["x"]=-139018.2804046,}, +[3]={["y"]=-108092.98121312,["x"]=-141406.67838426,}, +[4]={["y"]=-108052.34416748,["x"]=-141341.82058294,}, +[5]={["y"]=-111879.5412879,["x"]=-138952.87693763,}, +}, +}, +}, +[AIRBASE.PersianGulf.Al_Minhad_AB]={ +PointsRunways={ +[1]={ +[1]={["y"]=-91070.628933035,["x"]=-125989.64095162,}, +[2]={["y"]=-91072.346560159,["x"]=-126040.59722299,}, +[3]={["y"]=-87098.282779771,["x"]=-126039.41747017,}, +[4]={["y"]=-87099.632735396,["x"]=-125991.26905291,}, +[5]={["y"]=-91071.031270042,["x"]=-125987.44617225,}, +}, +}, +}, +[AIRBASE.PersianGulf.Bandar_Abbas_Intl]={ +PointsRunways={ +[1]={ +[1]={["y"]=12988.484058788,["x"]=113979.99250505,}, +[2]={["y"]=13037.8836239,["x"]=113952.60241152,}, +[3]={["y"]=14877.313199902,["x"]=117414.37833333,}, +[4]={["y"]=14828.777486364,["x"]=117439.06043783,}, +[5]={["y"]=12988.939584604,["x"]=113979.52494386,}, +}, +[2]={ +[1]={["y"]=13203.406014284,["x"]=113848.44907555,}, +[2]={["y"]=13258.268500181,["x"]=113818.47303925,}, +[3]={["y"]=15315.015323566,["x"]=117694.27156647,}, +[4]={["y"]=15264.815746383,["x"]=117725.22168173,}, +[5]={["y"]=13203.861540099,["x"]=113847.98151436,}, +}, +}, +}, +[AIRBASE.PersianGulf.Bandar_Lengeh]={ +PointsRunways={ +[1]={ +[1]={["y"]=-142373.15541415,["x"]=41364.94047809,}, +[2]={["y"]=-142363.30071107,["x"]=41298.112282592,}, +[3]={["y"]=-142217.57151662,["x"]=41320.35666061,}, +[4]={["y"]=-142213.00856728,["x"]=41291.838227254,}, +[5]={["y"]=-142131.44584788,["x"]=41301.534494595,}, +[6]={["y"]=-142132.58658522,["x"]=41323.778872613,}, +[7]={["y"]=-142123.17550221,["x"]=41336.041798956,}, +[8]={["y"]=-139580.45381288,["x"]=41711.022304533,}, +[9]={["y"]=-139590.04241918,["x"]=41778.350996659,}, +[10]={["y"]=-139732.41237808,["x"]=41757.089304408,}, +[11]={["y"]=-139736.7897853,["x"]=41785.646675372,}, +[12]={["y"]=-139816.41690726,["x"]=41775.641173137,}, +[13]={["y"]=-139816.00001133,["x"]=41754.58792885,}, +[14]={["y"]=-139824.1294819,["x"]=41743.748634761,}, +[15]={["y"]=-142373.20183966,["x"]=41365.161507021,}, +}, +}, +}, +[AIRBASE.PersianGulf.Dubai_Intl]={ +PointsRunways={ +[1]={ +[1]={["y"]=-89693.511670714,["x"]=-100490.47082052,}, +[2]={["y"]=-89731.488328846,["x"]=-100555.50584758,}, +[3]={["y"]=-85706.437275049,["x"]=-103076.68123933,}, +[4]={["y"]=-85669.519216262,["x"]=-103010.44994755,}, +[5]={["y"]=-89693.036962487,["x"]=-100489.9961123,}, +}, +[2]={ +[1]={["y"]=-90797.505501889,["x"]=-99344.082465487,}, +[2]={["y"]=-90835.482160021,["x"]=-99409.11749254,}, +[3]={["y"]=-87210.216900398,["x"]=-101681.72494832,}, +[4]={["y"]=-87171.474397253,["x"]=-101619.20256393,}, +[5]={["y"]=-90797.030793662,["x"]=-99343.607757261,}, +}, +}, +}, +[AIRBASE.PersianGulf.Fujairah_Intl]={ +PointsRunways={ +[1]={ +[1]={["y"]=5808.8716147284,["x"]=-116602.15633995,}, +[2]={["y"]=5781.9885293892,["x"]=-116666.67574476,}, +[3]={["y"]=9435.1910907931,["x"]=-118192.91910235,}, +[4]={["y"]=9459.878635843,["x"]=-118134.40047704,}, +[5]={["y"]=5808.4078522575,["x"]=-116603.31550719,}, +}, +}, +}, +[AIRBASE.PersianGulf.Havadarya]={ +PointsRunways={ +[1]={ +[1]={["y"]=-7565.4887830428,["x"]=109074.13162774,}, +[2]={["y"]=-7557.8281079193,["x"]=109030.65729641,}, +[3]={["y"]=-4987.3556518085,["x"]=109524.49147773,}, +[4]={["y"]=-4996.215358578,["x"]=109566.57508489,}, +[5]={["y"]=-7565.4936338604,["x"]=109074.32262205,}, +}, +}, +}, +[AIRBASE.PersianGulf.Kerman_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=70375.468628778,["x"]=456046.12685302,}, +[2]={["y"]=70297.050081575,["x"]=456015.1578105,}, +[3]={["y"]=71814.291673715,["x"]=452165.51037702,}, +[4]={["y"]=71902.918622452,["x"]=452188.46411914,}, +[5]={["y"]=70860.465673482,["x"]=454829.89695989,}, +[6]={["y"]=70862.525255971,["x"]=454892.77675983,}, +[7]={["y"]=70816.157465062,["x"]=454922.77944807,}, +[8]={["y"]=70462.749176371,["x"]=455833.38051827,}, +[9]={["y"]=70483.400377364,["x"]=455901.17880077,}, +[10]={["y"]=70453.787334431,["x"]=455974.8217628,}, +[11]={["y"]=70405.860962315,["x"]=455961.57382254,}, +[12]={["y"]=70374.689338175,["x"]=456046.51649833,}, +}, +}, +}, +[AIRBASE.PersianGulf.Khasab]={ +PointsRunways={ +[1]={ +[1]={["y"]=-534.81827307392,["x"]=-1495.070060483,}, +[2]={["y"]=-434.82912685139,["x"]=-1519.8421462589,}, +[3]={["y"]=-405.55302547993,["x"]=-1413.0969766429,}, +[4]={["y"]=-424.92029254105,["x"]=-1352.0675653224,}, +[5]={["y"]=216.05735069389,["x"]=1206.9187095195,}, +[6]={["y"]=116.42961315781,["x"]=1229.9576238247,}, +[7]={["y"]=88.253643635887,["x"]=1123.7918160128,}, +[8]={["y"]=101.1741158476,["x"]=1042.6886109249,}, +[9]={["y"]=-535.31436058928,["x"]=-1494.8762081291,}, +}, +}, +}, +[AIRBASE.PersianGulf.Lar_Airbase]={ +PointsRunways={ +[1]={ +[1]={["y"]=-183987.5454359,["x"]=169021.72039309,}, +[2]={["y"]=-183988.41292374,["x"]=168955.27082471,}, +[3]={["y"]=-180847.92031188,["x"]=168930.46175795,}, +[4]={["y"]=-180806.58653731,["x"]=168888.39641215,}, +[5]={["y"]=-180740.37934087,["x"]=168886.56748407,}, +[6]={["y"]=-180735.62412787,["x"]=168932.65647164,}, +[7]={["y"]=-180685.14571291,["x"]=168934.11961411,}, +[8]={["y"]=-180682.5852136,["x"]=169001.78995301,}, +[9]={["y"]=-183987.48111493,["x"]=169021.35002828,}, +}, +}, +}, +[AIRBASE.PersianGulf.Qeshm_Island]={ +PointsRunways={ +[1]={ +[1]={["y"]=-35140.372717152,["x"]=63373.658918509,}, +[2]={["y"]=-35098.556715749,["x"]=63320.377239302,}, +[3]={["y"]=-34991.318905699,["x"]=63408.730403557,}, +[4]={["y"]=-34984.574389344,["x"]=63401.311435566,}, +[5]={["y"]=-34991.993357335,["x"]=63313.632722947,}, +[6]={["y"]=-34956.921872287,["x"]=63265.746656824,}, +[7]={["y"]=-34917.129225791,["x"]=63261.699947011,}, +[8]={["y"]=-34832.822771349,["x"]=63337.23853019,}, +[9]={["y"]=-34915.105870884,["x"]=63436.382920614,}, +[10]={["y"]=-34906.337999622,["x"]=63478.198922017,}, +[11]={["y"]=-32728.533668488,["x"]=65307.986209216,}, +[12]={["y"]=-32676.600892552,["x"]=65299.218337954,}, +[13]={["y"]=-32623.99366498,["x"]=65334.964274638,}, +[14]={["y"]=-32626.691471522,["x"]=65388.92040548,}, +[15]={["y"]=-31822.745121968,["x"]=66067.418750826,}, +[16]={["y"]=-31777.556862387,["x"]=66068.767654097,}, +[17]={["y"]=-31691.227053039,["x"]=65974.344425122,}, +[18]={["y"]=-31606.246146962,["x"]=66042.464040311,}, +[19]={["y"]=-31602.199437148,["x"]=66084.280041714,}, +[20]={["y"]=-31632.549760747,["x"]=66124.747139846,}, +[21]={["y"]=-31727.647441358,["x"]=66134.189462744,}, +[22]={["y"]=-31734.391957713,["x"]=66141.608430735,}, +[23]={["y"]=-31632.549760747,["x"]=66225.914885176,}, +[24]={["y"]=-31673.691310515,["x"]=66277.173209477,}, +[25]={["y"]=-35140.880825624,["x"]=63373.905965825,}, +}, +}, +}, +[AIRBASE.PersianGulf.Sharjah_Intl]={ +PointsRunways={ +[1]={ +[1]={["y"]=-71668.808658476,["x"]=-93980.156242153,}, +[2]={["y"]=-75307.847363315,["x"]=-91617.097584505,}, +[3]={["y"]=-75280.458023829,["x"]=-91574.709321014,}, +[4]={["y"]=-72249.697184234,["x"]=-93529.134331507,}, +[5]={["y"]=-72179.919581256,["x"]=-93526.199759419,}, +[6]={["y"]=-72138.183444896,["x"]=-93597.933743788,}, +[7]={["y"]=-71638.654062835,["x"]=-93927.584008321,}, +[8]={["y"]=-71668.325847279,["x"]=-93979.428115206,}, +}, +[2]={ +[1]={["y"]=-71553.225408723,["x"]=-93775.312323319,}, +[2]={["y"]=-75168.13829548,["x"]=-91426.51571111,}, +[3]={["y"]=-75125.388157445,["x"]=-91363.754870166,}, +[4]={["y"]=-71510.511081666,["x"]=-93703.252275385,}, +[5]={["y"]=-71552.247218027,["x"]=-93775.638386885,}, +}, +}, +}, +[AIRBASE.PersianGulf.Shiraz_International_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-353995.75579778,["x"]=382327.42294273,}, +[2]={["y"]=-354029.77009807,["x"]=382265.46199492,}, +[3]={["y"]=-349407.98049238,["x"]=379941.14030526,}, +[4]={["y"]=-349376.87025024,["x"]=380004.69408564,}, +[5]={["y"]=-353995.71101815,["x"]=382327.59771695,}, +}, +[2]={ +[1]={["y"]=-354056.29510012,["x"]=381845.97598829,}, +[2]={["y"]=-354091.48797289,["x"]=381783.6025623,}, +[3]={["y"]=-349650.64038107,["x"]=379550.92898242,}, +[4]={["y"]=-349624.41889127,["x"]=379614.92719482,}, +[5]={["y"]=-354056.25032049,["x"]=381846.15076251,}, +}, +}, +}, +[AIRBASE.PersianGulf.Sir_Abu_Nuayr]={ +PointsRunways={ +[1]={ +[1]={["y"]=-203367.3128691,["x"]=-103017.22553918,}, +[2]={["y"]=-203373.59664477,["x"]=-103054.92819323,}, +[3]={["y"]=-202578.27577922,["x"]=-103188.26018333,}, +[4]={["y"]=-202571.37254488,["x"]=-103151.01482599,}, +[5]={["y"]=-203367.65259839,["x"]=-103016.48202662,}, +[6]={["y"]=-203291.39594004,["x"]=-102985.49774228,}, +}, +}, +}, +[AIRBASE.PersianGulf.Sirri_Island]={ +PointsRunways={ +[1]={ +[1]={["y"]=-169713.12842428,["x"]=-27766.658020853,}, +[2]={["y"]=-169682.02009414,["x"]=-27726.583172021,}, +[3]={["y"]=-169727.21866794,["x"]=-27691.632048154,}, +[4]={["y"]=-169694.28043602,["x"]=-27650.276268081,}, +[5]={["y"]=-169763.08474269,["x"]=-27598.490047901,}, +[6]={["y"]=-169825.30140298,["x"]=-27607.090586235,}, +[7]={["y"]=-171614.98889813,["x"]=-26246.247907014,}, +[8]={["y"]=-171620.85326172,["x"]=-26187.105176343,}, +[9]={["y"]=-171686.10990337,["x"]=-26138.56820961,}, +[10]={["y"]=-171716.55468456,["x"]=-26178.745338885,}, +[11]={["y"]=-171764.9668776,["x"]=-26142.810515186,}, +[12]={["y"]=-171796.29599657,["x"]=-26183.416460911,}, +[13]={["y"]=-169713.5628285,["x"]=-27766.883787223,}, +}, +}, +}, +[AIRBASE.PersianGulf.Tunb_Island_AFB]={ +PointsRunways={ +[1]={ +[1]={["y"]=-92923.634698863,["x"]=9547.6862547173,}, +[2]={["y"]=-92963.030803298,["x"]=9565.7274614215,}, +[3]={["y"]=-92934.128053782,["x"]=9619.2987996964,}, +[4]={["y"]=-92970.946842975,["x"]=9640.1014155901,}, +[5]={["y"]=-92949.591945243,["x"]=9682.8112110532,}, +[6]={["y"]=-92899.518391942,["x"]=9699.7478540817,}, +[7]={["y"]=-91969.13471408,["x"]=11464.627292768,}, +[8]={["y"]=-91983.666755417,["x"]=11515.293058512,}, +[9]={["y"]=-91960.101282978,["x"]=11557.710908902,}, +[10]={["y"]=-91921.021874517,["x"]=11539.251288825,}, +[11]={["y"]=-91893.725202275,["x"]=11589.720675632,}, +[12]={["y"]=-91859.751646175,["x"]=11571.850192366,}, +[13]={["y"]=-92922.149728329,["x"]=9547.2937058617,}, +}, +}, +}, +[AIRBASE.PersianGulf.Tunb_Kochak]={ +PointsRunways={ +[1]={ +[1]={["y"]=-109925.50271188,["x"]=8974.5666013181,}, +[2]={["y"]=-109905.7382908,["x"]=8937.53274444,}, +[3]={["y"]=-109009.93726324,["x"]=9072.2234968343,}, +[4]={["y"]=-109040.82867587,["x"]=9104.9871291834,}, +[5]={["y"]=-109925.26515172,["x"]=8974.091480998,}, +}, +}, +}, +[AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-176230.75865538,["x"]=-188732.01369812,}, +[2]={["y"]=-176274.78045186,["x"]=-188744.8049371,}, +[3]={["y"]=-175692.03171595,["x"]=-190564.17145168,}, +[4]={["y"]=-175649.7486572,["x"]=-190550.58435053,}, +[5]={["y"]=-176230.66274076,["x"]=-188731.5667818,}, +}, +}, +}, +[AIRBASE.PersianGulf.Bandar_e_Jask_airfield]={ +PointsRunways={ +[1]={ +[1]={["y"]=155156.73167657,["x"]=-57837.031277333,}, +[2]={["y"]=155130.38996239,["x"]=-57790.475605714,}, +[3]={["y"]=157137.17872571,["x"]=-56710.411783359,}, +[4]={["y"]=157148.46631801,["x"]=-56688.071756941,}, +[5]={["y"]=157220.07198163,["x"]=-56649.035500253,}, +[6]={["y"]=157227.83220133,["x"]=-56662.204357931,}, +[7]={["y"]=157359.6383572,["x"]=-56590.481115222,}, +[8]={["y"]=157383.03659539,["x"]=-56633.044744502,}, +[9]={["y"]=155156.7940421,["x"]=-57837.149989814,}, +}, +}, +}, +[AIRBASE.PersianGulf.Abu_Dhabi_International_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-163964.56943899,["x"]=-189427.63621921,}, +[2]={["y"]=-164005.96838287,["x"]=-189478.90226888,}, +[3]={["y"]=-160798.22080495,["x"]=-192054.59531727,}, +[4]={["y"]=-160755.05282258,["x"]=-192002.58569997,}, +[5]={["y"]=-163964.47352437,["x"]=-189427.18930288,}, +}, +[2]={ +[1]={["y"]=-163615.44952024,["x"]=-187144.00786922,}, +[2]={["y"]=-163656.84846411,["x"]=-187195.27391888,}, +[3]={["y"]=-160452.71811093,["x"]=-189764.86593382,}, +[4]={["y"]=-160411.94568221,["x"]=-189715.47961171,}, +[5]={["y"]=-163615.35360562,["x"]=-187143.56095289,}, +}, +}, +}, +[AIRBASE.PersianGulf.Al_Bateen_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-183207.51774197,["x"]=-189871.8319832,}, +[2]={["y"]=-183240.61462564,["x"]=-189914.01184622,}, +[3]={["y"]=-180748.88998479,["x"]=-191943.30402837,}, +[4]={["y"]=-180711.83076051,["x"]=-191896.52435182,}, +[5]={["y"]=-183207.42182735,["x"]=-189871.38506688,}, +}, +}, +}, +[AIRBASE.PersianGulf.Kish_International_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-227330.79164594,["x"]=42691.91536494,}, +[2]={["y"]=-227321.58531968,["x"]=42758.113234714,}, +[3]={["y"]=-223235.73004619,["x"]=42313.579195302,}, +[4]={["y"]=-223240.99080406,["x"]=42247.819722016,}, +[5]={["y"]=-227330.67774245,["x"]=42691.785682556,}, +}, +[2]={ +[1]={["y"]=-227283.77911886,["x"]=42987.748941936,}, +[2]={["y"]=-227274.5727926,["x"]=43053.946811711,}, +[3]={["y"]=-222907.94761294,["x"]=42580.826755904,}, +[4]={["y"]=-222915.76510871,["x"]=42514.58376547,}, +[5]={["y"]=-227283.66521537,["x"]=42987.619259553,}, +}, +}, +}, +[AIRBASE.PersianGulf.Al_Ain_International_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-65165.315648901,["x"]=-209042.45716363,}, +[2]={["y"]=-65112.933878375,["x"]=-209048.84518442,}, +[3]={["y"]=-65672.013626755,["x"]=-213019.66479976,}, +[4]={["y"]=-65722.555424932,["x"]=-213013.91596964,}, +[5]={["y"]=-65165.400582791,["x"]=-209042.15059908,}, +}, +}, +}, +[AIRBASE.PersianGulf.Lavan_Island_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=-288099.83301495,["x"]=76353.443273049,}, +[2]={["y"]=-288119.51457685,["x"]=76302.756224611,}, +[3]={["y"]=-288070.96603401,["x"]=76283.898526152,}, +[4]={["y"]=-288085.61084238,["x"]=76247.386812114,}, +[5]={["y"]=-288032.04695421,["x"]=76224.316223573,}, +[6]={["y"]=-287991.12173627,["x"]=76245.38067398,}, +[7]={["y"]=-287489.96435675,["x"]=76037.610404141,}, +[8]={["y"]=-287497.65444594,["x"]=76017.686082159,}, +[9]={["y"]=-287453.61120787,["x"]=75998.111309685,}, +[10]={["y"]=-287419.70490555,["x"]=76007.199596905,}, +[11]={["y"]=-285642.24565503,["x"]=75279.787069797,}, +[12]={["y"]=-285625.46727862,["x"]=75239.239326815,}, +[13]={["y"]=-285570.23845628,["x"]=75217.217707782,}, +[14]={["y"]=-285555.20782742,["x"]=75252.172658628,}, +[15]={["y"]=-285505.92134673,["x"]=75231.199688121,}, +[16]={["y"]=-285484.28380792,["x"]=75284.258832895,}, +[17]={["y"]=-288099.97979219,["x"]=76354.32393647,}, +}, +}, +}, +[AIRBASE.PersianGulf.Jiroft_Airport]={ +PointsRunways={ +[1]={ +[1]={["y"]=140376.87310595,["x"]=283748.07558774,}, +[2]={["y"]=140299.43760975,["x"]=283655.81201779,}, +[3]={["y"]=143008.43807723,["x"]=281517.41347718,}, +[4]={["y"]=143052.6952428,["x"]=281573.25195709,}, +[5]={["y"]=142946.60213095,["x"]=281656.5960586,}, +[6]={["y"]=142975.14179847,["x"]=281687.20381796,}, +[7]={["y"]=142932.12548801,["x"]=281724.01585287,}, +[8]={["y"]=142870.49635092,["x"]=281719.05243244,}, +[9]={["y"]=140437.35783025,["x"]=283640.84253664,}, +[10]={["y"]=140433.27045062,["x"]=283705.80267729,}, +[11]={["y"]=140376.77702493,["x"]=283747.8442964,}, +}, +}, +}, +}, +} +function ATC_GROUND_PERSIANGULF:New(AirbaseNames) +local self=BASE:Inherit(self,ATC_GROUND:New(self.Airbases,AirbaseNames)) +self:SetKickSpeedKmph(50) +self:SetMaximumKickSpeedKmph(150) +return self +end +function ATC_GROUND_PERSIANGULF:Start(RepeatScanSeconds) +RepeatScanSeconds=RepeatScanSeconds or 0.05 +self.AirbaseMonitor=SCHEDULER:New(self,self._AirbaseMonitor,{self},0,2,RepeatScanSeconds) +end +do +DETECTION_BASE={ +ClassName="DETECTION_BASE", +DetectionSetGroup=nil, +DetectionRange=nil, +DetectedObjects={}, +DetectionRun=0, +DetectedObjectsIdentified={}, +DetectedItems={}, +DetectedItemsByIndex={}, +} +function DETECTION_BASE:New(DetectionSet) +local self=BASE:Inherit(self,FSM:New()) +self.DetectedItemCount=0 +self.DetectedItemMax=0 +self.DetectedItems={} +self.DetectionSet=DetectionSet +self.RefreshTimeInterval=30 +self:InitDetectVisual(nil) +self:InitDetectOptical(nil) +self:InitDetectRadar(nil) +self:InitDetectRWR(nil) +self:InitDetectIRST(nil) +self:InitDetectDLINK(nil) +self:FilterCategories({ +Unit.Category.AIRPLANE, +Unit.Category.GROUND_UNIT, +Unit.Category.HELICOPTER, +Unit.Category.SHIP, +Unit.Category.STRUCTURE +}) +self:SetFriendliesRange(6000) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Detecting") +self:AddTransition("Detecting","Detect","Detecting") +self:AddTransition("Detecting","Detection","Detecting") +self:AddTransition("Detecting","Detected","Detecting") +self:AddTransition("Detecting","DetectedItem","Detecting") +self:AddTransition("*","Stop","Stopped") +return self +end +do +function DETECTION_BASE:onafterStart(From,Event,To) +self:__Detect(1) +end +function DETECTION_BASE:onafterDetect(From,Event,To) +local DetectDelay=0.1 +self.DetectionCount=0 +self.DetectionRun=0 +self:UnIdentifyAllDetectedObjects() +local DetectionTimeStamp=timer.getTime() +for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do +self.DetectedObjects[DetectionObjectName].IsDetected=false +self.DetectedObjects[DetectionObjectName].IsVisible=false +self.DetectedObjects[DetectionObjectName].KnowDistance=nil +self.DetectedObjects[DetectionObjectName].LastTime=nil +self.DetectedObjects[DetectionObjectName].LastPos=nil +self.DetectedObjects[DetectionObjectName].LastVelocity=nil +self.DetectedObjects[DetectionObjectName].Distance=10000000 +end +self.DetectionCount=self:CountAliveRecce() +local DetectionInterval=self.DetectionCount/(self.RefreshTimeInterval-1) +self:ForEachAliveRecce( +function(DetectionGroup) +self:__Detection(DetectDelay,DetectionGroup,DetectionTimeStamp) +DetectDelay=DetectDelay+DetectionInterval +end +) +self:__Detect(-self.RefreshTimeInterval) +end +function DETECTION_BASE:CountAliveRecce() +return self.DetectionSet:CountAlive() +end +function DETECTION_BASE:ForEachAliveRecce(IteratorFunction,...) +self:F2(arg) +self.DetectionSet:ForEachGroupAlive(IteratorFunction,arg) +return self +end +function DETECTION_BASE:onafterDetection(From,Event,To,Detection,DetectionTimeStamp) +self.DetectionRun=self.DetectionRun+1 +local HasDetectedObjects=false +if Detection and Detection:IsAlive()then +local DetectionGroupName=Detection:GetName() +local DetectionUnit=Detection:GetUnit(1) +local DetectedUnits={} +local DetectedTargets=Detection:GetDetectedTargets( +self.DetectVisual, +self.DetectOptical, +self.DetectRadar, +self.DetectIRST, +self.DetectRWR, +self.DetectDLINK +) +self:F({DetectedTargets=DetectedTargets}) +for DetectionObjectID,Detection in pairs(DetectedTargets)do +local DetectedObject=Detection.object +if DetectedObject and DetectedObject:isExist()and DetectedObject.id_<50000000 then +local DetectedObjectName=DetectedObject:getName() +if not self.DetectedObjects[DetectedObjectName]then +self.DetectedObjects[DetectedObjectName]=self.DetectedObjects[DetectedObjectName]or{} +self.DetectedObjects[DetectedObjectName].Name=DetectedObjectName +self.DetectedObjects[DetectedObjectName].Object=DetectedObject +end +end +end +for DetectionObjectName,DetectedObjectData in pairs(self.DetectedObjects)do +local DetectedObject=DetectedObjectData.Object +if DetectedObject:isExist()then +local TargetIsDetected,TargetIsVisible,TargetLastTime,TargetKnowType,TargetKnowDistance,TargetLastPos,TargetLastVelocity=DetectionUnit:IsTargetDetected( +DetectedObject, +self.DetectVisual, +self.DetectOptical, +self.DetectRadar, +self.DetectIRST, +self.DetectRWR, +self.DetectDLINK +) +local DetectionAccepted=true +local DetectedObjectName=DetectedObject:getName() +local DetectedObjectType=DetectedObject:getTypeName() +local DetectedObjectVec3=DetectedObject:getPoint() +local DetectedObjectVec2={x=DetectedObjectVec3.x,y=DetectedObjectVec3.z} +local DetectionGroupVec3=Detection:GetVec3() +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:IsPointVec2InZone(DetectedObjectVec2)==true then +DetectionAccepted=false +end +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:IsPointVec2InZone(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 +end +do +function DETECTION_BASE:SetRefreshTimeInterval(RefreshTimeInterval) +self:F2() +self.RefreshTimeInterval=RefreshTimeInterval +return self +end +end +do +function DETECTION_BASE:SetFriendliesRange(FriendliesRange) +self:F2() +self.FriendliesRange=FriendliesRange +return self +end +end +do +function DETECTION_BASE:SetIntercept(Intercept,InterceptDelay) +self:F2() +self.Intercept=Intercept +self.InterceptDelay=InterceptDelay +return self +end +end +do +function DETECTION_BASE:SetAcceptRange(AcceptRange) +self:F2() +self.AcceptRange=AcceptRange +return self +end +function DETECTION_BASE:SetAcceptZones(AcceptZones) +self:F2() +if type(AcceptZones)=="table"then +if AcceptZones.ClassName and AcceptZones:IsInstanceOf(ZONE_BASE)then +self.AcceptZones={AcceptZones} +else +self.AcceptZones=AcceptZones +end +else +self:F({"AcceptZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",AcceptZones}) +error() +end +return self +end +function DETECTION_BASE:SetRejectZones(RejectZones) +self:F2() +if type(RejectZones)=="table"then +if RejectZones.ClassName and RejectZones:IsInstanceOf(ZONE_BASE)then +self.RejectZones={RejectZones} +else +self.RejectZones=RejectZones +end +else +self:F({"RejectZones must be a list of ZONE_BASE derived objects or one ZONE_BASE derived object",RejectZones}) +error() +end +return self +end +end +do +function DETECTION_BASE:SetDistanceProbability(DistanceProbability) +self:F2() +self.DistanceProbability=DistanceProbability +return self +end +function DETECTION_BASE:SetAlphaAngleProbability(AlphaAngleProbability) +self:F2() +self.AlphaAngleProbability=AlphaAngleProbability +return self +end +function DETECTION_BASE:SetZoneProbability(ZoneArray) +self:F2() +self.ZoneProbability=ZoneArray +return self +end +end +do +function DETECTION_BASE:AcceptChanges(DetectedItem) +DetectedItem.Changed=false +DetectedItem.Changes={} +return self +end +function DETECTION_BASE:AddChangeItem(DetectedItem,ChangeCode,ItemUnitType) +DetectedItem.Changed=true +local ID=DetectedItem.ID +DetectedItem.Changes=DetectedItem.Changes or{} +DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{} +DetectedItem.Changes[ChangeCode].ID=ID +DetectedItem.Changes[ChangeCode].ItemUnitType=ItemUnitType +self:F({"Change on Detected Item:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ItemUnitType=ItemUnitType}) +return self +end +function DETECTION_BASE:AddChangeUnit(DetectedItem,ChangeCode,ChangeUnitType) +DetectedItem.Changed=true +local ID=DetectedItem.ID +DetectedItem.Changes=DetectedItem.Changes or{} +DetectedItem.Changes[ChangeCode]=DetectedItem.Changes[ChangeCode]or{} +DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]or 0 +DetectedItem.Changes[ChangeCode][ChangeUnitType]=DetectedItem.Changes[ChangeCode][ChangeUnitType]+1 +DetectedItem.Changes[ChangeCode].ID=ID +self:F({"Change on Detected Unit:",DetectedItemID=DetectedItem.ID,ChangeCode=ChangeCode,ChangeUnitType=ChangeUnitType}) +return self +end +end +do +function DETECTION_BASE:SetFriendlyPrefixes(FriendlyPrefixes) +self.FriendlyPrefixes=self.FriendlyPrefixes or{} +if type(FriendlyPrefixes)~="table"then +FriendlyPrefixes={FriendlyPrefixes} +end +for PrefixID,Prefix in pairs(FriendlyPrefixes)do +self:F({FriendlyPrefix=Prefix}) +self.FriendlyPrefixes[Prefix]=Prefix +end +return self +end +function DETECTION_BASE:IsFriendliesNearBy(DetectedItem,Category) +return(DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category]~=nil)or false +end +function DETECTION_BASE:GetFriendliesNearBy(DetectedItem,Category) +return DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] +end +function DETECTION_BASE:IsFriendliesNearIntercept(DetectedItem) +return DetectedItem.FriendliesNearIntercept~=nil or false +end +function DETECTION_BASE:GetFriendliesNearIntercept(DetectedItem) +return DetectedItem.FriendliesNearIntercept +end +function DETECTION_BASE:GetFriendliesDistance(DetectedItem) +return DetectedItem.FriendliesDistance +end +function DETECTION_BASE:IsPlayersNearBy(DetectedItem) +return DetectedItem.PlayersNearBy~=nil +end +function DETECTION_BASE:GetPlayersNearBy(DetectedItem) +return DetectedItem.PlayersNearBy +end +function DETECTION_BASE:ReportFriendliesNearBy(TargetData) +local DetectedItem=TargetData.DetectedItem +local DetectedSet=TargetData.DetectedItem.Set +local DetectedUnit=DetectedSet:GetFirst() +DetectedItem.FriendliesNearBy=nil +if DetectedUnit and DetectedUnit:IsAlive()then +local DetectedUnitCoord=DetectedUnit:GetCoordinate() +local InterceptCoord=TargetData.InterceptCoord or DetectedUnitCoord +local SphereSearch={ +id=world.VolumeType.SPHERE, +params={ +point=InterceptCoord:GetVec3(), +radius=self.FriendliesRange, +} +} +local FindNearByFriendlies=function(FoundDCSUnit,ReportGroupData) +local DetectedItem=ReportGroupData.DetectedItem +local DetectedSet=ReportGroupData.DetectedItem.Set +local DetectedUnit=DetectedSet:GetFirst() +local DetectedUnitCoord=DetectedUnit:GetCoordinate() +local InterceptCoord=ReportGroupData.InterceptCoord or DetectedUnitCoord +local ReportSetGroup=ReportGroupData.ReportSetGroup +local EnemyCoalition=DetectedUnit:GetCoalition() +local FoundUnitCoalition=FoundDCSUnit:getCoalition() +local FoundUnitCategory=FoundDCSUnit:getDesc().category +local FoundUnitName=FoundDCSUnit:getName() +local FoundUnitGroupName=FoundDCSUnit:getGroup():getName() +local EnemyUnitName=DetectedUnit:GetName() +local FoundUnitInReportSetGroup=ReportSetGroup:FindGroup(FoundUnitGroupName)~=nil +if FoundUnitInReportSetGroup==true then +for PrefixID,Prefix in pairs(self.FriendlyPrefixes or{})do +if string.find(FoundUnitName,Prefix:gsub("-","%%-"),1)then +FoundUnitInReportSetGroup=false +break +end +end +end +if FoundUnitCoalition~=EnemyCoalition and FoundUnitInReportSetGroup==false then +local FriendlyUnit=UNIT:Find(FoundDCSUnit) +local FriendlyUnitName=FriendlyUnit:GetName() +local FriendlyUnitCategory=FriendlyUnit:GetDesc().category +DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{} +DetectedItem.FriendliesNearBy[FoundUnitCategory]=DetectedItem.FriendliesNearBy[FoundUnitCategory]or{} +DetectedItem.FriendliesNearBy[FoundUnitCategory][FriendlyUnitName]=FriendlyUnit +local Distance=DetectedUnitCoord:Get2DDistance(FriendlyUnit:GetCoordinate()) +DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{} +DetectedItem.FriendliesDistance[Distance]=FriendlyUnit +return true +end +return true +end +world.searchObjects(Object.Category.UNIT,SphereSearch,FindNearByFriendlies,TargetData) +DetectedItem.PlayersNearBy=nil +_DATABASE:ForEachPlayer( +function(PlayerUnitName) +local PlayerUnit=UNIT:FindByName(PlayerUnitName) +if PlayerUnit and PlayerUnit:IsAlive()then +local coord=PlayerUnit:GetCoordinate() +if coord and coord:IsInRadius(DetectedUnitCoord,self.FriendliesRange)then +local PlayerUnitCategory=PlayerUnit:GetDesc().category +if(not self.FriendliesCategory)or(self.FriendliesCategory and(self.FriendliesCategory==PlayerUnitCategory))then +local PlayerUnitName=PlayerUnit:GetName() +DetectedItem.PlayersNearBy=DetectedItem.PlayersNearBy or{} +DetectedItem.PlayersNearBy[PlayerUnitName]=PlayerUnit +DetectedItem.FriendliesNearBy=DetectedItem.FriendliesNearBy or{} +DetectedItem.FriendliesNearBy[PlayerUnitCategory]=DetectedItem.FriendliesNearBy[PlayerUnitCategory]or{} +DetectedItem.FriendliesNearBy[PlayerUnitCategory][PlayerUnitName]=PlayerUnit +local Distance=DetectedUnitCoord:Get2DDistance(PlayerUnit:GetCoordinate()) +DetectedItem.FriendliesDistance=DetectedItem.FriendliesDistance or{} +DetectedItem.FriendliesDistance[Distance]=PlayerUnit +end +end +end +end +) +end +self:F({Friendlies=DetectedItem.FriendliesNearBy,Players=DetectedItem.PlayersNearBy}) +end +end +function DETECTION_BASE:IsDetectedObjectIdentified(DetectedObject) +local DetectedObjectName=DetectedObject.Name +if DetectedObjectName then +local DetectedObjectIdentified=self.DetectedObjectsIdentified[DetectedObjectName]==true +return DetectedObjectIdentified +else +return nil +end +end +function DETECTION_BASE:IdentifyDetectedObject(DetectedObject) +local DetectedObjectName=DetectedObject.Name +self.DetectedObjectsIdentified[DetectedObjectName]=true +end +function DETECTION_BASE:UnIdentifyDetectedObject(DetectedObject) +local DetectedObjectName=DetectedObject.Name +self.DetectedObjectsIdentified[DetectedObjectName]=false +end +function DETECTION_BASE:UnIdentifyAllDetectedObjects() +self.DetectedObjectsIdentified={} +end +function DETECTION_BASE:GetDetectedObject(ObjectName) +self:F2({ObjectName=ObjectName}) +if ObjectName then +local DetectedObject=self.DetectedObjects[ObjectName] +if DetectedObject then +local DetectedUnit=UNIT:FindByName(ObjectName) +if DetectedUnit and DetectedUnit:IsAlive()then +if self:IsDetectedObjectIdentified(DetectedObject)==false then +return DetectedObject +end +end +end +end +return nil +end +function DETECTION_BASE:GetDetectedUnitTypeName(DetectedUnit) +if DetectedUnit and DetectedUnit:IsAlive()then +local DetectedUnitName=DetectedUnit:GetName() +local DetectedObject=self.DetectedObjects[DetectedUnitName] +if DetectedObject then +if DetectedObject.KnowType then +return DetectedUnit:GetTypeName() +else +return"Unknown" +end +else +return"Unknown" +end +else +return"Dead:"..DetectedUnit:GetName() +end +return"Undetected:"..DetectedUnit:GetName() +end +function DETECTION_BASE:AddDetectedItem(ItemPrefix,DetectedItemKey,Set) +local DetectedItem={} +self.DetectedItemCount=self.DetectedItemCount+1 +self.DetectedItemMax=self.DetectedItemMax+1 +DetectedItemKey=DetectedItemKey or self.DetectedItemMax +self.DetectedItems[DetectedItemKey]=DetectedItem +self.DetectedItemsByIndex[DetectedItemKey]=DetectedItem +DetectedItem.Index=DetectedItemKey +DetectedItem.Set=Set or SET_UNIT:New():FilterDeads():FilterCrashes() +DetectedItem.ItemID=ItemPrefix.."."..self.DetectedItemMax +DetectedItem.ID=self.DetectedItemMax +DetectedItem.Removed=false +if self.Locking then +self:LockDetectedItem(DetectedItem) +end +return DetectedItem +end +function DETECTION_BASE:AddDetectedItemZone(ItemPrefix,DetectedItemKey,Set,Zone) +self:F({ItemPrefix,DetectedItemKey,Set,Zone}) +local DetectedItem=self:AddDetectedItem(ItemPrefix,DetectedItemKey,Set) +DetectedItem.Zone=Zone +return DetectedItem +end +function DETECTION_BASE:RemoveDetectedItem(DetectedItemKey) +local DetectedItem=self.DetectedItems[DetectedItemKey] +if DetectedItem then +self.DetectedItemCount=self.DetectedItemCount-1 +local DetectedItemIndex=DetectedItem.Index +self.DetectedItemsByIndex[DetectedItemIndex]=nil +self.DetectedItems[DetectedItemKey]=nil +end +end +function DETECTION_BASE:GetDetectedItems() +return self.DetectedItems +end +function DETECTION_BASE:GetDetectedItemsByIndex() +return self.DetectedItemsByIndex +end +function DETECTION_BASE:GetDetectedItemsCount() +local DetectedCount=self.DetectedItemCount +return DetectedCount +end +function DETECTION_BASE:GetDetectedItemByKey(Key) +self:F({DetectedItems=self.DetectedItems}) +local DetectedItem=self.DetectedItems[Key] +if DetectedItem then +return DetectedItem +end +return nil +end +function DETECTION_BASE:GetDetectedItemByIndex(Index) +self:F({self.DetectedItemsByIndex}) +local DetectedItem=self.DetectedItemsByIndex[Index] +if DetectedItem then +return DetectedItem +end +return nil +end +function DETECTION_BASE:GetDetectedItemID(DetectedItem) +return DetectedItem and DetectedItem.ItemID or"" +end +function DETECTION_BASE:GetDetectedID(Index) +local DetectedItem=self.DetectedItemsByIndex[Index] +if DetectedItem then +return DetectedItem.ID +end +return"" +end +function DETECTION_BASE:GetDetectedItemSet(DetectedItem) +local DetectedSetUnit=DetectedItem and DetectedItem.Set +if DetectedSetUnit then +return DetectedSetUnit +end +return nil +end +function DETECTION_BASE:UpdateDetectedItemDetection(DetectedItem) +local IsDetected=false +for UnitName,UnitData in pairs(DetectedItem.Set:GetSet())do +local DetectedObject=self.DetectedObjects[UnitName] +self:F({UnitName=UnitName,IsDetected=DetectedObject.IsDetected}) +if DetectedObject.IsDetected then +IsDetected=true +break +end +end +self:F({IsDetected=DetectedItem.IsDetected}) +DetectedItem.IsDetected=IsDetected +return IsDetected +end +function DETECTION_BASE:IsDetectedItemDetected(DetectedItem) +return DetectedItem.IsDetected +end +do +function DETECTION_BASE:GetDetectedItemZone(DetectedItem) +local DetectedZone=DetectedItem and DetectedItem.Zone +if DetectedZone then +return DetectedZone +end +local Detected +return nil +end +end +function DETECTION_BASE:LockDetectedItems() +for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do +self:LockDetectedItem(DetectedItem) +end +self.Locking=true +return self +end +function DETECTION_BASE:UnlockDetectedItems() +for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do +self:UnlockDetectedItem(DetectedItem) +end +self.Locking=nil +return self +end +function DETECTION_BASE:IsDetectedItemLocked(DetectedItem) +return self.Locking and DetectedItem.Locked==true +end +function DETECTION_BASE:LockDetectedItem(DetectedItem) +DetectedItem.Locked=true +return self +end +function DETECTION_BASE:UnlockDetectedItem(DetectedItem) +DetectedItem.Locked=nil +return self +end +function DETECTION_BASE:SetDetectedItemCoordinate(DetectedItem,Coordinate,DetectedItemUnit) +self:F({Coordinate=Coordinate}) +if DetectedItem then +if DetectedItemUnit then +DetectedItem.Coordinate=Coordinate +DetectedItem.Coordinate:SetHeading(DetectedItemUnit:GetHeading()) +DetectedItem.Coordinate.y=DetectedItemUnit:GetAltitude() +DetectedItem.Coordinate:SetVelocity(DetectedItemUnit:GetVelocityMPS()) +end +end +end +function DETECTION_BASE:GetDetectedItemCoordinate(DetectedItem) +self:F({DetectedItem=DetectedItem}) +if DetectedItem then +return DetectedItem.Coordinate +end +return nil +end +function DETECTION_BASE:GetDetectedItemCoordinates() +local Coordinates={} +for DetectedItemID,DetectedItem in pairs(self:GetDetectedItems())do +Coordinates[DetectedItem]=self:GetDetectedItemCoordinate(DetectedItem) +end +return Coordinates +end +function DETECTION_BASE:SetDetectedItemThreatLevel(DetectedItem) +local DetectedSet=DetectedItem.Set +if DetectedItem then +DetectedItem.ThreatLevel,DetectedItem.ThreatText=DetectedSet:CalculateThreatLevelA2G() +end +end +function DETECTION_BASE:GetDetectedItemThreatLevel(DetectedItem) +self:F({DetectedItem=DetectedItem}) +if DetectedItem then +self:F({ThreatLevel=DetectedItem.ThreatLevel,ThreatText=DetectedItem.ThreatText}) +return DetectedItem.ThreatLevel or 0,DetectedItem.ThreatText or"" +end +return nil,"" +end +function DETECTION_BASE:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings) +self:F() +return nil +end +function DETECTION_BASE:DetectedReportDetailed(AttackGroup) +self:F() +return nil +end +function DETECTION_BASE:GetDetectionSet() +local DetectionSet=self.DetectionSet +return DetectionSet +end +function DETECTION_BASE:NearestRecce(DetectedItem) +local NearestRecce=nil +local DistanceRecce=1000000000 +for RecceGroupName,RecceGroup in pairs(self.DetectionSet:GetSet())do +if RecceGroup and RecceGroup:IsAlive()then +for RecceUnit,RecceUnit in pairs(RecceGroup:GetUnits())do +if RecceUnit:IsActive()then +local RecceUnitCoord=RecceUnit:GetCoordinate() +local Distance=RecceUnitCoord:Get2DDistance(self:GetDetectedItemCoordinate(DetectedItem)) +if Distance0 then +DetectedItemCoordText=DetectedItemCoordinate:ToStringA2A(AttackGroup,Settings) +else +DetectedItemCoordText=DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings) +end +local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem) +local DetectedItemsCount=DetectedSet:Count() +local DetectedItemsTypes=DetectedSet:GetTypeNames() +local Report=REPORT:New() +Report:Add(DetectedItemID..", "..DetectedItemCoordText) +Report:Add(string.format("Threat: [%s%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G))) +Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes)) +return Report +end +return nil +end +function DETECTION_AREAS:DetectedReportDetailed(AttackGroup) +self:F() +local Report=REPORT:New() +for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do +local DetectedItem=DetectedItem +local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup) +Report:SetTitle("Detected areas:") +Report:Add(ReportSummary:Text()) +end +local ReportText=Report:Text() +return ReportText +end +function DETECTION_AREAS:CalculateIntercept(DetectedItem) +local DetectedCoord=DetectedItem.Coordinate +local DetectedSpeed=DetectedCoord:GetVelocity() +local DetectedHeading=DetectedCoord:GetHeading() +if self.Intercept then +local DetectedSet=DetectedItem.Set +local TranslateDistance=DetectedSpeed*self.InterceptDelay +local InterceptCoord=DetectedCoord:Translate(TranslateDistance,DetectedHeading) +DetectedItem.InterceptCoord=InterceptCoord +else +DetectedItem.InterceptCoord=DetectedCoord +end +end +function DETECTION_AREAS:SmokeDetectedUnits() +self:F2() +self._SmokeDetectedUnits=true +return self +end +function DETECTION_AREAS:FlareDetectedUnits() +self:F2() +self._FlareDetectedUnits=true +return self +end +function DETECTION_AREAS:SmokeDetectedZones() +self:F2() +self._SmokeDetectedZones=true +return self +end +function DETECTION_AREAS:FlareDetectedZones() +self:F2() +self._FlareDetectedZones=true +return self +end +function DETECTION_AREAS:BoundDetectedZones() +self:F2() +self._BoundDetectedZones=true +return self +end +function DETECTION_AREAS:GetChangeText(DetectedItem) +self:F(DetectedItem) +local MT={} +for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do +if ChangeCode=="AA"then +MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target." +end +if ChangeCode=="AAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RA"then +MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area." +end +if ChangeCode=="AU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."." +end +if ChangeCode=="RU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."." +end +end +return table.concat(MT,"\n") +end +function DETECTION_AREAS:CreateDetectionItems() +self:F("Checking Detected Items for new Detected Units ...") +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +if DetectedItem then +self:T2({"Detected Item ID: ",DetectedItemID}) +local DetectedSet=DetectedItem.Set +local AreaExists=false +self:T3({"Zone Center Unit:",DetectedItem.Zone.ZoneUNIT.UnitName}) +local DetectedZoneObject=self:GetDetectedObject(DetectedItem.Zone.ZoneUNIT.UnitName) +self:T3({"Detected Zone Object:",DetectedItem.Zone:GetName(),DetectedZoneObject}) +if DetectedZoneObject then +AreaExists=true +else +DetectedSet:RemoveUnitsByName(DetectedItem.Zone.ZoneUNIT.UnitName) +self:AddChangeItem(DetectedItem,'RAU',self:GetDetectedUnitTypeName(DetectedItem.Zone.ZoneUNIT)) +for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do +local DetectedUnit=DetectedUnitData +local DetectedObject=self:GetDetectedObject(DetectedUnit.UnitName) +local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) +if DetectedObject then +self:IdentifyDetectedObject(DetectedObject) +AreaExists=true +DetectedItem.Zone=ZONE_UNIT:New(DetectedUnit:GetName(),DetectedUnit,self.DetectionZoneRange) +self:AddChangeItem(DetectedItem,"AAU",DetectedUnitTypeName) +break +else +DetectedSet:Remove(DetectedUnitName) +self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName) +end +end +end +if AreaExists then +for DetectedUnitName,DetectedUnitData in pairs(DetectedSet:GetSet())do +local DetectedUnit=DetectedUnitData +local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) +local DetectedObject=nil +if DetectedUnit:IsAlive()then +DetectedObject=self:GetDetectedObject(DetectedUnit:GetName()) +end +if DetectedObject then +if DetectedUnit:IsInZone(DetectedItem.Zone)then +self:IdentifyDetectedObject(DetectedObject) +DetectedSet:AddUnit(DetectedUnit) +else +DetectedSet:Remove(DetectedUnitName) +self:AddChangeUnit(DetectedItem,"RU",DetectedUnitTypeName) +end +else +self:AddChangeUnit(DetectedItem,"RU","destroyed target") +DetectedSet:Remove(DetectedUnitName) +end +end +else +self:RemoveDetectedItem(DetectedItemID) +self:AddChangeItem(DetectedItem,"RA") +end +end +end +for DetectedUnitName,DetectedObjectData in pairs(self.DetectedObjects)do +local DetectedObject=self:GetDetectedObject(DetectedUnitName) +if DetectedObject then +local DetectedUnit=UNIT:FindByName(DetectedUnitName) +local DetectedUnitTypeName=self:GetDetectedUnitTypeName(DetectedUnit) +local AddedToDetectionArea=false +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +if DetectedItem then +local DetectedSet=DetectedItem.Set +if not self:IsDetectedObjectIdentified(DetectedObject)and DetectedUnit:IsInZone(DetectedItem.Zone)then +self:IdentifyDetectedObject(DetectedObject) +DetectedSet:AddUnit(DetectedUnit) +AddedToDetectionArea=true +self:AddChangeUnit(DetectedItem,"AU",DetectedUnitTypeName) +end +end +end +if AddedToDetectionArea==false then +local DetectedItem=self:AddDetectedItemZone("AREA",nil, +SET_UNIT:New():FilterDeads():FilterCrashes(), +ZONE_UNIT:New(DetectedUnitName,DetectedUnit,self.DetectionZoneRange) +) +DetectedItem.Set:AddUnit(DetectedUnit) +self:AddChangeItem(DetectedItem,"AA",DetectedUnitTypeName) +end +end +end +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +local DetectedSet=DetectedItem.Set +local DetectedFirstUnit=DetectedSet:GetFirst() +local DetectedZone=DetectedItem.Zone +local DetectedZoneCoord=DetectedZone:GetCoordinate() +self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit) +self:CalculateIntercept(DetectedItem) +local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSet}) +local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then +DetectedItem.Changed=true +end +self:SetDetectedItemThreatLevel(DetectedItem) +self:NearestRecce(DetectedItem) +if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedZone.ZoneUNIT:SmokeRed() +end +DetectedSet:ForEachUnit( +function(DetectedUnit) +if DetectedUnit:IsAlive()then +if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then +DetectedUnit:FlareGreen() +end +if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedUnit:SmokeGreen() +end +end +end +) +if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then +DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90)) +end +if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then +DetectedZone:SmokeZone(SMOKECOLOR.White,30) +end +if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then +self.CountryID=DetectedSet:GetFirst():GetCountry() +DetectedZone:BoundZone(12,self.CountryID) +end +end +end +end +do +DETECTION_ZONES={ +ClassName="DETECTION_ZONES", +DetectionZoneRange=nil, +} +function DETECTION_ZONES:New(DetectionSetZone,DetectionCoalition) +local self=BASE:Inherit(self,DETECTION_BASE:New(DetectionSetZone)) +self.DetectionSetZone=DetectionSetZone +self.DetectionCoalition=DetectionCoalition +self._SmokeDetectedUnits=false +self._FlareDetectedUnits=false +self._SmokeDetectedZones=false +self._FlareDetectedZones=false +self._BoundDetectedZones=false +return self +end +function DETECTION_ZONES:CountAliveRecce() +return self.DetectionSetZone:Count() +end +function DETECTION_ZONES:ForEachAliveRecce(IteratorFunction,...) +self:F2(arg) +self.DetectionSetZone:ForEachZone(IteratorFunction,arg) +return self +end +function DETECTION_ZONES:DetectedItemReportSummary(DetectedItem,AttackGroup,Settings) +self:F({DetectedItem=DetectedItem}) +local DetectedItemID=self:GetDetectedItemID(DetectedItem) +if DetectedItem then +local DetectedSet=self:GetDetectedItemSet(DetectedItem) +local ReportSummaryItem +local DetectedZone=self:GetDetectedItemZone(DetectedItem) +local DetectedItemCoordinate=DetectedZone:GetCoordinate() +local DetectedItemCoordText=DetectedItemCoordinate:ToString(AttackGroup,Settings) +local ThreatLevelA2G=self:GetDetectedItemThreatLevel(DetectedItem) +local DetectedItemsCount=DetectedSet:Count() +local DetectedItemsTypes=DetectedSet:GetTypeNames() +local Report=REPORT:New() +Report:Add(DetectedItemID..", "..DetectedItemCoordText) +Report:Add(string.format("Threat: [%s]",string.rep("■",ThreatLevelA2G),string.rep("□",10-ThreatLevelA2G))) +Report:Add(string.format("Type: %2d of %s",DetectedItemsCount,DetectedItemsTypes)) +Report:Add(string.format("Detected: %s",DetectedItem.IsDetected and"yes"or"no")) +return Report +end +return nil +end +function DETECTION_ZONES:DetectedReportDetailed(AttackGroup) +self:F() +local Report=REPORT:New() +for DetectedItemIndex,DetectedItem in pairs(self.DetectedItems)do +local DetectedItem=DetectedItem +local ReportSummary=self:DetectedItemReportSummary(DetectedItem,AttackGroup) +Report:SetTitle("Detected areas:") +Report:Add(ReportSummary:Text()) +end +local ReportText=Report:Text() +return ReportText +end +function DETECTION_ZONES:CalculateIntercept(DetectedItem) +local DetectedCoord=DetectedItem.Coordinate +DetectedItem.InterceptCoord=DetectedCoord +end +function DETECTION_ZONES:SmokeDetectedUnits() +self:F2() +self._SmokeDetectedUnits=true +return self +end +function DETECTION_ZONES:FlareDetectedUnits() +self:F2() +self._FlareDetectedUnits=true +return self +end +function DETECTION_ZONES:SmokeDetectedZones() +self:F2() +self._SmokeDetectedZones=true +return self +end +function DETECTION_ZONES:FlareDetectedZones() +self:F2() +self._FlareDetectedZones=true +return self +end +function DETECTION_ZONES:BoundDetectedZones() +self:F2() +self._BoundDetectedZones=true +return self +end +function DETECTION_ZONES:GetChangeText(DetectedItem) +self:F(DetectedItem) +local MT={} +for ChangeCode,ChangeData in pairs(DetectedItem.Changes)do +if ChangeCode=="AA"then +MT[#MT+1]="Detected new area "..ChangeData.ID..". The center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". Removed the center target." +end +if ChangeCode=="AAU"then +MT[#MT+1]="Changed area "..ChangeData.ID..". The new center target is a "..ChangeData.ItemUnitType.."." +end +if ChangeCode=="RA"then +MT[#MT+1]="Removed old area "..ChangeData.ID..". No more targets in this area." +end +if ChangeCode=="AU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Detected for area "..ChangeData.ID.." new target(s) "..table.concat(MTUT,", ").."." +end +if ChangeCode=="RU"then +local MTUT={} +for ChangeUnitType,ChangeUnitCount in pairs(ChangeData)do +if ChangeUnitType~="ID"then +MTUT[#MTUT+1]=ChangeUnitCount.." of "..ChangeUnitType +end +end +MT[#MT+1]="Removed for area "..ChangeData.ID.." invisible or destroyed target(s) "..table.concat(MTUT,", ").."." +end +end +return table.concat(MT,"\n") +end +function DETECTION_ZONES:CreateDetectionItems() +self:F("Checking Detected Items for new Detected Units ...") +local DetectedUnits=SET_UNIT:New() +for ZoneName,DetectionZone in pairs(self.DetectionSetZone:GetSet())do +local DetectedItem=self:GetDetectedItemByKey(ZoneName) +if DetectedItem==nil then +DetectedItem=self:AddDetectedItemZone("ZONE",ZoneName,nil,DetectionZone) +end +local DetectedItemSetUnit=self:GetDetectedItemSet(DetectedItem) +DetectionZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +local ZoneUnits=DetectionZone:GetScannedUnits() +for DCSUnitID,DCSUnit in pairs(ZoneUnits)do +local UnitName=DCSUnit:getName() +local ZoneUnit=UNIT:FindByName(UnitName) +local ZoneUnitCoalition=ZoneUnit:GetCoalition() +if ZoneUnitCoalition==self.DetectionCoalition then +if DetectedItemSetUnit:FindUnit(UnitName)==nil and DetectedUnits:FindUnit(UnitName)==nil then +self:F("Adding "..UnitName) +DetectedItemSetUnit:AddUnit(ZoneUnit) +DetectedUnits:AddUnit(ZoneUnit) +end +end +end +end +for DetectedItemID,DetectedItemData in pairs(self.DetectedItems)do +local DetectedItem=DetectedItemData +local DetectedSet=self:GetDetectedItemSet(DetectedItem) +local DetectedFirstUnit=DetectedSet:GetFirst() +local DetectedZone=self:GetDetectedItemZone(DetectedItem) +local DetectedZoneCoord=DetectedZone:GetCoordinate() +self:SetDetectedItemCoordinate(DetectedItem,DetectedZoneCoord,DetectedFirstUnit) +self:CalculateIntercept(DetectedItem) +local OldFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +self:ReportFriendliesNearBy({DetectedItem=DetectedItem,ReportSetGroup=self.DetectionSetGroup}) +local NewFriendliesNearbyGround=self:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +if OldFriendliesNearbyGround~=NewFriendliesNearbyGround then +DetectedItem.Changed=true +end +self:SetDetectedItemThreatLevel(DetectedItem) +if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedZone:SmokeZone(SMOKECOLOR.Red,30) +end +DetectedSet:ForEachUnit( +function(DetectedUnit) +if DetectedUnit:IsAlive()then +if DETECTION_ZONES._FlareDetectedUnits or self._FlareDetectedUnits then +DetectedUnit:FlareGreen() +end +if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then +DetectedUnit:SmokeGreen() +end +end +end +) +if DETECTION_ZONES._FlareDetectedZones or self._FlareDetectedZones then +DetectedZone:FlareZone(SMOKECOLOR.White,30,math.random(0,90)) +end +if DETECTION_ZONES._SmokeDetectedZones or self._SmokeDetectedZones then +DetectedZone:SmokeZone(SMOKECOLOR.White,30) +end +if DETECTION_ZONES._BoundDetectedZones or self._BoundDetectedZones then +self.CountryID=DetectedSet:GetFirst():GetCountry() +DetectedZone:BoundZone(12,self.CountryID) +end +end +end +function DETECTION_ZONES:onafterDetection(From,Event,To,Detection,DetectionTimeStamp) +self.DetectionRun=self.DetectionRun+1 +if self.DetectionCount>0 and self.DetectionRun==self.DetectionCount then +self:CreateDetectionItems() +for DetectedItemID,DetectedItem in pairs(self.DetectedItems)do +self:UpdateDetectedItemDetection(DetectedItem) +self:CleanDetectionItem(DetectedItem,DetectedItemID) +if DetectedItem then +self:__DetectedItem(0.1,DetectedItem) +end +end +self:__Detect(-self.RefreshTimeInterval) +end +end +function DETECTION_ZONES:UpdateDetectedItemDetection(DetectedItem) +local IsDetected=true +DetectedItem.IsDetected=true +return IsDetected +end +end +do +DESIGNATE={ +ClassName="DESIGNATE", +} +function DESIGNATE:New(CC,Detection,AttackSet,Mission) +local self=BASE:Inherit(self,FSM:New()) +self:F({Detection}) +self:SetStartState("Designating") +self:AddTransition("*","Detect","*") +self:AddTransition("*","LaseOn","Lasing") +self:AddTransition("Lasing","Lasing","Lasing") +self:AddTransition("*","LaseOff","Designate") +self:AddTransition("*","Smoke","*") +self:AddTransition("*","Illuminate","*") +self:AddTransition("*","DoneSmoking","*") +self:AddTransition("*","DoneIlluminating","*") +self:AddTransition("*","Status","*") +self.CC=CC +self.Detection=Detection +self.AttackSet=AttackSet +self.RecceSet=Detection:GetDetectionSet() +self.Recces={} +self.Designating={} +self:SetDesignateName() +self:SetLaseDuration() +self:SetFlashStatusMenu(false) +self:SetFlashDetectionMessages(true) +self:SetMission(Mission) +self:SetLaserCodes({1688,1130,4785,6547,1465,4578}) +self:SetAutoLase(false,false) +self:SetThreatLevelPrioritization(false) +self:SetMaximumDesignations(5) +self:SetMaximumDistanceDesignations(8000) +self:SetMaximumMarkings(2) +self:SetDesignateMenu() +self.LaserCodesUsed={} +self.MenuLaserCodes={} +self.Detection:__Start(2) +self:__Detect(-15) +self.MarkScheduler=SCHEDULER:New(self) +return self +end +function DESIGNATE:SetFlashStatusMenu(FlashMenu) +self.FlashStatusMenu={} +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +self.FlashStatusMenu[AttackGroup]=FlashMenu +end +) +return self +end +function DESIGNATE:SetFlashDetectionMessages(FlashDetectionMessage) +self.FlashDetectionMessage={} +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +self.FlashDetectionMessage[AttackGroup]=FlashDetectionMessage +end +) +return self +end +function DESIGNATE:SetMaximumDesignations(MaximumDesignations) +self.MaximumDesignations=MaximumDesignations +return self +end +function DESIGNATE:SetMaximumDistanceGroundDesignation(MaximumDistanceGroundDesignation) +self.MaximumDistanceGroundDesignation=MaximumDistanceGroundDesignation +return self +end +function DESIGNATE:SetMaximumDistanceAirDesignation(MaximumDistanceAirDesignation) +self.MaximumDistanceAirDesignation=MaximumDistanceAirDesignation +return self +end +function DESIGNATE:SetMaximumDistanceDesignations(MaximumDistanceDesignations) +self.MaximumDistanceDesignations=MaximumDistanceDesignations +return self +end +function DESIGNATE:SetMaximumMarkings(MaximumMarkings) +self.MaximumMarkings=MaximumMarkings +return self +end +function DESIGNATE:SetLaserCodes(LaserCodes) +self.LaserCodes=(type(LaserCodes)=="table")and LaserCodes or{LaserCodes} +self:F({LaserCodes=self.LaserCodes}) +self.LaserCodesUsed={} +return self +end +function DESIGNATE:AddMenuLaserCode(LaserCode,MenuText) +self.MenuLaserCodes[LaserCode]=MenuText +self:SetDesignateMenu() +return self +end +function DESIGNATE:RemoveMenuLaserCode(LaserCode) +self.MenuLaserCodes[LaserCode]=nil +self:SetDesignateMenu() +return self +end +function DESIGNATE:SetDesignateName(DesignateName) +self.DesignateName="Designation"..(DesignateName and(" for "..DesignateName)or"") +return self +end +function DESIGNATE:SetLaseDuration(LaseDuration) +self.LaseDuration=LaseDuration or 120 +return self +end +function DESIGNATE:GenerateLaserCodes() +self.LaserCodes={} +local function containsDigit(_number,_numberToFind) +local _thisNumber=_number +local _thisDigit=0 +while _thisNumber~=0 do +_thisDigit=_thisNumber%10 +_thisNumber=math.floor(_thisNumber/10) +if _thisDigit==_numberToFind then +return true +end +end +return false +end +local _code=1111 +local _count=1 +while _code<1777 and _count<30 do +while true do +_code=_code+1 +if not containsDigit(_code,8) +and not containsDigit(_code,9) +and not containsDigit(_code,0)then +self:T(_code) +table.insert(self.LaserCodes,_code) +break +end +end +_count=_count+1 +end +self.LaserCodesUsed={} +return self +end +function DESIGNATE:SetAutoLase(AutoLase,Message) +self.AutoLase=AutoLase or false +if Message then +local AutoLaseOnOff=(self.AutoLase==true)and"On"or"Off" +local CC=self.CC:GetPositionable() +if CC then +CC:MessageToSetGroup(self.DesignateName..": Auto Lase "..AutoLaseOnOff..".",15,self.AttackSet) +end +end +self:CoordinateLase() +self:SetDesignateMenu() +return self +end +function DESIGNATE:SetThreatLevelPrioritization(Prioritize) +self.ThreatLevelPrioritization=Prioritize +return self +end +function DESIGNATE:SetMission(Mission) +self.Mission=Mission +return self +end +function DESIGNATE:onafterDetect() +self:__Detect(-math.random(60)) +self:DesignationScope() +self:CoordinateLase() +self:SendStatus() +self:SetDesignateMenu() +return self +end +function DESIGNATE:DesignationScope() +local DetectedItems=self.Detection:GetDetectedItemsByIndex() +local DetectedItemCount=0 +for DesignateIndex,Designating in pairs(self.Designating)do +local DetectedItem=self.Detection:GetDetectedItemByIndex(DesignateIndex) +if DetectedItem then +local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem) +self:F({IsDetected=IsDetected}) +if IsDetected==false then +self:F("Removing") +self.Designating[DesignateIndex]=nil +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +if AttackGroup:IsAlive()==true then +local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ") +self.CC:GetPositionable():MessageToGroup("Targets out of LOS\n"..DetectionText,10,AttackGroup,self.DesignateName) +end +end +) +else +DetectedItemCount=DetectedItemCount+1 +end +else +self.Designating[DesignateIndex]=nil +end +end +if DetectedItemCount<5 then +for DesignateIndex,DetectedItem in pairs(DetectedItems)do +local IsDetected=self.Detection:IsDetectedItemDetected(DetectedItem) +if IsDetected==true then +self:F({DistanceRecce=DetectedItem.DistanceRecce}) +if DetectedItem.DistanceRecce<=self.MaximumDistanceDesignations then +if self.Designating[DesignateIndex]==nil then +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +if self.FlashDetectionMessage[AttackGroup]==true then +local DetectionText=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ") +self.CC:GetPositionable():MessageToGroup("Targets detected at \n"..DetectionText,10,AttackGroup,self.DesignateName) +end +end +) +self.Designating[DesignateIndex]="" +break +end +end +end +end +end +return self +end +function DESIGNATE:CoordinateLase() +local DetectedItems=self.Detection:GetDetectedItemsByIndex() +for DesignateIndex,Designating in pairs(self.Designating)do +local DetectedItem=DetectedItems[DesignateIndex] +if DetectedItem then +if self.AutoLase then +self:LaseOn(DesignateIndex,self.LaseDuration) +end +end +end +return self +end +function DESIGNATE:SendStatus(MenuAttackGroup) +self.AttackSet:ForEachGroupAlive( +function(AttackGroup) +if self.FlashStatusMenu[AttackGroup]or(MenuAttackGroup and(AttackGroup:GetName()==MenuAttackGroup:GetName()))then +local DetectedReport=REPORT:New("Targets ready for Designation:") +local DetectedItems=self.Detection:GetDetectedItemsByIndex() +for DesignateIndex,Designating in pairs(self.Designating)do +local DetectedItem=DetectedItems[DesignateIndex] +if DetectedItem then +local Report=self.Detection:DetectedItemReportSummary(DetectedItem,AttackGroup):Text(", ") +DetectedReport:Add(string.rep("-",140)) +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={} +local ReportTypes=REPORT:New() +local ReportLaserCodes=REPORT:New() +TargetSetUnit:Flush(self) +for TargetUnit,RecceData in pairs(self.Recces)do +local Recce=RecceData +self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()}) +if not Recce:IsLasing()then +local LaserCode=Recce:GetLaserCode() +self:F({ClearingLaserCode=LaserCode}) +self.LaserCodesUsed[LaserCode]=nil +self.Recces[TargetUnit]=nil +end +end +if LaserCodeRequested then +for TargetUnit,RecceData in pairs(self.Recces)do +local Recce=RecceData +self:F({TargetUnit=TargetUnit,Recce=Recce:GetName()}) +if Recce:IsLasing()then +Recce:LaseOff() +local LaserCode=Recce:GetLaserCode() +self:F({ClearingLaserCode=LaserCode}) +self.LaserCodesUsed[LaserCode]=nil +self.Recces[TargetUnit]=nil +break +end +end +end +if self.AutoLase or(not self.AutoLase and(self.LaseStart+Duration>=timer.getTime()))then +TargetSetUnit:ForEachUnitPerThreatLevel(10,0, +function(TargetUnit) +self:F({TargetUnit=TargetUnit:GetName()}) +if MarkingCount0 and self.takeoff~=RAT.wp.air then +self.takeoff=RAT.wp.air +self:E(RAT.id..string.format("ERROR: At least one zone defined as departure and takeoff is NOT set to air. Enabling air start for RAT group %s!",self.alias)) +end +if self.Ndeparture_Airports==0 and self.Ndeparture_Zone==0 then +self.random_departure=true +local text=string.format("No airports or zones found given in SetDeparture(). Enabling random departure airports for RAT group %s!",self.alias) +self:E(RAT.id.."ERROR: "..text) +MESSAGE:New(text,30):ToAll() +end +end +if not self.random_destination then +for _,name in pairs(self.destination_ports)do +if self:_AirportExists(name)then +self.Ndestination_Airports=self.Ndestination_Airports+1 +elseif self:_ZoneExists(name)then +self.Ndestination_Zones=self.Ndestination_Zones+1 +end +end +if self.Ndestination_Zones>0 and self.landing~=RAT.wp.air and not self.returnzone then +self.landing=RAT.wp.air +self.destinationzone=true +self:E(RAT.id.."ERROR: At least one zone defined as destination and landing is NOT set to air. Enabling destination zone!") +end +if self.Ndestination_Airports==0 and self.Ndestination_Zones==0 then +self.random_destination=true +local text="No airports or zones found given in SetDestination(). Enabling random destination airports!" +self:E(RAT.id.."ERROR: "..text) +MESSAGE:New(text,30):ToAll() +end +end +if self.destinationzone and self.returnzone then +self:E(RAT.id.."ERROR: Destination zone _and_ return to zone not possible! Disabling return to zone.") +self.returnzone=false +end +if self.returnzone and self.takeoff==RAT.wp.air then +self.landing=RAT.wp.air +end +if self.FLminuser then +self.FLminuser=math.min(self.FLminuser,self.aircraft.ceiling) +end +if self.FLmaxuser then +self.FLmaxuser=math.min(self.FLmaxuser,self.aircraft.ceiling) +end +if self.FLcruise then +self.FLcruise=math.min(self.FLcruise,self.aircraft.ceiling) +end +if self.FLminuser and self.FLmaxuser then +if self.FLminuser>self.FLmaxuser then +local min=self.FLminuser +local max=self.FLmaxuser +self.FLminuser=max +self.FLmaxuser=min +end +end +if self.FLminuser and self.FLcruiseself.FLmaxuser then +self.FLcruise=self.FLmaxuser +end +if self.uncontrolled then +self.takeoff=RAT.wp.cold +end +end +function RAT:SetCoalition(friendly) +self:F2(friendly) +if friendly:lower()=="sameonly"then +self.friendly=RAT.coal.sameonly +elseif friendly:lower()=="neutral"then +self.friendly=RAT.coal.neutral +else +self.friendly=RAT.coal.same +end +return self +end +function RAT:SetCoalitionAircraft(color) +self:F2(color) +if color:lower()=="blue"then +self.coalition=coalition.side.BLUE +if not self.country then +self.country=country.id.USA +end +elseif color:lower()=="red"then +self.coalition=coalition.side.RED +if not self.country then +self.country=country.id.RUSSIA +end +elseif color:lower()=="neutral"then +self.coalition=coalition.side.NEUTRAL +if not self.country then +self.country=country.id.SWITZERLAND +end +end +return self +end +function RAT:SetCountry(id) +self:F2(id) +self.country=id +return self +end +function RAT:SetTerminalType(termtype) +self:F2(termtype) +self.termtype=termtype +return self +end +function RAT:SetParkingScanRadius(radius) +self:F2(radius) +self.parkingscanradius=radius or 50 +return self +end +function RAT:SetParkingScanSceneryON() +self:F2() +self.parkingscanscenery=true +return self +end +function RAT:SetParkingScanSceneryOFF() +self:F2() +self.parkingscanscenery=false +return self +end +function RAT:SetParkingSpotSafeON() +self:F2() +self.parkingverysafe=true +return self +end +function RAT:SetParkingSpotSafeOFF() +self:F2() +self.parkingverysafe=false +return self +end +function RAT:SetDespawnAirOFF() +self.despawnair=false +return self +end +function RAT:SetTakeoff(type) +self:F2(type) +local _Type +if type:lower()=="takeoff-cold"or type:lower()=="cold"then +_Type=RAT.wp.cold +elseif type:lower()=="takeoff-hot"or type:lower()=="hot"then +_Type=RAT.wp.hot +elseif type:lower()=="takeoff-runway"or type:lower()=="runway"then +_Type=RAT.wp.runway +elseif type:lower()=="air"then +_Type=RAT.wp.air +else +_Type=RAT.wp.coldorhot +end +self.takeoff=_Type +return self +end +function RAT:SetTakeoffCold() +self.takeoff=RAT.wp.cold +return self +end +function RAT:SetTakeoffHot() +self.takeoff=RAT.wp.hot +return self +end +function RAT:SetTakeoffRunway() +self.takeoff=RAT.wp.runway +return self +end +function RAT:SetTakeoffColdOrHot() +self.takeoff=RAT.wp.coldorhot +return self +end +function RAT:SetTakeoffAir() +self.takeoff=RAT.wp.air +return self +end +function RAT:SetDeparture(departurenames) +self:F2(departurenames) +self.random_departure=false +local names +if type(departurenames)=="table"then +names=departurenames +elseif type(departurenames)=="string"then +names={departurenames} +else +self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDeparture()!") +end +for _,name in pairs(names)do +if self:_AirportExists(name)then +table.insert(self.departure_ports,name) +elseif self:_ZoneExists(name)then +table.insert(self.departure_ports,name) +else +self:E(RAT.id.."ERROR: No departure airport or zone found with name "..name) +end +end +return self +end +function RAT:SetDestination(destinationnames) +self:F2(destinationnames) +self.random_destination=false +local names +if type(destinationnames)=="table"then +names=destinationnames +elseif type(destinationnames)=="string"then +names={destinationnames} +else +self:E(RAT.id.."ERROR: Input parameter must be a string or a table in SetDestination()!") +end +for _,name in pairs(names)do +if self:_AirportExists(name)then +table.insert(self.destination_ports,name) +elseif self:_ZoneExists(name)then +table.insert(self.destination_ports,name) +else +self:E(RAT.id.."ERROR: No destination airport or zone found with name "..name) +end +end +return self +end +function RAT:DestinationZone() +self:F2() +self.destinationzone=true +self.landing=RAT.wp.air +return self +end +function RAT:ReturnZone() +self:F2() +self.returnzone=true +return self +end +function RAT:SetDestinationsFromZone(zone) +self:F2(zone) +self.random_destination=false +self.destination_Azone=zone +return self +end +function RAT:SetDeparturesFromZone(zone) +self:F2(zone) +self.random_departure=false +self.departure_Azone=zone +return self +end +function RAT:AddFriendlyAirportsToDepartures() +self:F2() +self.addfriendlydepartures=true +return self +end +function RAT:AddFriendlyAirportsToDestinations() +self:F2() +self.addfriendlydestinations=true +return self +end +function RAT:ExcludedAirports(ports) +self:F2(ports) +if type(ports)=="string"then +self.excluded_ports={ports} +else +self.excluded_ports=ports +end +return self +end +function RAT:SetAISkill(skill) +self:F2(skill) +if skill:lower()=="average"then +self.skill="Average" +elseif skill:lower()=="good"then +self.skill="Good" +elseif skill:lower()=="excellent"then +self.skill="Excellent" +elseif skill:lower()=="random"then +self.skill="Random" +else +self.skill="High" +end +return self +end +function RAT:Livery(skins) +self:F2(skins) +if type(skins)=="string"then +self.livery={skins} +else +self.livery=skins +end +return self +end +function RAT:ChangeAircraft(actype) +self:F2(actype) +self.actype=actype +return self +end +function RAT:ContinueJourney() +self:F2() +self.continuejourney=true +self.commute=false +return self +end +function RAT:Commute(starshape) +self:F2() +self.commute=true +self.continuejourney=false +if starshape then +self.starshape=starshape +else +self.starshape=false +end +return self +end +function RAT:SetSpawnDelay(delay) +self:F2(delay) +delay=delay or 5 +self.spawndelay=math.max(0.5,delay) +return self +end +function RAT:SetSpawnInterval(interval) +self:F2(interval) +interval=interval or 5 +self.spawninterval=math.max(0.5,interval) +return self +end +function RAT:RespawnAfterLanding(delay) +self:F2(delay) +delay=delay or 180 +self.respawn_at_landing=true +delay=math.max(1.0,delay) +self.respawn_delay=delay +return self +end +function RAT:SetRespawnDelay(delay) +self:F2(delay) +delay=delay or 1.0 +delay=math.max(1.0,delay) +self.respawn_delay=delay +return self +end +function RAT:NoRespawn() +self:F2() +self.norespawn=true +return self +end +function RAT:SetMaxRespawnTriedWhenSpawnedOnRunway(n) +self:F2(n) +n=n or 3 +self.onrunwaymaxretry=n +return self +end +function RAT:RespawnAfterTakeoff() +self:F2() +self.respawn_after_takeoff=true +return self +end +function RAT:RespawnAfterCrashON() +self:F2() +self.respawn_after_crash=true +return self +end +function RAT:RespawnAfterCrashOFF() +self:F2() +self.respawn_after_crash=false +return self +end +function RAT:RespawnInAirAllowed() +self:F2() +self.respawn_inair=true +return self +end +function RAT:RespawnInAirNotAllowed() +self:F2() +self.respawn_inair=false +return self +end +function RAT:CheckOnRunway(switch,distance) +self:F2(switch) +if switch==nil then +switch=true +end +self.checkonrunway=switch +self.onrunwayradius=distance or 75 +return self +end +function RAT:CheckOnTop(switch,radius) +self:F2(switch) +if switch==nil then +switch=true +end +self.checkontop=switch +self.ontopradius=radius or 2 +return self +end +function RAT:ParkingSpotDB(switch) +self:E("RAT ParkingSpotDB function is obsolete and will be removed soon!") +return self +end +function RAT:RadioON() +self:F2() +self.radio=true +return self +end +function RAT:RadioOFF() +self:F2() +self.radio=false +return self +end +function RAT:RadioFrequency(frequency) +self:F2(frequency) +self.frequency=frequency +return self +end +function RAT:RadioModulation(modulation) +self:F2(modulation) +if modulation=="AM"then +self.modulation=radio.modulation.AM +elseif modulation=="FM"then +self.modulation=radio.modulation.FM +else +self.modulation=radio.modulation.AM +end +return self +end +function RAT:RadioMenuON() +self:F2() +self.f10menu=true +return self +end +function RAT:RadioMenuOFF() +self:F2() +self.f10menu=false +return self +end +function RAT:Invisible() +self:F2() +self.invisible=true +return self +end +function RAT:SetEPLRS(switch) +if switch==nil or switch==true then +self.eplrs=true +else +self.eplrs=false +end +return self +end +function RAT:Immortal() +self:F2() +self.immortal=true +return self +end +function RAT:Uncontrolled() +self:F2() +self.uncontrolled=true +return self +end +function RAT:ActivateUncontrolled(maxactivated,delay,delta,frand) +self:F2({max=maxactivated,delay=delay,delta=delta,rand=frand}) +self.activate_uncontrolled=true +self.activate_max=maxactivated or 1 +self.activate_delay=delay or 1 +self.activate_delta=delta or 1 +self.activate_frand=frand or 0 +self.activate_delay=math.max(self.activate_delay,1) +self.activate_delta=math.max(self.activate_delta,0) +self.activate_frand=math.max(self.activate_frand,0) +self.activate_frand=math.min(self.activate_frand,1) +return self +end +function RAT:TimeDestroyInactive(time) +self:F2(time) +time=time or self.Tinactive +time=math.max(time,60) +self.Tinactive=time +return self +end +function RAT:SetMaxCruiseSpeed(speed) +self:F2(speed) +self.Vcruisemax=speed/3.6 +return self +end +function RAT:SetClimbRate(rate) +self:F2(rate) +rate=rate or self.Vclimb +rate=math.max(rate,100) +rate=math.min(rate,15000) +self.Vclimb=rate +return self +end +function RAT:SetDescentAngle(angle) +self:F2(angle) +angle=angle or self.AlphaDescent +angle=math.max(angle,0.5) +angle=math.min(angle,50) +self.AlphaDescent=angle +return self +end +function RAT:SetROE(roe) +self:F2(roe) +if roe=="return"then +self.roe=RAT.ROE.returnfire +elseif roe=="free"then +self.roe=RAT.ROE.weaponfree +else +self.roe=RAT.ROE.weaponhold +end +return self +end +function RAT:SetROT(rot) +self:F2(rot) +if rot=="passive"then +self.rot=RAT.ROT.passive +elseif rot=="evade"then +self.rot=RAT.ROT.evade +else +self.rot=RAT.ROT.noreaction +end +return self +end +function RAT:MenuName(name) +self:F2(name) +self.SubMenuName=tostring(name) +return self +end +function RAT:EnableATC(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.ATCswitch=switch +return self +end +function RAT:ATC_Messages(switch) +self:F2(switch) +if switch==nil then +switch=true +end +RAT.ATC.messages=switch +return self +end +function RAT:ATC_Clearance(n) +self:F2(n) +RAT.ATC.Nclearance=n or 2 +return self +end +function RAT:ATC_Delay(time) +self:F2(time) +RAT.ATC.delay=time or 240 +return self +end +function RAT:SetMinDistance(dist) +self:F2(dist) +self.mindist=math.max(100,dist*1000) +return self +end +function RAT:SetMaxDistance(dist) +self:F2(dist) +self.maxdist=dist*1000 +return self +end +function RAT:_Debug(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.Debug=switch +return self +end +function RAT:Debugmode() +self:F2() +self.Debug=true +return self +end +function RAT:StatusReports(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.reportstatus=switch +return self +end +function RAT:PlaceMarkers(switch) +self:F2(switch) +if switch==nil then +switch=true +end +self.placemarkers=switch +return self +end +function RAT:SetFL(FL) +self:F2(FL) +FL=FL or self.FLcruise +FL=math.max(FL,0) +self.FLuser=FL*RAT.unit.FL2m +return self +end +function RAT:SetFLmax(FL) +self:F2(FL) +self.FLmaxuser=FL*RAT.unit.FL2m +return self +end +function RAT:SetMaxCruiseAltitude(alt) +self:F2(alt) +self.FLmaxuser=alt +return self +end +function RAT:SetFLmin(FL) +self:F2(FL) +self.FLminuser=FL*RAT.unit.FL2m +return self +end +function RAT:SetMinCruiseAltitude(alt) +self:F2(alt) +self.FLminuser=alt +return self +end +function RAT:SetFLcruise(FL) +self:F2(FL) +self.FLcruise=FL*RAT.unit.FL2m +return self +end +function RAT:SetCruiseAltitude(alt) +self:F2(alt) +self.FLcruise=alt +return self +end +function RAT:SetOnboardNum(tailnumprefix,zero) +self:F2({tailnumprefix=tailnumprefix,zero=zero}) +self.onboardnum=tailnumprefix +if zero~=nil then +self.onboardnum0=zero +end +return self +end +function RAT:_InitAircraft(DCSgroup) +self:F2(DCSgroup) +local DCSunit=DCSgroup:getUnit(1) +local DCSdesc=DCSunit:getDesc() +local DCScategory=DCSgroup:getCategory() +local DCStype=DCSunit:getTypeName() +if DCScategory==Group.Category.AIRPLANE then +self.category=RAT.cat.plane +elseif DCScategory==Group.Category.HELICOPTER then +self.category=RAT.cat.heli +else +self.category="other" +self:E(RAT.id.."ERROR: Group of RAT is neither airplane nor helicopter!") +end +self.aircraft.type=DCStype +self.aircraft.fuel=DCSunit:getFuel() +self.aircraft.Rmax=DCSdesc.range*RAT.unit.nm2m +self.aircraft.Reff=self.aircraft.Rmax*self.aircraft.fuel*0.95 +self.aircraft.Vmax=DCSdesc.speedMax +self.aircraft.Vymax=DCSdesc.VyMax +self.aircraft.ceiling=DCSdesc.Hmax +self.aircraft.length=DCSdesc.box.max.x +self.aircraft.height=DCSdesc.box.max.y +self.aircraft.width=DCSdesc.box.max.z +self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width) +local text=string.format("\n******************************************************\n") +text=text..string.format("Aircraft parameters:\n") +text=text..string.format("Template group = %s\n",self.SpawnTemplatePrefix) +text=text..string.format("Alias = %s\n",self.alias) +text=text..string.format("Category = %s\n",self.category) +text=text..string.format("Type = %s\n",self.aircraft.type) +text=text..string.format("Length (x) = %6.1f m\n",self.aircraft.length) +text=text..string.format("Width (z) = %6.1f m\n",self.aircraft.width) +text=text..string.format("Height (y) = %6.1f m\n",self.aircraft.height) +text=text..string.format("Max air speed = %6.1f m/s\n",self.aircraft.Vmax) +text=text..string.format("Max climb speed = %6.1f m/s\n",self.aircraft.Vymax) +text=text..string.format("Initial Fuel = %6.1f\n",self.aircraft.fuel*100) +text=text..string.format("Max range = %6.1f km\n",self.aircraft.Rmax/1000) +text=text..string.format("Eff range = %6.1f km (with 95 percent initial fuel amount)\n",self.aircraft.Reff/1000) +text=text..string.format("Ceiling = %6.1f km = FL%3.0f\n",self.aircraft.ceiling/1000,self.aircraft.ceiling/RAT.unit.FL2m) +text=text..string.format("******************************************************\n") +self:T(RAT.id..text) +end +function RAT:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata) +self:F({rat=RAT.id,departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,waypoint=_waypoint,lastpos=_lastpos,nrespawn=_nrespawn}) +local takeoff=self.takeoff +local landing=self.landing +if _takeoff then +takeoff=_takeoff +end +if _landing then +landing=_landing +end +if takeoff==RAT.wp.coldorhot then +local temp={RAT.wp.cold,RAT.wp.hot} +takeoff=temp[math.random(2)] +end +local nrespawn=0 +if _nrespawn then +nrespawn=_nrespawn +end +local departure,destination,waypoints,WPholding,WPfinal=self:_SetRoute(takeoff,landing,_departure,_destination,_waypoint) +if not(departure and destination and waypoints)then +return nil +end +local livery +if _livery then +livery=_livery +elseif self.livery then +livery=self.livery[math.random(#self.livery)] +local text=string.format("Chosen livery for group %s: %s",self:_AnticipatedGroupName(),livery) +self:T(RAT.id..text) +else +livery=nil +end +local successful=self:_ModifySpawnTemplate(waypoints,livery,_lastpos,departure,takeoff,parkingdata) +if not successful then +return nil +end +local group=self:SpawnWithIndex(self.SpawnIndex) +self.alive=self.alive+1 +self:T(RAT.id..string.format("Alive groups counter now = %d.",self.alive)) +if self.ATCswitch and landing==RAT.wp.landing then +if self.returnzone then +self:_ATCAddFlight(group:GetName(),departure:GetName()) +else +self:_ATCAddFlight(group:GetName(),destination:GetName()) +end +end +if self.placemarkers then +self:_PlaceMarkers(waypoints,self.SpawnIndex) +end +if self.invisible then +self:_CommandInvisible(group,true) +end +if self.immortal then +self:_CommandImmortal(group,true) +end +if self.eplrs then +group:CommandEPLRS(true,1) +end +self:_SetROE(group,self.roe) +self:_SetROT(group,self.rot) +self.ratcraft[self.SpawnIndex]={} +self.ratcraft[self.SpawnIndex]["group"]=group +self.ratcraft[self.SpawnIndex]["destination"]=destination +self.ratcraft[self.SpawnIndex]["departure"]=departure +self.ratcraft[self.SpawnIndex]["waypoints"]=waypoints +self.ratcraft[self.SpawnIndex]["airborne"]=group:InAir() +self.ratcraft[self.SpawnIndex]["nunits"]=group:GetInitialSize() +if group:InAir()then +self.ratcraft[self.SpawnIndex]["Tground"]=nil +self.ratcraft[self.SpawnIndex]["Pground"]=nil +self.ratcraft[self.SpawnIndex]["Uground"]=nil +self.ratcraft[self.SpawnIndex]["Tlastcheck"]=nil +else +self.ratcraft[self.SpawnIndex]["Tground"]=timer.getTime() +self.ratcraft[self.SpawnIndex]["Pground"]=group:GetCoordinate() +self.ratcraft[self.SpawnIndex]["Uground"]={} +for _,_unit in pairs(group:GetUnits())do +local _unitname=_unit:GetName() +self.ratcraft[self.SpawnIndex]["Uground"][_unitname]=_unit:GetCoordinate() +end +self.ratcraft[self.SpawnIndex]["Tlastcheck"]=timer.getTime() +end +self.ratcraft[self.SpawnIndex]["P0"]=group:GetCoordinate() +self.ratcraft[self.SpawnIndex]["Pnow"]=group:GetCoordinate() +self.ratcraft[self.SpawnIndex]["Distance"]=0 +self.ratcraft[self.SpawnIndex].takeoff=takeoff +self.ratcraft[self.SpawnIndex].landing=landing +self.ratcraft[self.SpawnIndex].wpholding=WPholding +self.ratcraft[self.SpawnIndex].wpfinal=WPfinal +self.ratcraft[self.SpawnIndex].active=not self.uncontrolled +self.ratcraft[self.SpawnIndex]["status"]=RAT.status.Spawned +self.ratcraft[self.SpawnIndex].livery=livery +self.ratcraft[self.SpawnIndex].despawnme=false +self.ratcraft[self.SpawnIndex].nrespawn=nrespawn +if self.f10menu then +local name=self.aircraft.type.." ID "..tostring(self.SpawnIndex) +self.Menu[self.SubMenuName].groups[self.SpawnIndex]=MENU_MISSION:New(name,self.Menu[self.SubMenuName].groups) +self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"]=MENU_MISSION:New("Set ROE",self.Menu[self.SubMenuName].groups[self.SpawnIndex]) +MENU_MISSION_COMMAND:New("Weapons hold",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.weaponhold) +MENU_MISSION_COMMAND:New("Weapons free",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.weaponfree) +MENU_MISSION_COMMAND:New("Return fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["roe"],self._SetROE,self,group,RAT.ROE.returnfire) +self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"]=MENU_MISSION:New("Set ROT",self.Menu[self.SubMenuName].groups[self.SpawnIndex]) +MENU_MISSION_COMMAND:New("No reaction",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.noreaction) +MENU_MISSION_COMMAND:New("Passive defense",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.passive) +MENU_MISSION_COMMAND:New("Evade on fire",self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"],self._SetROT,self,group,RAT.ROT.evade) +MENU_MISSION_COMMAND:New("Despawn group",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._Despawn,self,group) +MENU_MISSION_COMMAND:New("Place markers",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self._PlaceMarkers,self,waypoints,self.SpawnIndex) +MENU_MISSION_COMMAND:New("Status report",self.Menu[self.SubMenuName].groups[self.SpawnIndex],self.Status,self,true,self.SpawnIndex) +end +return self.SpawnIndex +end +function RAT:ClearForLanding(name) +trigger.action.setUserFlag(name,1) +local flagvalue=trigger.misc.getUserFlag(name) +self:T(RAT.id.."ATC: User flag value (landing) for "..name.." set to "..flagvalue) +end +function RAT:_Respawn(index,lastpos,delay) +local departure=self.ratcraft[index].departure +local destination=self.ratcraft[index].destination +local takeoff=self.ratcraft[index].takeoff +local landing=self.ratcraft[index].landing +local livery=self.ratcraft[index].livery +local lastwp=self.ratcraft[index].waypoints[#self.ratcraft[index].waypoints] +local _departure=nil +local _destination=nil +local _takeoff=nil +local _landing=nil +local _livery=nil +local _lastwp=nil +local _lastpos=nil +if self.continuejourney then +_departure=destination:GetName() +_livery=livery +if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then +if destination:GetCategory()==4 then +_lastpos=lastpos +end +end +if self.destinationzone then +_takeoff=RAT.wp.air +_landing=RAT.wp.air +elseif self.returnzone then +_takeoff=self.takeoff +if self.takeoff==RAT.wp.air then +_landing=RAT.wp.air +else +_landing=RAT.wp.landing +end +_departure=departure:GetName() +else +_takeoff=self.takeoff +_landing=self.landing +end +elseif self.commute then +if self.starshape==true then +if destination:GetName()==self.homebase then +_departure=self.homebase +_destination=nil +else +_departure=destination:GetName() +_destination=self.homebase +end +else +_departure=destination:GetName() +_destination=departure:GetName() +end +_livery=livery +if landing==RAT.wp.landing and lastpos and not(self.respawn_at_landing or self.respawn_after_takeoff)then +if destination:GetCategory()==4 then +_lastpos=lastpos +end +end +if self.destinationzone then +if self.takeoff==RAT.wp.air then +_takeoff=RAT.wp.air +_landing=RAT.wp.air +else +if takeoff==RAT.wp.air then +_takeoff=self.takeoff +_landing=RAT.wp.air +else +_takeoff=RAT.wp.air +_landing=RAT.wp.landing +end +end +elseif self.returnzone then +_departure=departure:GetName() +_destination=destination:GetName() +_takeoff=self.takeoff +_landing=self.landing +end +end +if _takeoff==RAT.wp.air and(self.continuejourney or self.commute)then +_lastwp=lastwp +end +self:T2({departure=_departure,destination=_destination,takeoff=_takeoff,landing=_landing,livery=_livery,lastwp=_lastwp}) +local respawndelay +if delay then +respawndelay=delay +elseif self.respawn_delay then +respawndelay=self.respawn_delay+3 +else +respawndelay=3 +end +local arg={} +arg.self=self +arg.departure=_departure +arg.destination=_destination +arg.takeoff=_takeoff +arg.landing=_landing +arg.livery=_livery +arg.lastwp=_lastwp +arg.lastpos=_lastpos +self:T(RAT.id..string.format("%s delayed respawn in %.1f seconds.",self.alias,respawndelay)) +SCHEDULER:New(nil,self._SpawnWithRouteTimer,{arg},respawndelay) +end +function RAT._SpawnWithRouteTimer(arg) +RAT._SpawnWithRoute(arg.self,arg.departure,arg.destination,arg.takeoff,arg.landing,arg.livery,arg.lastwp,arg.lastpos) +end +function RAT:_SetRoute(takeoff,landing,_departure,_destination,_waypoint) +local VxCruiseMax +if self.Vcruisemax then +VxCruiseMax=math.min(self.Vcruisemax,self.aircraft.Vmax) +else +VxCruiseMax=math.min(self.aircraft.Vmax*0.90,250) +end +local VxCruiseMin=math.min(VxCruiseMax*0.70,166) +local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax) +local VxClimb=math.min(self.aircraft.Vmax*0.90,200) +local VxDescent=math.min(self.aircraft.Vmax*0.60,140) +local VxHolding=VxDescent*0.9 +local VxFinal=VxHolding*0.9 +local VyClimb=math.min(self.Vclimb*RAT.unit.ft2meter/60,self.aircraft.Vymax) +local AlphaClimb=math.asin(VyClimb/VxClimb) +local AlphaDescent=math.rad(self.AlphaDescent) +local FLcruise_expect=self.FLcruise +local departure=nil +if _departure then +if self:_AirportExists(_departure)then +departure=AIRBASE:FindByName(_departure) +if takeoff==RAT.wp.air then +departure=departure:GetZone() +end +elseif self:_ZoneExists(_departure)then +departure=ZONE:New(_departure) +else +local text=string.format("ERROR! Specified departure airport %s does not exist for %s.",_departure,self.alias) +self:E(RAT.id..text) +end +else +departure=self:_PickDeparture(takeoff) +if self.commute and self.starshape==true and self.homebase==nil then +self.homebase=departure:GetName() +end +end +if not departure then +local text=string.format("ERROR! No valid departure airport could be found for %s.",self.alias) +self:E(RAT.id..text) +return nil +end +local Pdeparture +if takeoff==RAT.wp.air then +if _waypoint then +Pdeparture=COORDINATE:New(_waypoint.x,_waypoint.alt,_waypoint.y) +else +local vec2=departure:GetRandomVec2() +Pdeparture=COORDINATE:NewFromVec2(vec2) +end +else +Pdeparture=departure:GetCoordinate() +end +local H_departure +if takeoff==RAT.wp.air then +local Hmin +if self.category==RAT.cat.plane then +Hmin=1000 +else +Hmin=50 +end +H_departure=self:_Randomize(FLcruise_expect*0.7,0.3,Pdeparture.y+Hmin,FLcruise_expect) +if self.FLminuser then +H_departure=math.max(H_departure,self.FLminuser) +end +if _waypoint then +H_departure=_waypoint.alt +end +else +H_departure=Pdeparture.y +end +local mindist=self.mindist +if self.FLminuser then +local hclimb=self.FLminuser-H_departure +local hdescent=self.FLminuser-H_departure +local Dclimb,Ddescent,Dtot=self:_MinDistance(AlphaClimb,AlphaDescent,hclimb,hdescent) +if takeoff==RAT.wp.air and landing==RAT.wpair then +mindist=0 +elseif takeoff==RAT.wp.air then +mindist=Ddescent +elseif landing==RAT.wp.air then +mindist=Dclimb +else +mindist=Dtot +end +mindist=math.max(self.mindist,mindist) +local text=string.format("Adjusting min distance to %d km (for given min FL%03d)",mindist/1000,self.FLminuser/RAT.unit.FL2m) +self:T(RAT.id..text) +end +local destination=nil +if _destination then +if self:_AirportExists(_destination)then +destination=AIRBASE:FindByName(_destination) +if landing==RAT.wp.air or self.returnzone then +destination=destination:GetZone() +end +elseif self:_ZoneExists(_destination)then +destination=ZONE:New(_destination) +else +local text=string.format("ERROR: Specified destination airport/zone %s does not exist for %s!",_destination,self.alias) +self:E(RAT.id.."ERROR: "..text) +end +else +local random=self.random_destination +if self.continuejourney and _departure and#self.destination_ports<3 then +random=true +end +local mylanding=landing +local acrange=self.aircraft.Reff +if self.returnzone then +mylanding=RAT.wp.air +acrange=self.aircraft.Reff/2 +end +destination=self:_PickDestination(departure,Pdeparture,mindist,math.min(acrange,self.maxdist),random,mylanding) +end +if not destination then +local text=string.format("No valid destination airport could be found for %s!",self.alias) +MESSAGE:New(text,60):ToAll() +self:E(RAT.id.."ERROR: "..text) +return nil +end +if destination:GetName()==departure:GetName()then +local text=string.format("%s: Destination and departure are identical. Airport/zone %s.",self.alias,destination:GetName()) +MESSAGE:New(text,30):ToAll() +self:E(RAT.id.."ERROR: "..text) +end +local Preturn +local destination_returnzone +if self.returnzone then +local vec2=destination:GetRandomVec2() +Preturn=COORDINATE:NewFromVec2(vec2) +destination_returnzone=destination +destination=departure +end +local Pdestination +if landing==RAT.wp.air then +local vec2=destination:GetRandomVec2() +Pdestination=COORDINATE:NewFromVec2(vec2) +else +Pdestination=destination:GetCoordinate() +end +local H_destination=Pdestination.y +local Rhmin=8000 +local Rhmax=20000 +if self.category==RAT.cat.heli then +Rhmin=500 +Rhmax=1000 +end +local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax,Rhmin) +local Pholding=COORDINATE:NewFromVec2(Vholding) +local H_holding=Pholding.y +local h_holding +if self.category==RAT.cat.plane then +h_holding=1200 +else +h_holding=150 +end +h_holding=self:_Randomize(h_holding,0.2) +local Hh_holding=H_holding+h_holding +if landing==RAT.wp.air then +Hh_holding=H_departure +end +local d_holding=Pholding:Get2DDistance(Pdestination) +local heading +local d_total +if self.returnzone then +heading=self:_Course(Pdeparture,Preturn) +d_total=Pdeparture:Get2DDistance(Preturn)+Preturn:Get2DDistance(Pholding) +else +heading=self:_Course(Pdeparture,Pholding) +d_total=Pdeparture:Get2DDistance(Pholding) +end +if takeoff==RAT.wp.air then +local H_departure_max +if landing==RAT.wp.air then +H_departure_max=H_departure +else +H_departure_max=d_total*math.tan(AlphaDescent)+Hh_holding +end +H_departure=math.min(H_departure,H_departure_max) +end +local deltaH=math.abs(H_departure-Hh_holding) +local phi=math.atan(deltaH/d_total) +local phi_climb +local phi_descent +if(H_departure>Hh_holding)then +phi_climb=AlphaClimb+phi +phi_descent=AlphaDescent-phi +else +phi_climb=AlphaClimb-phi +phi_descent=AlphaDescent+phi +end +local D_total +if self.returnzone then +D_total=math.sqrt(deltaH*deltaH+d_total/2*d_total/2) +else +D_total=math.sqrt(deltaH*deltaH+d_total*d_total) +end +local gamma=math.rad(180)-phi_climb-phi_descent +local a=D_total*math.sin(phi_climb)/math.sin(gamma) +local b=D_total*math.sin(phi_descent)/math.sin(gamma) +local hphi_max=b*math.sin(phi_climb) +local hphi_max2=a*math.sin(phi_descent) +local h_max1=b*math.sin(AlphaClimb) +local h_max2=a*math.sin(AlphaDescent) +local h_max +if(H_departure>Hh_holding)then +h_max=math.min(h_max1,h_max2) +else +h_max=math.max(h_max1,h_max2) +end +local FLmax=h_max+H_departure +local FLmin=math.max(H_departure,Hh_holding) +if self.category==RAT.cat.heli then +FLmin=math.max(H_departure,H_destination)+50 +FLmax=math.max(H_departure,H_destination)+1000 +end +FLmax=math.min(FLmax,self.aircraft.ceiling) +if self.FLminuser then +FLmin=math.max(self.FLminuser,FLmin) +end +if self.FLmaxuser then +FLmax=math.min(self.FLmaxuser,FLmax) +end +if FLmin>FLmax then +FLmin=FLmax +end +if FLcruise_expectFLmax then +FLcruise_expect=FLmax +end +local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax) +if self.FLuser then +FLcruise=self.FLuser +FLcruise=math.max(FLcruise,FLmin) +FLcruise=math.min(FLcruise,FLmax) +end +local h_climb=FLcruise-H_departure +local h_descent=FLcruise-Hh_holding +local d_climb=h_climb/math.tan(AlphaClimb) +local d_descent=h_descent/math.tan(AlphaDescent) +local d_cruise=d_total-d_climb-d_descent +local text=string.format("\n******************************************************\n") +text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) +text=text..string.format("Alias = %s\n",self.alias) +text=text..string.format("Group name = %s\n\n",self:_AnticipatedGroupName()) +text=text..string.format("Speeds:\n") +text=text..string.format("VxCruiseMin = %6.1f m/s = %5.1f km/h\n",VxCruiseMin,VxCruiseMin*3.6) +text=text..string.format("VxCruiseMax = %6.1f m/s = %5.1f km/h\n",VxCruiseMax,VxCruiseMax*3.6) +text=text..string.format("VxCruise = %6.1f m/s = %5.1f km/h\n",VxCruise,VxCruise*3.6) +text=text..string.format("VxClimb = %6.1f m/s = %5.1f km/h\n",VxClimb,VxClimb*3.6) +text=text..string.format("VxDescent = %6.1f m/s = %5.1f km/h\n",VxDescent,VxDescent*3.6) +text=text..string.format("VxHolding = %6.1f m/s = %5.1f km/h\n",VxHolding,VxHolding*3.6) +text=text..string.format("VxFinal = %6.1f m/s = %5.1f km/h\n",VxFinal,VxFinal*3.6) +text=text..string.format("VyClimb = %6.1f m/s\n",VyClimb) +text=text..string.format("\nDistances:\n") +text=text..string.format("d_climb = %6.1f km\n",d_climb/1000) +text=text..string.format("d_cruise = %6.1f km\n",d_cruise/1000) +text=text..string.format("d_descent = %6.1f km\n",d_descent/1000) +text=text..string.format("d_holding = %6.1f km\n",d_holding/1000) +text=text..string.format("d_total = %6.1f km\n",d_total/1000) +text=text..string.format("\nHeights:\n") +text=text..string.format("H_departure = %6.1f m ASL\n",H_departure) +text=text..string.format("H_destination = %6.1f m ASL\n",H_destination) +text=text..string.format("H_holding = %6.1f m ASL\n",H_holding) +text=text..string.format("h_climb = %6.1f m\n",h_climb) +text=text..string.format("h_descent = %6.1f m\n",h_descent) +text=text..string.format("h_holding = %6.1f m\n",h_holding) +text=text..string.format("delta H = %6.1f m\n",deltaH) +text=text..string.format("FLmin = %6.1f m ASL = FL%03d\n",FLmin,FLmin/RAT.unit.FL2m) +text=text..string.format("FLcruise = %6.1f m ASL = FL%03d\n",FLcruise,FLcruise/RAT.unit.FL2m) +text=text..string.format("FLmax = %6.1f m ASL = FL%03d\n",FLmax,FLmax/RAT.unit.FL2m) +text=text..string.format("\nAngles:\n") +text=text..string.format("Alpha climb = %6.2f Deg\n",math.deg(AlphaClimb)) +text=text..string.format("Alpha descent = %6.2f Deg\n",math.deg(AlphaDescent)) +text=text..string.format("Phi (slope) = %6.2f Deg\n",math.deg(phi)) +text=text..string.format("Phi climb = %6.2f Deg\n",math.deg(phi_climb)) +text=text..string.format("Phi descent = %6.2f Deg\n",math.deg(phi_descent)) +if self.Debug then +local h_climb_max=FLmax-H_departure +local h_descent_max=FLmax-Hh_holding +local d_climb_max=h_climb_max/math.tan(AlphaClimb) +local d_descent_max=h_descent_max/math.tan(AlphaDescent) +local d_cruise_max=d_total-d_climb_max-d_descent_max +text=text..string.format("Heading = %6.1f Deg\n",heading) +text=text..string.format("\nSSA triangle:\n") +text=text..string.format("D_total = %6.1f km\n",D_total/1000) +text=text..string.format("gamma = %6.1f Deg\n",math.deg(gamma)) +text=text..string.format("a = %6.1f m\n",a) +text=text..string.format("b = %6.1f m\n",b) +text=text..string.format("hphi_max = %6.1f m\n",hphi_max) +text=text..string.format("hphi_max2 = %6.1f m\n",hphi_max2) +text=text..string.format("h_max1 = %6.1f m\n",h_max1) +text=text..string.format("h_max2 = %6.1f m\n",h_max2) +text=text..string.format("h_max = %6.1f m\n",h_max) +text=text..string.format("\nMax heights and distances:\n") +text=text..string.format("d_climb_max = %6.1f km\n",d_climb_max/1000) +text=text..string.format("d_cruise_max = %6.1f km\n",d_cruise_max/1000) +text=text..string.format("d_descent_max = %6.1f km\n",d_descent_max/1000) +text=text..string.format("h_climb_max = %6.1f m\n",h_climb_max) +text=text..string.format("h_descent_max = %6.1f m\n",h_descent_max) +end +text=text..string.format("******************************************************\n") +self:T2(RAT.id..text) +if d_cruise<0 then +d_cruise=100 +end +local wp={} +local c={} +local wpholding=nil +local wpfinal=nil +c[#c+1]=Pdeparture +wp[#wp+1]=self:_Waypoint(#wp+1,"Departure",takeoff,c[#wp+1],VxClimb,H_departure,departure) +self.waypointdescriptions[#wp]="Departure" +self.waypointstatus[#wp]=RAT.status.Departure +if takeoff==RAT.wp.air then +if d_climb<5000 or d_cruise<5000 then +d_cruise=d_cruise+d_climb +else +c[#c+1]=c[#c]:Translate(d_climb,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +self.waypointdescriptions[#wp]="Begin of Cruise" +self.waypointstatus[#wp]=RAT.status.Cruise +end +else +c[#c+1]=c[#c]:Translate(d_climb/2,heading) +c[#c+1]=c[#c]:Translate(d_climb/2,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"Climb",RAT.wp.climb,c[#wp+1],VxClimb,H_departure+(FLcruise-H_departure)/2) +self.waypointdescriptions[#wp]="Climb" +self.waypointstatus[#wp]=RAT.status.Climb +wp[#wp+1]=self:_Waypoint(#wp+1,"Begin of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +self.waypointdescriptions[#wp]="Begin of Cruise" +self.waypointstatus[#wp]=RAT.status.Cruise +end +if self.returnzone then +c[#c+1]=Preturn +wp[#wp+1]=self:_Waypoint(#wp+1,"Return Zone",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +self.waypointdescriptions[#wp]="Return Zone" +self.waypointstatus[#wp]=RAT.status.Uturn +end +if landing==RAT.wp.air then +c[#c+1]=Pdestination +wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",RAT.wp.finalwp,c[#wp+1],VxCruise,FLcruise) +self.waypointdescriptions[#wp]="Final Destination" +self.waypointstatus[#wp]=RAT.status.Destination +elseif self.returnzone then +c[#c+1]=c[#c]:Translate(d_cruise/2,heading-180) +wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +self.waypointdescriptions[#wp]="End of Cruise" +self.waypointstatus[#wp]=RAT.status.Descent +else +c[#c+1]=c[#c]:Translate(d_cruise,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"End of Cruise",RAT.wp.cruise,c[#wp+1],VxCruise,FLcruise) +self.waypointdescriptions[#wp]="End of Cruise" +self.waypointstatus[#wp]=RAT.status.Descent +end +if landing==RAT.wp.landing then +if self.returnzone then +c[#c+1]=c[#c]:Translate(d_descent/2,heading-180) +wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2) +self.waypointdescriptions[#wp]="Descent" +self.waypointstatus[#wp]=RAT.status.DescentHolding +else +c[#c+1]=c[#c]:Translate(d_descent/2,heading) +wp[#wp+1]=self:_Waypoint(#wp+1,"Descent",RAT.wp.descent,c[#wp+1],VxDescent,FLcruise-(FLcruise-(h_holding+H_holding))/2) +self.waypointdescriptions[#wp]="Descent" +self.waypointstatus[#wp]=RAT.status.DescentHolding +end +end +if landing==RAT.wp.landing then +c[#c+1]=Pholding +wp[#wp+1]=self:_Waypoint(#wp+1,"Holding Point",RAT.wp.holding,c[#wp+1],VxHolding,H_holding+h_holding) +self.waypointdescriptions[#wp]="Holding Point" +self.waypointstatus[#wp]=RAT.status.Holding +wpholding=#wp +c[#c+1]=Pdestination +wp[#wp+1]=self:_Waypoint(#wp+1,"Final Destination",landing,c[#wp+1],VxFinal,H_destination,destination) +self.waypointdescriptions[#wp]="Final Destination" +self.waypointstatus[#wp]=RAT.status.Destination +end +wpfinal=#wp +local waypoints={} +for _,p in ipairs(wp)do +table.insert(waypoints,p) +end +self:_Routeinfo(waypoints,"Waypoint info in set_route:") +if self.returnzone then +return departure,destination_returnzone,waypoints,wpholding,wpfinal +else +return departure,destination,waypoints,wpholding,wpfinal +end +end +function RAT:_PickDeparture(takeoff) +local departures={} +if self.random_departure then +for _,_airport in pairs(self.airports)do +local airport=_airport +local name=airport:GetName() +if not self:_Excluded(name)then +if takeoff==RAT.wp.air then +table.insert(departures,airport:GetZone()) +else +local nspots=1 +if self.termtype~=nil then +nspots=airport:GetParkingSpotsNumber(self.termtype) +end +if nspots>0 then +table.insert(departures,airport) +end +end +end +end +else +for _,name in pairs(self.departure_ports)do +local dep=nil +if self:_AirportExists(name)then +if takeoff==RAT.wp.air then +dep=AIRBASE:FindByName(name):GetZone() +else +dep=AIRBASE:FindByName(name) +if self.termtype~=nil and dep~=nil then +local _dep=dep +local nspots=_dep:GetParkingSpotsNumber(self.termtype) +if nspots==0 then +dep=nil +end +end +end +elseif self:_ZoneExists(name)then +if takeoff==RAT.wp.air then +dep=ZONE:New(name) +else +self:E(RAT.id..string.format("ERROR! Takeoff is not in air. Cannot use %s as departure.",name)) +end +else +self:E(RAT.id..string.format("ERROR: No airport or zone found with name %s.",name)) +end +if dep then +table.insert(departures,dep) +end +end +end +self:T(RAT.id..string.format("Number of possible departures for %s= %d",self.alias,#departures)) +local departure=departures[math.random(#departures)] +local text +if departure and departure:GetName()then +if takeoff==RAT.wp.air then +text=string.format("%s: Chosen departure zone: %s",self.alias,departure:GetName()) +else +text=string.format("%s: Chosen departure airport: %s (ID %d)",self.alias,departure:GetName(),departure:GetID()) +end +self:T(RAT.id..text) +else +self:E(RAT.id..string.format("ERROR! No departure airport or zone found for %s.",self.alias)) +departure=nil +end +return departure +end +function RAT:_PickDestination(departure,q,minrange,maxrange,random,landing) +minrange=minrange or self.mindist +maxrange=maxrange or self.maxdist +local destinations={} +if random then +for _,_airport in pairs(self.airports)do +local airport=_airport +local name=airport:GetName() +if self:_IsFriendly(name)and not self:_Excluded(name)and name~=departure:GetName()then +local distance=q:Get2DDistance(airport:GetCoordinate()) +if distance>=minrange and distance<=maxrange then +if landing==RAT.wp.air then +table.insert(destinations,airport:GetZone()) +else +local nspot=1 +if self.termtype then +nspot=airport:GetParkingSpotsNumber(self.termtype) +end +if nspot>0 then +table.insert(destinations,airport) +end +end +end +end +end +else +for _,name in pairs(self.destination_ports)do +if name~=departure:GetName()then +local dest=nil +if self:_AirportExists(name)then +if landing==RAT.wp.air then +dest=AIRBASE:FindByName(name):GetZone() +else +dest=AIRBASE:FindByName(name) +local nspot=1 +if self.termtype then +nspot=dest:GetParkingSpotsNumber(self.termtype) +end +if nspot==0 then +dest=nil +end +end +elseif self:_ZoneExists(name)then +if landing==RAT.wp.air then +dest=ZONE:New(name) +else +self:E(RAT.id..string.format("ERROR! Landing is not in air. Cannot use zone %s as destination!",name)) +end +else +self:E(RAT.id..string.format("ERROR! No airport or zone found with name %s",name)) +end +if dest then +local distance=q:Get2DDistance(dest:GetCoordinate()) +if distance>=minrange and distance<=maxrange then +table.insert(destinations,dest) +else +local text=string.format("Destination %s is ouside range. Distance = %5.1f km, min = %5.1f km, max = %5.1f km.",name,distance,minrange,maxrange) +self:T(RAT.id..text) +end +end +end +end +end +self:T(RAT.id..string.format("Number of possible destinations = %s.",#destinations)) +if#destinations>0 then +local function compare(a,b) +local qa=q:Get2DDistance(a:GetCoordinate()) +local qb=q:Get2DDistance(b:GetCoordinate()) +return qa0 then +destination=destinations[math.random(#destinations)] +local text +if landing==RAT.wp.air then +text=string.format("%s: Chosen destination zone: %s.",self.alias,destination:GetName()) +else +text=string.format("%s Chosen destination airport: %s (ID %d).",self.alias,destination:GetName(),destination:GetID()) +end +self:T(RAT.id..text) +else +self:E(RAT.id.."ERROR! No destination airport or zone found.") +destination=nil +end +return destination +end +function RAT:_GetAirportsInZone(zone) +local airports={} +for _,airport in pairs(self.airports)do +local name=airport:GetName() +local coord=airport:GetCoordinate() +if zone:IsPointVec3InZone(coord)then +table.insert(airports,name) +end +end +return airports +end +function RAT:_Excluded(port) +for _,name in pairs(self.excluded_ports)do +if name==port then +return true +end +end +return false +end +function RAT:_IsFriendly(port) +for _,airport in pairs(self.airports)do +local name=airport:GetName() +if name==port then +return true +end +end +return false +end +function RAT:_GetAirportsOfMap() +local _coalition +for i=0,2 do +if i==0 then +_coalition=coalition.side.NEUTRAL +elseif i==1 then +_coalition=coalition.side.RED +elseif i==2 then +_coalition=coalition.side.BLUE +end +local ab=coalition.getAirbases(i) +for _,airbase in pairs(ab)do +local _id=airbase:getID() +local _p=airbase:getPosition().p +local _name=airbase:getName() +local _myab=AIRBASE:FindByName(_name) +if _myab then +table.insert(self.airports_map,_myab) +local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() +self:T(RAT.id..text) +else +self:E(RAT.id..string.format("WARNING: Airbase %s does not exsist as MOOSE object!",tostring(_name))) +end +end +end +end +function RAT:_GetAirportsOfCoalition() +for _,coalition in pairs(self.ctable)do +for _,_airport in pairs(self.airports_map)do +local airport=_airport +local category=airport:GetAirbaseCategory() +if airport:GetCoalition()==coalition then +local condition1=self.category==RAT.cat.plane and category==Airbase.Category.HELIPAD +local condition2=self.category==RAT.cat.plane and category==Airbase.Category.SHIP +if not(condition1 or condition2)then +table.insert(self.airports,airport) +end +end +end +end +if#self.airports==0 then +local text=string.format("No possible departure/destination airports found for RAT %s.",tostring(self.alias)) +MESSAGE:New(text,10):ToAll() +self:E(RAT.id..text) +end +end +function RAT:Status(message,forID) +if message==nil then +message=false +end +if forID==nil then +forID=false +end +local Tnow=timer.getTime() +local nalive=0 +for spawnindex,ratcraft in ipairs(self.ratcraft)do +local group=ratcraft.group +if group and group:IsAlive()then +nalive=nalive+1 +local prefix=self:_GetPrefixFromGroup(group) +local life=self:_GetLife(group) +local fuel=group:GetFuel()*100.0 +local airborne=group:InAir() +local coords=group:GetCoordinate() +local alt=coords.y +local departure=ratcraft.departure:GetName() +local destination=ratcraft.destination:GetName() +local type=self.aircraft.type +local status=ratcraft.status +local active=ratcraft.active +local Nunits=ratcraft.nunits +local N0units=group:GetInitialSize() +local Tg=0 +local Dg=0 +local dTlast=0 +local stationary=false +if airborne then +ratcraft["Tground"]=nil +ratcraft["Pground"]=nil +ratcraft["Uground"]=nil +ratcraft["Tlastcheck"]=nil +else +if ratcraft["Tground"]then +Tg=Tnow-ratcraft["Tground"] +Dg=coords:Get2DDistance(ratcraft["Pground"]) +dTlast=Tnow-ratcraft["Tlastcheck"] +if dTlast>self.Tinactive then +for _,_unit in pairs(group:GetUnits())do +if _unit and _unit:IsAlive()then +local unitname=_unit:GetName() +local unitcoord=_unit:GetCoordinate() +local Ug=unitcoord:Get2DDistance(ratcraft.Uground[unitname]) +self:T2(RAT.id..string.format("Unit %s travelled distance on ground %.1f m since %d seconds.",unitname,Ug,dTlast)) +if Ug<50 and active and status~=RAT.status.EventBirth then +stationary=true +end +ratcraft["Uground"][unitname]=unitcoord +end +end +ratcraft["Tlastcheck"]=Tnow +ratcraft["Pground"]=coords +end +else +ratcraft["Tground"]=Tnow +ratcraft["Tlastcheck"]=Tnow +ratcraft["Pground"]=coords +ratcraft["Uground"]={} +for _,_unit in pairs(group:GetUnits())do +local unitname=_unit:GetName() +ratcraft.Uground[unitname]=_unit:GetCoordinate() +end +end +end +local Pn=coords +local Dtravel=Pn:Get2DDistance(ratcraft["Pnow"]) +ratcraft["Pnow"]=Pn +ratcraft["Distance"]=ratcraft["Distance"]+Dtravel +local Ddestination=Pn:Get2DDistance(ratcraft.destination:GetCoordinate()) +if(forID and spawnindex==forID)or(not forID)then +local text=string.format("ID %i of flight %s",spawnindex,prefix) +if N0units>1 then +text=text..string.format(" (%d/%d)\n",Nunits,N0units) +else +text=text.."\n" +end +if self.commute then +text=text..string.format("%s commuting between %s and %s\n",type,departure,destination) +elseif self.continuejourney then +text=text..string.format("%s travelling from %s to %s (and continueing form there)\n",type,departure,destination) +else +text=text..string.format("%s travelling from %s to %s\n",type,departure,destination) +end +text=text..string.format("Status: %s",status) +if airborne then +text=text.." [airborne]\n" +else +text=text.." [on ground]\n" +end +text=text..string.format("Fuel = %3.0f %%\n",fuel) +text=text..string.format("Life = %3.0f %%\n",life) +text=text..string.format("FL%03d = %i m ASL\n",alt/RAT.unit.FL2m,alt) +text=text..string.format("Distance travelled = %6.1f km\n",ratcraft["Distance"]/1000) +text=text..string.format("Distance to destination = %6.1f km",Ddestination/1000) +if not airborne then +text=text..string.format("\nTime on ground = %6.0f seconds\n",Tg) +text=text..string.format("Position change = %8.1f m since %3.0f seconds.",Dg,dTlast) +end +self:T(RAT.id..text) +if message then +MESSAGE:New(text,20):ToAll() +end +end +if not airborne then +if stationary then +local text=string.format("Group %s is despawned after being %d seconds inaktive on ground.",self.alias,dTlast) +self:T(RAT.id..text) +self:_Despawn(group) +end +if life<10 and Dtravel<100 then +local text=string.format("Damaged group %s is despawned. Life = %3.0f",self.alias,life) +self:T(RAT.id..text) +self:_Despawn(group) +end +end +if ratcraft.despawnme then +local text=string.format("Flight %s will be despawned NOW!",self.alias) +self:T(RAT.id..text) +if(not self.norespawn)and(not self.respawn_after_takeoff)then +local idx=self:GetSpawnIndexFromGroup(group) +local coord=group:GetCoordinate() +self:_Respawn(idx,coord,0) +end +if self.despawnair then +self:_Despawn(group,0) +end +end +else +local text=string.format("Group does not exist in loop ratcraft status.") +self:T2(RAT.id..text) +end +end +local text=string.format("Alive groups of %s: %d, nalive=%d/%d",self.alias,self.alive,nalive,self.ngroups) +self:T(RAT.id..text) +MESSAGE:New(text,20):ToAllIf(message and not forID) +end +function RAT:_GetLife(group) +local life=0.0 +if group and group:IsAlive()then +local unit=group:GetUnit(1) +if unit then +life=unit:GetLife()/unit:GetLife0()*100 +else +self:T2(RAT.id.."ERROR! Unit does not exist in RAT_Getlife(). Returning zero.") +end +else +self:T2(RAT.id.."ERROR! Group does not exist in RAT_Getlife(). Returning zero.") +end +return life +end +function RAT:_SetStatus(group,status) +if group and group:IsAlive()then +local index=self:GetSpawnIndexFromGroup(group) +if self.ratcraft[index]then +self.ratcraft[index].status=status +local no1=status==RAT.status.Departure +local no2=status==RAT.status.EventBirthAir +local no3=status==RAT.status.Holding +local text=string.format("Flight %s: %s.",group:GetName(),status) +self:T(RAT.id..text) +if not(no1 or no2 or no3)then +MESSAGE:New(text,10):ToAllIf(self.reportstatus) +end +end +end +end +function RAT:GetStatus(group) +if group and group:IsAlive()then +local index=self:GetSpawnIndexFromGroup(group) +if self.ratcraft[index]then +return self.ratcraft[index].status +end +end +return"nonexistant" +end +function RAT:_OnBirth(EventData) +self:F3(EventData) +self:T3(RAT.id.."Captured event birth!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.alias then +local text="Event: Group "..SpawnGroup:GetName().." was born." +self:T(RAT.id..text) +local status="unknown in birth" +if SpawnGroup:InAir()then +status=RAT.status.EventBirthAir +elseif self.uncontrolled then +status=RAT.status.Uncontrolled +else +status=RAT.status.EventBirth +end +self:_SetStatus(SpawnGroup,status) +local i=self:GetSpawnIndexFromGroup(SpawnGroup) +local _departure=self.ratcraft[i].departure:GetName() +local _destination=self.ratcraft[i].destination:GetName() +local _nrespawn=self.ratcraft[i].nrespawn +local _takeoff=self.ratcraft[i].takeoff +local _landing=self.ratcraft[i].landing +local _livery=self.ratcraft[i].livery +local _airbase=AIRBASE:FindByName(_departure) +local onrunway=false +if _airbase then +if self.checkonrunway and _takeoff~=RAT.wp.runway and _takeoff~=RAT.wp.air then +onrunway=_airbase:CheckOnRunWay(SpawnGroup,self.onrunwayradius,false) +end +end +if onrunway then +local text=string.format("ERROR: RAT group of %s was spawned on runway. Group #%d will be despawned immediately!",self.alias,i) +MESSAGE:New(text,30):ToAllIf(self.Debug) +self:E(RAT.id..text) +if self.Debug then +SpawnGroup:FlareRed() +end +self:_Despawn(SpawnGroup) +if(self.Ndeparture_Airports>=2 or self.random_departure)and _nrespawn new state %s.",SpawnGroup:GetName(),currentstate,status) +self:T(RAT.id..text) +local idx=self:GetSpawnIndexFromGroup(SpawnGroup) +local coord=SpawnGroup:GetCoordinate() +self:_Respawn(idx,coord) +end +text="Event: Group "..SpawnGroup:GetName().." will be destroyed now." +self:T(RAT.id..text) +self:_Despawn(SpawnGroup) +end +end +end +else +self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnEngineShutdown().") +end +end +function RAT:_OnHit(EventData) +self:F3(EventData) +self:T(RAT.id..string.format("Captured event Hit by %s! Initiator %s. Target %s",self.alias,tostring(EventData.IniUnitName),tostring(EventData.TgtUnitName))) +local SpawnGroup=EventData.TgtGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix and EventPrefix==self.alias then +self:T(RAT.id..string.format("Event: Group %s was hit. Unit %s.",SpawnGroup:GetName(),tostring(EventData.TgtUnitName))) +local text=string.format("%s, unit %s was hit!",self.alias,EventData.TgtUnitName) +MESSAGE:New(text,10):ToAllIf(self.reportstatus or self.Debug) +end +end +end +function RAT:_OnDeadOrCrash(EventData) +self:F3(EventData) +self:T3(RAT.id.."Captured event DeadOrCrash!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.alias then +self.alive=self.alive-1 +local text=string.format("Event: Group %s crashed or died. Alive counter = %d.",SpawnGroup:GetName(),self.alive) +self:T(RAT.id..text) +if EventData.id==world.event.S_EVENT_CRASH then +self:_OnCrash(EventData) +elseif EventData.id==world.event.S_EVENT_DEAD then +self:_OnDead(EventData) +end +end +end +end +end +function RAT:_OnDead(EventData) +self:F3(EventData) +self:T3(RAT.id.."Captured event Dead!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix then +if EventPrefix==self.alias then +local text=string.format("Event: Group %s died. Unit %s.",SpawnGroup:GetName(),EventData.IniUnitName) +self:T(RAT.id..text) +local status=RAT.status.EventDead +self:_SetStatus(SpawnGroup,status) +end +end +else +self:T2(RAT.id.."ERROR: Group does not exist in RAT:_OnDead().") +end +end +function RAT:_OnCrash(EventData) +self:F3(EventData) +self:T3(RAT.id.."Captured event Crash!") +local SpawnGroup=EventData.IniGroup +if SpawnGroup then +local EventPrefix=self:_GetPrefixFromGroup(SpawnGroup) +if EventPrefix and EventPrefix==self.alias then +local _i=self:GetSpawnIndexFromGroup(SpawnGroup) +self.ratcraft[_i].nunits=self.ratcraft[_i].nunits-1 +local _n=self.ratcraft[_i].nunits +local _n0=SpawnGroup:GetInitialSize() +local text=string.format("Event: Group %s crashed. Unit %s. Units still alive %d of %d.",SpawnGroup:GetName(),EventData.IniUnitName,_n,_n0) +self:T(RAT.id..text) +local status=RAT.status.EventCrash +self:_SetStatus(SpawnGroup,status) +if _n==0 and self.respawn_after_crash and not self.norespawn then +local text=string.format("No units left of group %s. Group will be respawned now.",SpawnGroup:GetName()) +self:T(RAT.id..text) +local idx=self:GetSpawnIndexFromGroup(SpawnGroup) +local coord=SpawnGroup:GetCoordinate() +self:_Respawn(idx,coord) +end +end +else +if self.Debug then +self:E(RAT.id.."ERROR: Group does not exist in RAT:_OnCrash().") +end +end +end +function RAT:_Despawn(group,delay) +if group~=nil then +local index=self:GetSpawnIndexFromGroup(group) +if index~=nil then +self.ratcraft[index].group=nil +self.ratcraft[index]["status"]="Dead" +local despawndelay=0 +if delay then +despawndelay=delay +elseif self.respawn_delay then +despawndelay=self.respawn_delay +end +self:T(RAT.id..string.format("%s delayed despawn in %.1f seconds.",self.alias,despawndelay)) +SCHEDULER:New(nil,self._Destroy,{self,group},despawndelay) +if self.f10menu and self.SubMenuName~=nil then +self.Menu[self.SubMenuName]["groups"][index]:Remove() +end +end +end +end +function RAT:_Destroy(group) +self:F2(group) +local DCSGroup=group:GetDCSObject() +if DCSGroup and DCSGroup:isExist()then +local triggerdead=true +for _,DCSUnit in pairs(DCSGroup:getUnits())do +if DCSUnit then +if triggerdead then +self:_CreateEventDead(timer.getTime(),DCSUnit) +triggerdead=false +end +_DATABASE:DeleteUnit(DCSUnit:getName()) +end +end +DCSGroup:destroy() +DCSGroup=nil +end +return nil +end +function RAT:_CreateEventDead(EventTime,Initiator) +self:F({EventTime,Initiator}) +local Event={ +id=world.event.S_EVENT_DEAD, +time=EventTime, +initiator=Initiator, +} +world.onEvent(Event) +end +function RAT:_Waypoint(index,description,Type,Coord,Speed,Altitude,Airport) +local _Altitude=Altitude or Coord.y +local Hland=Coord:GetLandHeight() +local _Type=nil +local _Action=nil +local _alttype="RADIO" +if Type==RAT.wp.cold then +_Type="TakeOffParking" +_Action="From Parking Area" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.hot then +_Type="TakeOffParkingHot" +_Action="From Parking Area Hot" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.runway then +_Type="TakeOff" +_Action="From Parking Area" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.air then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.climb then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.cruise then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.descent then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.holding then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +elseif Type==RAT.wp.landing then +_Type="Land" +_Action="Landing" +_Altitude=10 +_alttype="RADIO" +elseif Type==RAT.wp.finalwp then +_Type="Turning Point" +_Action="Turning Point" +_alttype="BARO" +else +self:E(RAT.id.."ERROR: Unknown waypoint type in RAT:Waypoint() function!") +_Type="Turning Point" +_Action="Turning Point" +_alttype="RADIO" +end +local text=string.format("\n******************************************************\n") +text=text..string.format("Waypoint = %d\n",index) +text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) +text=text..string.format("Alias = %s\n",self.alias) +text=text..string.format("Type: %i - %s\n",Type,_Type) +text=text..string.format("Action: %s\n",_Action) +text=text..string.format("Coord: x = %6.1f km, y = %6.1f km, alt = %6.1f m\n",Coord.x/1000,Coord.z/1000,Coord.y) +text=text..string.format("Speed = %6.1f m/s = %6.1f km/h = %6.1f knots\n",Speed,Speed*3.6,Speed*1.94384) +text=text..string.format("Land = %6.1f m ASL\n",Hland) +text=text..string.format("Altitude = %6.1f m (%s)\n",_Altitude,_alttype) +if Airport then +if Type==RAT.wp.air then +text=text..string.format("Zone = %s\n",Airport:GetName()) +else +text=text..string.format("Airport = %s\n",Airport:GetName()) +end +else +text=text..string.format("No airport/zone specified\n") +end +text=text.."******************************************************\n" +self:T2(RAT.id..text) +local RoutePoint={} +RoutePoint.x=Coord.x +RoutePoint.y=Coord.z +RoutePoint.alt=_Altitude +RoutePoint.alt_type=_alttype +RoutePoint.type=_Type +RoutePoint.action=_Action +RoutePoint.speed=Speed +RoutePoint.speed_locked=true +RoutePoint.ETA=nil +RoutePoint.ETA_locked=false +RoutePoint.name=description +if(Airport~=nil)and(Type~=RAT.wp.air)then +local AirbaseID=Airport:GetID() +local AirbaseCategory=Airport:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.SHIP then +RoutePoint.linkUnit=AirbaseID +RoutePoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.HELIPAD then +RoutePoint.linkUnit=AirbaseID +RoutePoint.helipadId=AirbaseID +elseif AirbaseCategory==Airbase.Category.AIRDROME then +RoutePoint.airdromeId=AirbaseID +else +self:T(RAT.id.."Unknown Airport category in _Waypoint()!") +end +end +RoutePoint.properties={ +["vnav"]=1, +["scale"]=0, +["angle"]=0, +["vangle"]=0, +["steer"]=2, +} +local TaskCombo={} +local TaskHolding=self:_TaskHolding({x=Coord.x,y=Coord.z},Altitude,Speed,self:_Randomize(90,0.9)) +local TaskWaypoint=self:_TaskFunction("RAT._WaypointFunction",self,index) +RoutePoint.task={} +RoutePoint.task.id="ComboTask" +RoutePoint.task.params={} +TaskCombo[#TaskCombo+1]=TaskWaypoint +if Type==RAT.wp.holding then +TaskCombo[#TaskCombo+1]=TaskHolding +end +RoutePoint.task.params.tasks=TaskCombo +return RoutePoint +end +function RAT:_Routeinfo(waypoints,comment) +local text=string.format("\n******************************************************\n") +text=text..string.format("Template = %s\n",self.SpawnTemplatePrefix) +if comment then +text=text..comment.."\n" +end +text=text..string.format("Number of waypoints = %i\n",#waypoints) +for i=1,#waypoints do +local p=waypoints[i] +text=text..string.format("WP #%i: x = %6.1f km, y = %6.1f km, alt = %6.1f m %s\n",i-1,p.x/1000,p.y/1000,p.alt,self.waypointdescriptions[i]) +end +local total=0.0 +for i=1,#waypoints-1 do +local point1=waypoints[i] +local point2=waypoints[i+1] +local x1=point1.x +local y1=point1.y +local x2=point2.x +local y2=point2.y +local d=math.sqrt((x1-x2)^2+(y1-y2)^2) +local heading=self:_Course(point1,point2) +total=total+d +text=text..string.format("Distance from WP %i-->%i = %6.1f km. Heading = %03d : %s - %s\n",i-1,i,d/1000,heading,self.waypointdescriptions[i],self.waypointdescriptions[i+1]) +end +text=text..string.format("Total distance = %6.1f km\n",total/1000) +text=text..string.format("******************************************************\n") +self:T2(RAT.id..text) +return total +end +function RAT:_TaskHolding(P1,Altitude,Speed,Duration) +local dx=3000 +local dy=0 +if self.category==RAT.cat.heli then +dx=200 +dy=0 +end +local P2={} +P2.x=P1.x+dx +P2.y=P1.y+dy +local Task={ +id='Orbit', +params={ +pattern=AI.Task.OrbitPattern.RACE_TRACK, +point=P1, +point2=P2, +speed=Speed, +altitude=Altitude +} +} +local DCSTask={} +DCSTask.id="ControlledTask" +DCSTask.params={} +DCSTask.params.task=Task +if self.ATCswitch then +local userflagname=string.format("%s#%03d",self.alias,self.SpawnIndex+1) +local maxholdingduration=60*120 +DCSTask.params.stopCondition={userFlag=userflagname,userFlagValue=1,duration=maxholdingduration} +else +DCSTask.params.stopCondition={duration=Duration} +end +return DCSTask +end +function RAT._WaypointFunction(group,rat,wp) +local Tnow=timer.getTime() +local sdx=rat:GetSpawnIndexFromGroup(group) +local departure=rat.ratcraft[sdx].departure:GetName() +local destination=rat.ratcraft[sdx].destination:GetName() +local landing=rat.ratcraft[sdx].landing +local WPholding=rat.ratcraft[sdx].wpholding +local WPfinal=rat.ratcraft[sdx].wpfinal +local text +text=string.format("Flight %s passing waypoint #%d %s.",group:GetName(),wp,rat.waypointdescriptions[wp]) +BASE.T(rat,RAT.id..text) +local status=rat.waypointstatus[wp] +rat:_SetStatus(group,status) +if wp==WPholding then +text=string.format("Flight %s to %s ATC: Holding and awaiting landing clearance.",group:GetName(),destination) +MESSAGE:New(text,10):ToAllIf(rat.reportstatus) +if rat.ATCswitch then +if rat.f10menu then +MENU_MISSION_COMMAND:New("Clear for landing",rat.Menu[rat.SubMenuName].groups[sdx],rat.ClearForLanding,rat,group:GetName()) +end +rat._ATCRegisterFlight(rat,group:GetName(),Tnow) +end +end +if wp==WPfinal then +text=string.format("Flight %s arrived at final destination %s.",group:GetName(),destination) +MESSAGE:New(text,10):ToAllIf(rat.reportstatus) +BASE.T(rat,RAT.id..text) +if landing==RAT.wp.air then +text=string.format("Activating despawn switch for flight %s! Group will be detroyed soon.",group:GetName()) +MESSAGE:New(text,10):ToAllIf(rat.Debug) +BASE.T(rat,RAT.id..text) +rat.ratcraft[sdx].despawnme=true +end +end +end +function RAT:_TaskFunction(FunctionString,...) +self:F2({FunctionString,arg}) +local DCSTask +local ArgumentKey +local templatename=self.templategroup:GetName() +local groupname=self:_AnticipatedGroupName() +local DCSScript={} +DCSScript[#DCSScript+1]="local MissionControllable = GROUP:FindByName(\""..groupname.."\") " +DCSScript[#DCSScript+1]="local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") " +if arg and arg.n>0 then +ArgumentKey='_'..tostring(arg):match("table: (.*)") +self.templategroup:SetState(self.templategroup,ArgumentKey,arg) +DCSScript[#DCSScript+1]="local Arguments = RATtemplateControllable:GetState(RATtemplateControllable, '"..ArgumentKey.."' ) " +DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable, unpack( Arguments ) )" +else +DCSScript[#DCSScript+1]=FunctionString.."( MissionControllable )" +end +DCSTask=self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript))) +return DCSTask +end +function RAT:_AnticipatedGroupName(index) +local index=index or self.SpawnIndex+1 +return string.format("%s#%03d",self.alias,index) +end +function RAT:_ActivateUncontrolled() +self:F() +local idx={} +local rat={} +local nactive=0 +for spawnindex,ratcraft in pairs(self.ratcraft)do +local group=ratcraft.group +if group and group:IsAlive()then +local text=string.format("Uncontrolled: Group = %s (spawnindex = %d), active = %s.",ratcraft.group:GetName(),spawnindex,tostring(ratcraft.active)) +self:T2(RAT.id..text) +if ratcraft.active then +nactive=nactive+1 +else +table.insert(idx,spawnindex) +end +end +end +local text=string.format("Uncontrolled: Ninactive = %d, Nactive = %d (of max %d).",#idx,nactive,self.activate_max) +self:T(RAT.id..text) +if#idx>0 and nactive=1 then +for i=1,nunits do +table.insert(parkingspots,spots[1].Coordinate) +table.insert(parkingindex,spots[1].TerminalID) +end +PointVec3=spots[1].Coordinate +else +_notenough=true +end +elseif spawnonairport then +if nfree>=nunits then +for i=1,nunits do +table.insert(parkingspots,spots[i].Coordinate) +table.insert(parkingindex,spots[i].TerminalID) +end +else +_notenough=true +end +end +if _notenough then +if self.respawn_inair and not self.SpawnUnControlled then +self:E(RAT.id..string.format("WARNING: Group %s has no parking spots at %s ==> air start!",self.SpawnTemplatePrefix,departure:GetName())) +spawnonground=false +spawnonship=false +spawnonfarp=false +spawnonrunway=false +waypoints[1].type=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] +waypoints[1].action=GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] +PointVec3.x=PointVec3.x+math.random(-1500,1500) +PointVec3.z=PointVec3.z+math.random(-1500,1500) +if self.category==RAT.cat.heli then +PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) +else +PointVec3.y=PointVec3:GetLandHeight()+math.random(500,3000) +end +else +self:E(RAT.id..string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!",self.SpawnTemplatePrefix,departure:GetName())) +return nil +end +end +else +end +for UnitID=1,nunits do +local UnitTemplate=SpawnTemplate.units[UnitID] +local SX=UnitTemplate.x +local SY=UnitTemplate.y +local BX=SpawnTemplate.route.points[1].x +local BY=SpawnTemplate.route.points[1].y +local TX=PointVec3.x+(SX-BX) +local TY=PointVec3.z+(SY-BY) +if spawnonground then +if spawnonship or spawnonfarp or spawnonrunway or automatic then +self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.",self.alias,departure:GetName())) +SpawnTemplate.units[UnitID].x=PointVec3.x +SpawnTemplate.units[UnitID].y=PointVec3.z +SpawnTemplate.units[UnitID].alt=PointVec3.y +else +self:T(RAT.id..string.format("RAT group %s spawning at airbase %s on parking spot id %d",self.alias,departure:GetName(),parkingindex[UnitID])) +SpawnTemplate.units[UnitID].x=parkingspots[UnitID].x +SpawnTemplate.units[UnitID].y=parkingspots[UnitID].z +SpawnTemplate.units[UnitID].alt=parkingspots[UnitID].y +end +else +self:T(RAT.id..string.format("RAT group %s spawning in air at %s.",self.alias,departure:GetName())) +SpawnTemplate.units[UnitID].x=TX +SpawnTemplate.units[UnitID].y=TY +SpawnTemplate.units[UnitID].alt=PointVec3.y +end +if self.Debug then +local unitspawn=COORDINATE:New(SpawnTemplate.units[UnitID].x,SpawnTemplate.units[UnitID].alt,SpawnTemplate.units[UnitID].y) +unitspawn:MarkToAll(string.format("RAT %s Spawnplace unit #%d",self.alias,UnitID)) +end +UnitTemplate.parking=nil +UnitTemplate.parking_id=nil +if parkingindex[UnitID]and not automatic then +UnitTemplate.parking=parkingindex[UnitID] +end +self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking = %s",self.alias,UnitID,tostring(UnitTemplate.parking))) +self:T2(RAT.id..string.format("RAT group %s unit number %d: Parking ID = %s",self.alias,UnitID,tostring(UnitTemplate.parking_id))) +SpawnTemplate.units[UnitID].heading=heading +SpawnTemplate.units[UnitID].psi=-heading +if livery then +SpawnTemplate.units[UnitID].livery_id=livery +end +if self.actype then +SpawnTemplate.units[UnitID]["type"]=self.actype +end +SpawnTemplate.units[UnitID]["skill"]=self.skill +if self.onboardnum then +SpawnTemplate.units[UnitID]["onboard_num"]=string.format("%s%d%02d",self.onboardnum,(self.SpawnIndex-1)%10,(self.onboardnum0-1)+UnitID) +end +SpawnTemplate.CoalitionID=self.coalition +if self.country then +SpawnTemplate.CountryID=self.country +end +end +for i,wp in ipairs(waypoints)do +SpawnTemplate.route.points[i]=wp +end +SpawnTemplate.x=PointVec3.x +SpawnTemplate.y=PointVec3.z +if self.radio then +SpawnTemplate.communication=self.radio +end +if self.frequency then +SpawnTemplate.frequency=self.frequency +end +if self.modulation then +SpawnTemplate.modulation=self.modulation +end +self:T(SpawnTemplate) +end +end +return true +end +function RAT:_ATCInit(airports_map) +if not RAT.ATC.init then +local text +text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay +BASE:T(RAT.id..text) +RAT.ATC.init=true +for _,ap in pairs(airports_map)do +local name=ap:GetName() +RAT.ATC.airport[name]={} +RAT.ATC.airport[name].queue={} +RAT.ATC.airport[name].busy=false +RAT.ATC.airport[name].onfinal={} +RAT.ATC.airport[name].Nonfinal=0 +RAT.ATC.airport[name].traffic=0 +RAT.ATC.airport[name].Tlastclearance=nil +end +SCHEDULER:New(nil,RAT._ATCCheck,{self},5,15) +SCHEDULER:New(nil,RAT._ATCStatus,{self},5,60) +RAT.ATC.T0=timer.getTime() +end +end +function RAT:_ATCAddFlight(name,dest) +BASE:T(string.format("%sATC %s: Adding flight %s with destination %s.",RAT.id,dest,name,dest)) +RAT.ATC.flight[name]={} +RAT.ATC.flight[name].destination=dest +RAT.ATC.flight[name].Tarrive=-1 +RAT.ATC.flight[name].holding=-1 +RAT.ATC.flight[name].Tonfinal=-1 +end +function RAT:_ATCDelFlight(t,entry) +for k,_ in pairs(t)do +if k==entry then +t[entry]=nil +end +end +end +function RAT:_ATCRegisterFlight(name,time) +BASE:T(RAT.id.."Flight "..name.." registered at ATC for landing clearance.") +RAT.ATC.flight[name].Tarrive=time +RAT.ATC.flight[name].holding=0 +end +function RAT:_ATCStatus() +local Tnow=timer.getTime() +for name,_ in pairs(RAT.ATC.flight)do +local hold=RAT.ATC.flight[name].holding +local dest=RAT.ATC.flight[name].destination +if hold>=0 then +local busy="Runway state is unknown" +if RAT.ATC.airport[dest].Nonfinal>0 then +busy="Runway is occupied by "..RAT.ATC.airport[dest].Nonfinal +else +busy="Runway is currently clear" +end +local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.",dest,name,hold/60,hold%60,busy) +BASE:T(RAT.id..text) +elseif hold==RAT.ATC.onfinal then +local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal +local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.",dest,name,Tfinal/60,Tfinal%60) +BASE:T(RAT.id..text) +elseif hold==RAT.ATC.unregistered then +else +BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().") +end +end +end +function RAT:_ATCCheck() +RAT:_ATCQueue() +local Tnow=timer.getTime() +for name,_ in pairs(RAT.ATC.airport)do +for qID,flight in ipairs(RAT.ATC.airport[name].queue)do +local nqueue=#RAT.ATC.airport[name].queue +local landing1 +if RAT.ATC.airport[name].Tlastclearance then +landing1=(Tnow-RAT.ATC.airport[name].Tlastclearance>RAT.ATC.delay)and RAT.ATC.airport[name].Nonfinal=0 then +RAT.ATC.flight[name].holding=Tnow-RAT.ATC.flight[name].Tarrive +end +local hold=RAT.ATC.flight[name].holding +local dest=RAT.ATC.flight[name].destination +if hold>=0 and airport==dest then +_queue[#_queue+1]={name,hold} +end +end +local function compare(a,b) +return a[2]>b[2] +end +table.sort(_queue,compare) +RAT.ATC.airport[airport].queue={} +for k,v in ipairs(_queue)do +table.insert(RAT.ATC.airport[airport].queue,v[1]) +end +end +end +RATMANAGER={ +ClassName="RATMANAGER", +Debug=false, +rat={}, +name={}, +alive={}, +min={}, +nrat=0, +ntot=nil, +Tcheck=60, +dTspawn=1.0, +manager=nil, +managerid=nil, +} +RATMANAGER.id="RATMANAGER | " +function RATMANAGER:New(ntot) +local self=BASE:Inherit(self,BASE:New()) +self.ntot=ntot or 1 +self:E(RATMANAGER.id..string.format("Creating manager for %d groups.",ntot)) +return self +end +function RATMANAGER:Add(ratobject,min) +ratobject.norespawn=true +ratobject.f10menu=false +self.nrat=self.nrat+1 +self.rat[self.nrat]=ratobject +self.alive[self.nrat]=0 +self.name[self.nrat]=ratobject.alias +self.min[self.nrat]=min or 1 +self:T(RATMANAGER.id..string.format("Adding ratobject %s with min flights = %d",self.name[self.nrat],self.min[self.nrat])) +ratobject:Spawn(0) +return self +end +function RATMANAGER:Start(delay) +local delay=delay or 5 +local text=string.format(RATMANAGER.id.."RAT manager will be started in %d seconds.\n",delay) +text=text..string.format("Managed groups:\n") +for i=1,self.nrat do +text=text..string.format("- %s with min groups %d\n",self.name[i],self.min[i]) +end +text=text..string.format("Number of constantly alive groups %d",self.ntot) +self:E(text) +SCHEDULER:New(nil,self._Start,{self},delay) +return self +end +function RATMANAGER:_Start() +local n=0 +for i=1,self.nrat do +n=n+self.min[i] +end +self.ntot=math.max(self.ntot,n) +local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive) +local time=0.0 +for i=1,self.nrat do +for j=1,N[i]do +time=time+self.dTspawn +SCHEDULER:New(nil,RAT._SpawnWithRoute,{self.rat[i]},time) +end +end +for i=1,self.nrat do +if self.rat[i].uncontrolled and self.rat[i].activate_uncontrolled then +local Tactivate=math.max(time+1,self.rat[i].activate_delay) +SCHEDULER:New(self.rat[i],self.rat[i]._ActivateUncontrolled,{self.rat[i]},Tactivate,self.rat[i].activate_delta,self.rat[i].activate_frand) +end +end +local TstartManager=math.max(time+1,self.Tcheck) +self.manager,self.managerid=SCHEDULER:New(self,self._Manage,{self},TstartManager,self.Tcheck) +local text=string.format(RATMANAGER.id.."Starting RAT manager with scheduler ID %s in %d seconds. Repeat interval %d seconds.",self.managerid,TstartManager,self.Tcheck) +self:E(text) +return self +end +function RATMANAGER:Stop(delay) +delay=delay or 1 +self:E(string.format(RATMANAGER.id.."Manager will be stopped in %d seconds.",delay)) +SCHEDULER:New(nil,self._Stop,{self},delay) +return self +end +function RATMANAGER:_Stop() +self:E(string.format(RATMANAGER.id.."Stopping manager with scheduler ID %s.",self.managerid)) +self.manager:Stop(self.managerid) +return self +end +function RATMANAGER:SetTcheck(dt) +self.Tcheck=dt or 60 +return self +end +function RATMANAGER:SetTspawn(dt) +self.dTspawn=dt or 1.0 +return self +end +function RATMANAGER:_Manage() +local ntot=self:_Count() +local text=string.format("Number of alive groups %d. New groups to be spawned %d.",ntot,self.ntot-ntot) +self:T(RATMANAGER.id..text) +local N=self:_RollDice(self.nrat,self.ntot,self.min,self.alive) +local time=0.0 +for i=1,self.nrat do +for j=1,N[i]do +time=time+self.dTspawn +SCHEDULER:New(nil,RAT._SpawnWithRoute,{self.rat[i]},time) +end +end +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",self.name[i],n) +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 +N[#N+1]=0 +M[#M+1]=math.max(alive[i],min[i]) +P[#P+1]=math.max(min[i]-alive[i],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] +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, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d",j,alive[j],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, min=%d, mini=%d, maxi=%d, add=%d\n",self.name[i],i,alive[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, +eventmoose=true, +trackbombs=true, +trackrockets=true, +trackmissiles=true, +defaultsmokebomb=true, +autosave=false, +instructorfreq=nil, +instructor=nil, +rangecontrolfreq=nil, +rangecontrol=nil, +soundpath="Range Soundfiles/" +} +RANGE.Defaults={ +goodhitrange=25, +strafemaxalt=914, +dtBombtrack=0.005, +Tmsg=30, +ndisplayresult=10, +rangeradius=5000, +TdelaySmoke=3.0, +boxlength=3000, +boxwidth=300, +goodpass=20, +goodhitrange=25, +foulline=610, +} +RANGE.TargetType={ +UNIT="Unit", +STATIC="Static", +COORD="Coordinate", +} +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.3.0" +function RANGE:New(rangename) +BASE:F({rangename=rangename}) +local self=BASE:Inherit(self,FSM:New()) +self.rangename=rangename or"Practice Range" +self.id=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.id..text) +self:SetDefaultPlayerSmokeBomb() +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","Impact","*") +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.id..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.id..text) +if self.eventmoose then +self:T(self.id.."Events are handled by MOOSE.") +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.Hit) +self:HandleEvent(EVENTS.Shot) +else +self:T(self.id.."Events are handled directly by DCS.") +world.addEventHandler(self) +end +for _,_target in pairs(self.bombingTargets)do +local _static=_target.type==RANGE.TargetType.STATIC +if _target.move and _static==false and _target.speed>1 then +local unit=_target.target +_target.target:PatrolZones({self.rangezone},_target.speed*0.75,"Off road") +end +end +if self.rangecontrolfreq then +self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq,nil,self.rangename) +self.rangecontrol.schedonce=true +self.rangecontrol:SetDigit(0,RANGE.Sound.RC0.filename,RANGE.Sound.RC0.duration,self.soundpath) +self.rangecontrol:SetDigit(1,RANGE.Sound.RC1.filename,RANGE.Sound.RC1.duration,self.soundpath) +self.rangecontrol:SetDigit(2,RANGE.Sound.RC2.filename,RANGE.Sound.RC2.duration,self.soundpath) +self.rangecontrol:SetDigit(3,RANGE.Sound.RC3.filename,RANGE.Sound.RC3.duration,self.soundpath) +self.rangecontrol:SetDigit(4,RANGE.Sound.RC4.filename,RANGE.Sound.RC4.duration,self.soundpath) +self.rangecontrol:SetDigit(5,RANGE.Sound.RC5.filename,RANGE.Sound.RC5.duration,self.soundpath) +self.rangecontrol:SetDigit(6,RANGE.Sound.RC6.filename,RANGE.Sound.RC6.duration,self.soundpath) +self.rangecontrol:SetDigit(7,RANGE.Sound.RC7.filename,RANGE.Sound.RC7.duration,self.soundpath) +self.rangecontrol:SetDigit(8,RANGE.Sound.RC8.filename,RANGE.Sound.RC8.duration,self.soundpath) +self.rangecontrol:SetDigit(9,RANGE.Sound.RC9.filename,RANGE.Sound.RC9.duration,self.soundpath) +self.rangecontrol:SetSenderCoordinate(self.location) +self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) +self.rangecontrol:Start(1,0.1) +if self.instructorfreq then +self.instructor=RADIOQUEUE:New(self.instructorfreq,nil,self.rangename) +self.instructor.schedonce=true +self.instructor:SetDigit(0,RANGE.Sound.IR0.filename,RANGE.Sound.IR0.duration,self.soundpath) +self.instructor:SetDigit(1,RANGE.Sound.IR1.filename,RANGE.Sound.IR1.duration,self.soundpath) +self.instructor:SetDigit(2,RANGE.Sound.IR2.filename,RANGE.Sound.IR2.duration,self.soundpath) +self.instructor:SetDigit(3,RANGE.Sound.IR3.filename,RANGE.Sound.IR3.duration,self.soundpath) +self.instructor:SetDigit(4,RANGE.Sound.IR4.filename,RANGE.Sound.IR4.duration,self.soundpath) +self.instructor:SetDigit(5,RANGE.Sound.IR5.filename,RANGE.Sound.IR5.duration,self.soundpath) +self.instructor:SetDigit(6,RANGE.Sound.IR6.filename,RANGE.Sound.IR6.duration,self.soundpath) +self.instructor:SetDigit(7,RANGE.Sound.IR7.filename,RANGE.Sound.IR7.duration,self.soundpath) +self.instructor:SetDigit(8,RANGE.Sound.IR8.filename,RANGE.Sound.IR8.duration,self.soundpath) +self.instructor:SetDigit(9,RANGE.Sound.IR9.filename,RANGE.Sound.IR9.duration,self.soundpath) +self.instructor:SetSenderCoordinate(self.location) +self.instructor:SetSenderUnitName(self.instructorrelayname) +self.instructor:Start(1,0.1) +end +end +if self.autosave then +self:Load() +end +if self.Debug then +self:_MarkTargetsOnMap() +self:_SmokeBombTargets() +self:_SmokeStrafeTargets() +self:_SmokeStrafeTargetBoxes() +self.rangezone:SmokeZone(SMOKECOLOR.White) +end +self:__Status(-60) +end +function RANGE:SetMaxStrafeAlt(maxalt) +self.strafemaxalt=maxalt or RANGE.Defaults.strafemaxalt +return self +end +function RANGE:SetBombtrackTimestep(dt) +self.dtBombtrack=dt or RANGE.Defaults.dtBombtrack +return self +end +function RANGE:SetMessageTimeDuration(time) +self.Tmsg=time or RANGE.Defaults.Tmsg +return self +end +function RANGE:SetAutosaveOn() +self.autosave=true +return self +end +function RANGE:SetAutosaveOff() +self.autosave=false +return self +end +function RANGE:SetMessageToExaminer(examinergroupname,exclusively) +self.examinergroupname=examinergroupname +self.examinerexclusive=exclusively +return self +end +function RANGE:SetDisplayedMaxPlayerResults(nmax) +self.ndisplayresult=nmax or RANGE.Defaults.ndisplayresult +return self +end +function RANGE:SetRangeRadius(radius) +self.rangeradius=radius*1000 or RANGE.Defaults.rangeradius +return self +end +function RANGE:SetDefaultPlayerSmokeBomb(switch) +if switch==true or switch==nil then +self.defaultsmokebomb=true +else +self.defaultsmokebomb=false +end +return self +end +function RANGE:SetBombtrackThreshold(distance) +self.BombtrackThreshold=(distance or 25)*1000 +return self +end +function RANGE:SetRangeLocation(coordinate) +self.location=coordinate +return self +end +function RANGE:SetRangeZone(zone) +self.rangezone=zone +return self +end +function RANGE:SetBombTargetSmokeColor(colorid) +self.BombSmokeColor=colorid or SMOKECOLOR.Red +return self +end +function RANGE:SetScoreBombDistance(distance) +self.scorebombdistance=distance or 1000 +return self +end +function RANGE:SetStrafeTargetSmokeColor(colorid) +self.StrafeSmokeColor=colorid or SMOKECOLOR.Green +return self +end +function RANGE:SetStrafePitSmokeColor(colorid) +self.StrafePitSmokeColor=colorid or SMOKECOLOR.White +return self +end +function RANGE:SetSmokeTimeDelay(delay) +self.TdelaySmoke=delay or RANGE.Defaults.TdelaySmoke +return self +end +function RANGE:DebugON() +self.Debug=true +return self +end +function RANGE:DebugOFF() +self.Debug=false +return self +end +function RANGE:SetMessagesOFF() +self.messages=false +return self +end +function RANGE:SetMessagesON() +self.messages=true +return self +end +function RANGE:TrackBombsON() +self.trackbombs=true +return self +end +function RANGE:TrackBombsOFF() +self.trackbombs=false +return self +end +function RANGE:TrackRocketsON() +self.trackrockets=true +return self +end +function RANGE:TrackRocketsOFF() +self.trackrockets=false +return self +end +function RANGE:TrackMissilesON() +self.trackmissiles=true +return self +end +function RANGE:TrackMissilesOFF() +self.trackmissiles=false +return self +end +function RANGE:SetRangeControl(frequency,relayunitname) +self.rangecontrolfreq=frequency or 256 +self.rangecontrolrelayname=relayunitname +return self +end +function RANGE:SetInstructorRadio(frequency,relayunitname) +self.instructorfreq=frequency or 305 +self.instructorrelayname=relayunitname +return self +end +function RANGE:SetSoundfilesPath(path) +self.soundpath=tostring(path or"Range Soundfiles/") +self:I(self.id..string.format("Setting sound files path to %s",self.soundpath)) +return self +end +function RANGE:AddStrafePit(targetnames,boxlength,boxwidth,heading,inverseheading,goodpass,foulline) +self:F({targetnames=targetnames,boxlength=boxlength,boxwidth=boxwidth,heading=heading,inverseheading=inverseheading,goodpass=goodpass,foulline=foulline}) +if type(targetnames)~="table"then +targetnames={targetnames} +end +local _targets={} +local center=nil +local ntargets=0 +for _i,_name in ipairs(targetnames)do +local _isstatic=self:_CheckStatic(_name) +local unit=nil +if _isstatic==true then +self:T(self.id..string.format("Adding STATIC object %s as strafe target #%d.",_name,_i)) +unit=STATIC:FindByName(_name,false) +elseif _isstatic==false then +self:T(self.id..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.id..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.id..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.id..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.id..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.id..string.format("Adding unit bombing target %s with hit range %d.",name,goodhitrange,randommove)) +self:AddBombingTargetUnit(_unit,goodhitrange) +else +self:E(self.id..string.format("ERROR! Could not find bombing target %s.",name)) +end +end +return self +end +function RANGE:AddBombingTargetUnit(unit,goodhitrange,randommove) +self:F({unit=unit,goodhitrange=goodhitrange,randommove=randommove}) +local name=unit:GetName() +local _isstatic=self:_CheckStatic(name) +goodhitrange=goodhitrange or RANGE.Defaults.goodhitrange +if randommove==nil or _isstatic==true then +randommove=false +end +if _isstatic==true then +self:I(self.id..string.format("Adding STATIC bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove))) +elseif _isstatic==false then +self:I(self.id..string.format("Adding UNIT bombing target %s with good hit range %d. Random move = %s.",name,goodhitrange,tostring(randommove))) +else +self:E(self.id..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:AddBombingTargetGroup(group,goodhitrange,randommove) +self:F({group=group,goodhitrange=goodhitrange,randommove=randommove}) +if group then +local _units=group:GetUnits() +for _,_unit in pairs(_units)do +if _unit and _unit:IsAlive()then +self:AddBombingTargetUnit(_unit,goodhitrange,randommove) +end +end +end +return self +end +function RANGE:GetFoullineDistance(namepit,namefoulline) +self:F({namepit=namepit,namefoulline=namefoulline}) +local _staticpit=self:_CheckStatic(namepit) +local _staticfoul=self:_CheckStatic(namefoulline) +local pit=nil +if _staticpit==true then +pit=STATIC:FindByName(namepit,false) +elseif _staticpit==false then +pit=UNIT:FindByName(namepit) +else +self:E(self.id..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.id..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.id..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.id..string.format("Foul line distance = %.1f m.",fouldist)) +return fouldist +end +function RANGE:onEvent(Event) +self:F3(Event) +if Event==nil or Event.initiator==nil then +self:T3("Skipping onEvent. Event or Event.initiator unknown.") +return true +end +if Unit.getByName(Event.initiator:getName())==nil then +self:T3("Skipping onEvent. Initiator unit name unknown.") +return true +end +local DCSiniunit=Event.initiator +local DCStgtunit=Event.target +local DCSweapon=Event.weapon +local EventData={} +local _playerunit=nil +local _playername=nil +if Event.initiator then +EventData.IniUnitName=Event.initiator:getName() +EventData.IniDCSGroup=Event.initiator:getGroup() +EventData.IniGroupName=Event.initiator:getGroup():getName() +_playerunit,_playername=self:_GetPlayerUnitAndName(EventData.IniUnitName) +end +if Event.target then +EventData.TgtUnitName=Event.target:getName() +EventData.TgtUnit=UNIT:FindByName(EventData.TgtUnitName) +end +if Event.weapon then +EventData.Weapon=Event.weapon +EventData.weapon=Event.weapon +EventData.WeaponTypeName=Event.weapon:getTypeName() +end +self:T3(self.id..string.format("EVENT: Event in onEvent with ID = %s",tostring(Event.id))) +self:T3(self.id..string.format("EVENT: Ini unit = %s",tostring(EventData.IniUnitName))) +self:T3(self.id..string.format("EVENT: Ini group = %s",tostring(EventData.IniGroupName))) +self:T3(self.id..string.format("EVENT: Ini player = %s",tostring(_playername))) +self:T3(self.id..string.format("EVENT: Tgt unit = %s",tostring(EventData.TgtUnitName))) +self:T3(self.id..string.format("EVENT: Wpn type = %s",tostring(EventData.WeaponTypeName))) +if Event.id==world.event.S_EVENT_BIRTH and _playername then +self:OnEventBirth(EventData) +end +if Event.id==world.event.S_EVENT_SHOT and _playername and Event.weapon then +self:OnEventShot(EventData) +end +if Event.id==world.event.S_EVENT_HIT and _playername and DCStgtunit then +self:OnEventHit(EventData) +end +end +function RANGE:OnEventBirth(EventData) +self:F({eventbirth=EventData}) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.id.."BIRTH: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.id.."BIRTH: group = "..tostring(EventData.IniGroupName)) +self:T3(self.id.."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.id..text) +self.strafeStatus[_uid]=nil +self:ScheduleOnce(0.1,self._AddF10Commands,self,_unitName) +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].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.id.."HIT: Ini unit = "..tostring(EventData.IniUnitName)) +self:T3(self.id.."HIT: Ini group = "..tostring(EventData.IniGroupName)) +self:T3(self.id.."HIT: Tgt target = "..tostring(EventData.TgtUnitName)) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit==nil or _playername==nil then +return +end +local _unitID=_unit:GetID() +local target=EventData.TgtUnit +local targetname=EventData.TgtUnitName +local _currentTarget=self.strafeStatus[_unitID] +if _currentTarget and target:IsAlive()then +local playerPos=_unit:GetCoordinate() +local targetPos=target:GetCoordinate() +for _,_target in pairs(_currentTarget.zone.targets)do +if _target and _target:IsAlive()and _target:GetName()==targetname then +local dist=playerPos:Get2DDistance(targetPos) +if dist>_currentTarget.zone.foulline then +_currentTarget.hits=_currentTarget.hits+1 +if _unit and _playername and self.PlayerSettings[_playername].flaredirecthits then +targetPos:Flare(self.PlayerSettings[_playername].flarecolor) +end +else +if _currentTarget.pastfoulline==false and _unit and _playername then +local _d=_currentTarget.zone.foulline +local text=string.format("%s, Invalid hit!\nYou already passed foul line distance of %d m for target %s.",self:_myname(_unitName),_d,targetname) +self:_DisplayMessageToGroup(_unit,text) +self:T2(self.id..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:OnEventShot(EventData) +self:F({eventshot=EventData}) +if EventData.Weapon==nil then +return +end +if EventData.IniDCSUnit==nil then +return +end +local _weapon=EventData.Weapon:getTypeName() +local _weaponStrArray=UTILS.Split(_weapon,"%.") +local _weaponName=_weaponStrArray[#_weaponStrArray] +local desc=EventData.Weapon:getDesc() +local weaponcategory=desc.category +self:T(self.id.."EVENT SHOT: Range "..self.rangename) +self:T(self.id.."EVENT SHOT: Ini unit = "..EventData.IniUnitName) +self:T(self.id.."EVENT SHOT: Ini group = "..EventData.IniGroupName) +self:T(self.id.."EVENT SHOT: Weapon type = ".._weapon) +self:T(self.id.."EVENT SHOT: Weapon name = ".._weaponName) +self:T(self.id.."EVENT SHOT: Weapon cate = "..weaponcategory) +local _bombs=weaponcategory==Weapon.Category.BOMB +local _rockets=weaponcategory==Weapon.Category.ROCKET +local _missiles=weaponcategory==Weapon.Category.MISSILE +local _track=(_bombs and self.trackbombs)or(_rockets and self.trackrockets)or(_missiles and self.trackmissiles) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +local dPR=self.BombtrackThreshold*2 +if _unit and _playername then +dPR=_unit:GetCoordinate():Get2DDistance(self.location) +self:T(self.id..string.format("Range %s, player %s, player-range distance = %d km.",self.rangename,_playername,dPR/1000)) +end +if _track and dPR<=self.BombtrackThreshold and _unit and _playername then +local playerData=self.PlayerSettings[_playername] +self:T(self.id..string.format("RANGE %s: Tracking %s - %s.",self.rangename,_weapon,EventData.weapon:getName())) +local _lastBombPos={x=0,y=0,z=0} +local function trackBomb(_ordnance) +local _status,_bombPos=pcall( +function() +return _ordnance:getPoint() +end) +self:T2(self.id..string.format("Range %s: Bomb still in air: %s",self.rangename,tostring(_status))) +if _status then +_lastBombPos={x=_bombPos.x,y=_bombPos.y,z=_bombPos.z} +return timer.getTime()+self.dtBombtrack +else +local _closetTarget=nil +local _distance=nil +local _closeCoord=nil +local _hitquality="POOR" +local _callsign=self:_myname(_unitName) +local impactcoord=COORDINATE:NewFromVec3(_lastBombPos) +local insidezone=self.rangezone:IsCoordinateInZone(impactcoord) +if self.Debug then +impactcoord:MarkToAll("Bomb impact point") +end +if playerData.smokebombimpact and insidezone then +if playerData.delaysmoke then +timer.scheduleFunction(self._DelayedSmoke,{coord=impactcoord,color=playerData.smokecolor},timer.getTime()+self.TdelaySmoke) +else +impactcoord:Smoke(playerData.smokecolor) +end +end +for _,_bombtarget in pairs(self.bombingTargets)do +local 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<=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.name=_closetTarget.name or"unknown" +result.distance=_distance +result.radial=_closeCoord:HeadingTo(impactcoord) +result.weapon=_weaponName or"unknown" +result.quality=_hitquality +result.player=playerData.playername +result.time=timer.getAbsTime() +result.airframe=playerData.airframe +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) +self:_DisplayMessageToGroup(_unit,_message,nil,false) +if self.rangecontrol then +self.rangecontrol:NewTransmission(RANGE.Sound.RCWeaponImpactedTooFar.filename,RANGE.Sound.RCWeaponImpactedTooFar.duration,self.soundpath,nil,nil,_message,self.subduration) +end +else +self:T(self.id.."Weapon impacted outside range zone.") +end +self:T(self.id..string.format("Range %s, player %s: Terminating bomb track timer.",self.rangename,_playername)) +return nil +end +end +self:T(self.id..string.format("Range %s, player %s: Tracking of weapon starts in 0.1 seconds.",self.rangename,_playername)) +timer.scheduleFunction(trackBomb,EventData.weapon,timer.getTime()+0.1) +end +end +function RANGE:onafterStatus(From,Event,To) +if self.verbose>0 then +local fsmstate=self:GetState() +local text=string.format("Range status: %s",fsmstate) +if self.instructor then +local alive="N/A" +if self.instructorrelayname then +local relay=UNIT:FindByName(self.instructorrelayname) +if relay then +alive=tostring(relay:IsAlive()) +end +end +text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)",self.instructorfreq,tostring(self.instructorrelayname),alive) +end +if self.rangecontrol then +local alive="N/A" +if self.rangecontrolrelayname then +local relay=UNIT:FindByName(self.rangecontrolrelayname) +if relay then +alive=tostring(relay:IsAlive()) +end +end +text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)",self.rangecontrolfreq,tostring(self.rangecontrolrelayname),alive) +end +self:I(self.id..text) +end +self:_CheckPlayers() +self:__Status(-10) +end +function RANGE:onafterEnterRange(From,Event,To,player) +if self.instructor and self.rangecontrol then +local RF=UTILS.Split(string.format("%.3f",self.rangecontrolfreq),".") +self.instructor:NewTransmission(RANGE.Sound.IREnterRange.filename,RANGE.Sound.IREnterRange.duration,self.soundpath) +self.instructor:Number2Transmission(RF[1]) +if tonumber(RF[2])>0 then +self.instructor:NewTransmission(RANGE.Sound.IRDecimal.filename,RANGE.Sound.IRDecimal.duration,self.soundpath) +self.instructor:Number2Transmission(RF[2]) +end +self.instructor:NewTransmission(RANGE.Sound.IRMegaHertz.filename,RANGE.Sound.IRMegaHertz.duration,self.soundpath) +end +end +function RANGE:onafterExitRange(From,Event,To,player) +if self.instructor then +self.instructor:NewTransmission(RANGE.Sound.IRExitRange.filename,RANGE.Sound.IRExitRange.duration,self.soundpath) +end +end +function RANGE:onafterImpact(From,Event,To,result,player) +local targetname=nil +if#self.bombingTargets>1 then +local targetname=result.name +end +local text=string.format("%s, impact %03d° for %d ft",player.playername,result.radial,UTILS.MetersToFeet(result.distance)) +if targetname then +text=text..string.format(" from bulls of target %s.") +else +text=text.."." +end +text=text..string.format(" %s hit.",result.quality) +if self.rangecontrol then +self.rangecontrol:NewTransmission(RANGE.Sound.RCImpact.filename,RANGE.Sound.RCImpact.duration,self.soundpath,nil,nil,text,self.subduration) +self.rangecontrol:Number2Transmission(string.format("%03d",result.radial),nil,0.1) +self.rangecontrol:NewTransmission(RANGE.Sound.RCDegrees.filename,RANGE.Sound.RCDegrees.duration,self.soundpath) +self.rangecontrol:NewTransmission(RANGE.Sound.RCFor.filename,RANGE.Sound.RCFor.duration,self.soundpath) +self.rangecontrol:Number2Transmission(string.format("%d",UTILS.MetersToFeet(result.distance))) +self.rangecontrol:NewTransmission(RANGE.Sound.RCFeet.filename,RANGE.Sound.RCFeet.duration,self.soundpath) +if result.quality=="POOR"then +self.rangecontrol:NewTransmission(RANGE.Sound.RCPoorHit.filename,RANGE.Sound.RCPoorHit.duration,self.soundpath,nil,0.5) +elseif result.quality=="INEFFECTIVE"then +self.rangecontrol:NewTransmission(RANGE.Sound.RCIneffectiveHit.filename,RANGE.Sound.RCIneffectiveHit.duration,self.soundpath,nil,0.5) +elseif result.quality=="GOOD"then +self.rangecontrol:NewTransmission(RANGE.Sound.RCGoodHit.filename,RANGE.Sound.RCGoodHit.duration,self.soundpath,nil,0.5) +elseif result.quality=="EXCELLENT"then +self.rangecontrol:NewTransmission(RANGE.Sound.RCExcellentHit.filename,RANGE.Sound.RCExcellentHit.duration,self.soundpath,nil,0.5) +end +end +local unit=UNIT:FindByName(player.unitname) +self:_DisplayMessageToGroup(unit,text,nil,true) +self:T(self.id..text) +if self.autosave then +self:Save() +end +end +function RANGE:onbeforeSave(From,Event,To) +if io and lfs then +return true +else +self:E(self.id..string.format("WARNING: io and/or lfs not desanitized. Cannot save player results.")) +return false +end +end +function RANGE:onafterSave(From,Event,To) +local function _savefile(filename,data) +local f=io.open(filename,"wb") +if f then +f:write(data) +f:close() +self:I(self.id..string.format("Saving player results to file %s",tostring(filename))) +else +self:E(self.id..string.format("ERROR: Could not save results to file %s",tostring(filename))) +end +end +local path=lfs.writedir()..[[Logs\]] +local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename) +local scores="Name,Pass,Target,Distance,Radial,Quality,Weapon,Airframe,Mission Time" +for playername,results in pairs(self.bombPlayerResults)do +for i,_result in pairs(results)do +local result=_result +local distance=result.distance +local weapon=result.weapon +local target=result.name +local radial=result.radial +local quality=result.quality +local time=UTILS.SecondsToClock(result.time) +local airframe=result.airframe +local date="n/a" +if os then +date=os.date() +end +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.id..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.id..string.format("WARNING: Could not load player results from file %s. File might not exist just yet.",tostring(filename))) +return nil +end +end +local path=lfs.writedir()..[[Logs\]] +local filename=path..string.format("RANGE-%s_BombingResults.csv",self.rangename) +local text=string.format("Loading player bomb results from file %s",filename) +self:I(self.id..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._DelayedSmoke(_args) +trigger.action.smoke(_args.coord:GetVec3(),_args.color) +end +function RANGE:_DisplayMyStrafePitResults(_unitName) +self:F(_unitName) +local _unit,_playername=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.hits>b.hits end +table.sort(_results,_sort) +local _bestMsg="" +local _count=1 +for _,_result in pairs(_results)do +_message=_message..string.format("\n[%d] Hits %d - %s - %s",_count,_result.hits,_result.zone.name,_result.text) +if _bestMsg==""then +_bestMsg=string.format("Hits %d - %s - %s",_result.hits,_result.zone.name,_result.text) +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) +end +end +function RANGE:_DisplayStrafePitResults(_unitName) +self:F(_unitName) +local _unit,_playername=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.hits>_best.hits then +_best=_result +end +end +if _best~=nil then +local text=string.format("%s: Hits %i - %s - %s",_playerName,_best.hits,_best.zone.name,_best.text) +table.insert(_playerResults,{msg=text,hits=_best.hits}) +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) +end +end +function RANGE:_DisplayMyBombingResults(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local _message=string.format("My Top %d Bombing Results:\n",self.ndisplayresult) +local _results=self.bombPlayerResults[_playername] +if _results==nil then +_message=_playername..": No Score yet." +else +local _sort=function(a,b)return a.distance180 then +heading=heading-180 +else +heading=heading+180 +end +local mycoord=coord:ToStringA2G(_unit,_settings) +_text=_text..string.format("\n- %s: heading %03d°\n%s",_strafepit.name,heading,mycoord) +end +self:_DisplayMessageToGroup(_unit,_text,nil,true,true) +end +end +function RANGE:_DisplayRangeWeather(_unitname) +self:F(_unitname) +local unit,playername=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) +self:T2(self.id..text) +else +self:T(self.id..string.format("ERROR! Could not find player unit in RangeInfo! Name = %s",_unitname)) +end +end +function RANGE:_CheckPlayers() +for playername,_playersettings in pairs(self.PlayerSettings)do +local playersettings=_playersettings +local unitname=playersettings.unitname +local unit=UNIT:FindByName(unitname) +if unit and unit:IsAlive()then +if unit:IsInZone(self.rangezone)then +if not playersettings.inzone then +playersettings.inzone=true +self:EnterRange(playersettings) +end +else +if playersettings.inzone==true then +playersettings.inzone=false +self:ExitRange(playersettings) +end +end +end +end +end +function RANGE:_CheckInZone(_unitName) +self:F2(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +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 +self.rangecontrol:NewTransmission(RANGE.Sound.RCLeftStrafePitTooQuickly.filename,RANGE.Sound.RCLeftStrafePitTooQuickly.duration,self.soundpath) +end +else +local _ammo=self:_GetAmmo(_unitName) +local _result=self.strafeStatus[_unitID] +local _sound=nil +if _result.hits>=_result.zone.goodPass*2 then +_result.text="EXCELLENT PASS" +_sound=RANGE.Sound.RCExcellentPass +elseif _result.hits>=_result.zone.goodPass then +_result.text="GOOD PASS" +_sound=RANGE.Sound.RCGoodPass +elseif _result.hits>=_result.zone.goodPass/2 then +_result.text="INEFFECTIVE PASS" +_sound=RANGE.Sound.RCIneffectivePass +else +_result.text="POOR PASS" +_sound=RANGE.Sound.RCPoorPass +end +local shots=_result.ammo-_ammo +local accur=0 +if shots>0 then +accur=_result.hits/shots*100 +end +local _text=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) +end +_text=_text..string.format("\n%s",_result.text) +self:_DisplayMessageToGroup(_unit,_text) +if self.rangecontrol then +self.rangecontrol:NewTransmission(RANGE.Sound.RCHitsOnTarget.filename,RANGE.Sound.RCHitsOnTarget.duration,self.soundpath) +self.rangecontrol:Number2Transmission(string.format("%d",_result.hits)) +if shots and accur then +self.rangecontrol:NewTransmission(RANGE.Sound.RCTotalRoundsFired.filename,RANGE.Sound.RCTotalRoundsFired.duration,self.soundpath,nil,0.2) +self.rangecontrol:Number2Transmission(string.format("%d",shots),nil,0.2) +self.rangecontrol:NewTransmission(RANGE.Sound.RCAccuracy.filename,RANGE.Sound.RCAccuracy.duration,self.soundpath,nil,0.2) +self.rangecontrol:Number2Transmission(string.format("%d",UTILS.Round(accur,0))) +self.rangecontrol:NewTransmission(RANGE.Sound.RCPercent.filename,RANGE.Sound.RCPercent.duration,self.soundpath) +end +self.rangecontrol:NewTransmission(_sound.filename,_sound.duration,self.soundpath,nil,0.5) +end +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 zone=_targetZone.polygon +local unitinzone=checkme(_targetZone.heading,zone) +if unitinzone then +local _ammo=self:_GetAmmo(_unitName) +self.strafeStatus[_unitID]={hits=0,zone=_targetZone,time=1,ammo=_ammo,pastfoulline=false} +local _msg=string.format("%s, rolling in on strafe pit %s.",self:_myname(_unitName),_targetZone.name) +if self.rangecontrol then +self.rangecontrol:NewTransmission(RANGE.Sound.RCRollingInOnStrafeTarget.filename,RANGE.Sound.RCRollingInOnStrafeTarget.duration,self.soundpath) +end +self:_DisplayMessageToGroup(_unit,_msg,10,true) +break +end +end +end +end +end +function RANGE:_AddF10Commands(_unitName) +self:F(_unitName) +local _unit,playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and playername then +local group=_unit:GetGroup() +local _gid=group:GetID() +if group and _gid then +if not self.MenuAddedTo[_gid]then +self.MenuAddedTo[_gid]=true +local _rangePath=nil +if RANGE.MenuF10Root then +_rangePath=missionCommands.addSubMenuForGroup(_gid,self.rangename,RANGE.MenuF10Root) +else +if RANGE.MenuF10[_gid]==nil then +RANGE.MenuF10[_gid]=missionCommands.addSubMenuForGroup(_gid,"On the Range") +end +_rangePath=missionCommands.addSubMenuForGroup(_gid,self.rangename,RANGE.MenuF10[_gid]) +end +local _statsPath=missionCommands.addSubMenuForGroup(_gid,"Statistics",_rangePath) +local _markPath=missionCommands.addSubMenuForGroup(_gid,"Mark Targets",_rangePath) +local _settingsPath=missionCommands.addSubMenuForGroup(_gid,"My Settings",_rangePath) +local _infoPath=missionCommands.addSubMenuForGroup(_gid,"Range Info",_rangePath) +local _mysmokePath=missionCommands.addSubMenuForGroup(_gid,"Smoke Color",_settingsPath) +local _myflarePath=missionCommands.addSubMenuForGroup(_gid,"Flare Color",_settingsPath) +missionCommands.addCommandForGroup(_gid,"Mark On Map",_markPath,self._MarkTargetsOnMap,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Illuminate Range",_markPath,self._IlluminateBombTargets,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Smoke Strafe Pits",_markPath,self._SmokeStrafeTargetBoxes,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Smoke Strafe Tgts",_markPath,self._SmokeStrafeTargets,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Smoke Bomb Tgts",_markPath,self._SmokeBombTargets,self,_unitName) +missionCommands.addCommandForGroup(_gid,"All Strafe Results",_statsPath,self._DisplayStrafePitResults,self,_unitName) +missionCommands.addCommandForGroup(_gid,"All Bombing Results",_statsPath,self._DisplayBombingResults,self,_unitName) +missionCommands.addCommandForGroup(_gid,"My Strafe Results",_statsPath,self._DisplayMyStrafePitResults,self,_unitName) +missionCommands.addCommandForGroup(_gid,"My Bomb Results",_statsPath,self._DisplayMyBombingResults,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Reset All Stats",_statsPath,self._ResetRangeStats,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Blue Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Blue) +missionCommands.addCommandForGroup(_gid,"Green Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Green) +missionCommands.addCommandForGroup(_gid,"Orange Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Orange) +missionCommands.addCommandForGroup(_gid,"Red Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.Red) +missionCommands.addCommandForGroup(_gid,"White Smoke",_mysmokePath,self._playersmokecolor,self,_unitName,SMOKECOLOR.White) +missionCommands.addCommandForGroup(_gid,"Green Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Green) +missionCommands.addCommandForGroup(_gid,"Red Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Red) +missionCommands.addCommandForGroup(_gid,"White Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.White) +missionCommands.addCommandForGroup(_gid,"Yellow Flares",_myflarePath,self._playerflarecolor,self,_unitName,FLARECOLOR.Yellow) +missionCommands.addCommandForGroup(_gid,"Smoke Delay On/Off",_settingsPath,self._SmokeBombDelayOnOff,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Smoke Impact On/Off",_settingsPath,self._SmokeBombImpactOnOff,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Flare Hits On/Off",_settingsPath,self._FlareDirectHitsOnOff,self,_unitName) +missionCommands.addCommandForGroup(_gid,"All Messages On/Off",_settingsPath,self._MessagesToPlayerOnOff,self,_unitName) +missionCommands.addCommandForGroup(_gid,"General Info",_infoPath,self._DisplayRangeInfo,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Weather Report",_infoPath,self._DisplayRangeWeather,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Bombing Targets",_infoPath,self._DisplayBombTargets,self,_unitName) +missionCommands.addCommandForGroup(_gid,"Strafe Pits",_infoPath,self._DisplayStrafePits,self,_unitName) +end +else +self:E(self.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName) +end +else +self:E(self.id.."Player unit does not exist in AddF10Menu() function. Unit name: ".._unitName) +end +end +function RANGE:_GetBombTargetCoordinate(target) +local coord=nil +if target.type==RANGE.TargetType.UNIT then +if not target.move then +coord=target.coordinate +else +if target.target and target.target:IsAlive()then +coord=target.target:GetCoordinate() +end +end +elseif target.type==RANGE.TargetType.STATIC then +coord=target.coordinate +elseif target.type==RANGE.TargetType.COORD then +coord=target.coordinate +else +self:E(self.id.."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.id..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.id..text) +else +local text=string.format("Player %s has %d ammo of type %s",playername,Nammo,Tammo) +self:T(self.id..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) +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 _,playername=self:_GetPlayerUnitAndName(_unit:GetName()) +local playermessage=self.PlayerSettings[playername].messages +if _gid and(playermessage==true or display)and(not self.examinerexclusive)then +trigger.action.outTextForGroup(_gid,_text,_time,_clear) +end +if self.examinergroupname~=nil then +local _examinerid=GROUP:FindByName(self.examinergroupname):GetID() +if _examinerid then +trigger.action.outTextForGroup(_examinerid,_text,_time,_clear) +end +end +end +end +function RANGE:_SmokeBombImpactOnOff(unitname) +self:F(unitname) +local unit,playername=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.PlayerSettigs[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=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.PlayerSettigs[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=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:_FlareDirectHitsOnOff(unitname) +self:F(unitname) +local unit,playername=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.id..string.format("Adding DCS static to MOOSE database. Name = %s.",name)) +_DATABASE:AddStatic(name) +end +return true +else +self:T3(self.id..string.format("No static object with name %s exists.",name)) +end +if UNIT:FindByName(name)then +return false +else +self:T3(self.id..string.format("No unit object with name %s exists.",name)) +end +return nil +end +function RANGE:_GetSpeed(controllable) +self:F2(controllable) +local desc=controllable:GetDesc() +local speed=0 +if desc then +speed=desc.speedMax*3.6 +self:T({speed=speed}) +end +return speed +end +function RANGE:_GetPlayerUnitAndName(_unitName) +self:F2(_unitName) +if _unitName~=nil then +local 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 +return unit,playername +end +end +end +return nil,nil +end +function RANGE:_myname(unitname) +self:F2(unitname) +local unit=UNIT:FindByName(unitname) +local pname=unit:GetPlayerName() +local csign=unit:GetCallsign() +return string.format("%s",pname) +end +do +ZONE_GOAL={ +ClassName="ZONE_GOAL", +Goal=nil, +SmokeTime=nil, +SmokeScheduler=nil, +SmokeColor=nil, +SmokeZone=nil, +} +function ZONE_GOAL:New(Zone) +local self=BASE:Inherit(self,ZONE_RADIUS:New(Zone:GetName(),Zone:GetVec2(),Zone:GetRadius())) +self:F({Zone=Zone}) +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, +PreviousCoaliton=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_COALITON!") +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: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 +function ZONE_CAPTURE_COALITION:onafterGuard() +self:F2("After Guard") +if self.SmokeZone and not self.SmokeScheduler then +self.SmokeScheduler=self:ScheduleRepeat(self.StartInterval,self.RepeatInterval,0.1,nil,self.StatusSmoke,self) +end +end +function ZONE_CAPTURE_COALITION:onenterGuarded() +self:F2("Enter Guarded") +self:Mark() +end +function ZONE_CAPTURE_COALITION:onenterCaptured() +self:F2("Enter Captured") +local NewCoalition=self:GetScannedCoalition() +self:F({NewCoalition=NewCoalition}) +self:SetCoalition(NewCoalition) +self:Mark() +self.Goal:Achieved() +end +function ZONE_CAPTURE_COALITION:onenterEmpty() +self:F2("Enter Empty") +self:Mark() +end +function ZONE_CAPTURE_COALITION:onenterAttacked() +self:F2("Enter Attacked") +self:Mark() +end +function ZONE_CAPTURE_COALITION:IsEmpty() +local IsEmpty=self:IsNoneInZone() +self:F({IsEmpty=IsEmpty}) +return IsEmpty +end +function ZONE_CAPTURE_COALITION:IsGuarded() +local IsGuarded=self:IsAllInZoneOfCoalition(self.Coalition) +self:F({IsGuarded=IsGuarded}) +return IsGuarded +end +function ZONE_CAPTURE_COALITION:IsCaptured() +local IsCaptured=self:IsAllInZoneOfOtherCoalition(self.Coalition) +self:F({IsCaptured=IsCaptured}) +return IsCaptured +end +function ZONE_CAPTURE_COALITION:IsAttacked() +local IsAttacked=self:IsSomeInZoneOfCoalition(self.Coalition) +self:F({IsAttacked=IsAttacked}) +return IsAttacked +end +function ZONE_CAPTURE_COALITION:StatusZone() +local State=self:GetState() +self:GetParent(self,ZONE_CAPTURE_COALITION).StatusZone(self) +local Tnow=timer.getTime() +if State~="Guarded"and self:IsGuarded()then +if self.HitTimeLast==nil or Tnow>=self.HitTimeLast+self.HitTimeAttackOver then +self:Guard() +self.HitTimeLast=nil +end +end +if State~="Empty"and self:IsEmpty()then +self:Empty() +end +if State~="Attacked"and self:IsAttacked()then +self:Attack() +end +if State~="Captured"and self:IsCaptured()then +self:Capture() +end +local unitset=self:GetScannedSetUnit() +local nRed=0 +local nBlue=0 +for _,object in pairs(unitset:GetSet())do +local coal=object:GetCoalition() +if object:IsAlive()then +if coal==coalition.side.RED then +nRed=nRed+1 +elseif coal==coalition.side.BLUE then +nBlue=nBlue+1 +end +end +end +if false then +local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s",self:GetZoneName(),self:GetCoalitionName(),UTILS.GetCoalitionName(self:GetPreviousCoalition()),nBlue,nRed,State) +local NewState=self:GetState() +if NewState~=State then +text=text..string.format(" --> %s",NewState) +end +self:I(text) +end +end +function ZONE_CAPTURE_COALITION:Mark() +if self.MarkOn then +local Coord=self:GetCoordinate() +local ZoneName=self:GetZoneName() +local State=self:GetState() +if self.MarkRed then +Coord:RemoveMark(self.MarkRed) +end +if self.MarkBlue then +Coord:RemoveMark(self.MarkBlue) +end +if self.Coalition==coalition.side.BLUE then +self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Blue\nGuard Zone: "..ZoneName.."\nStatus: "..State) +self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Blue\nCapture Zone: "..ZoneName.."\nStatus: "..State) +elseif self.Coalition==coalition.side.RED then +self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Red\nGuard Zone: "..ZoneName.."\nStatus: "..State) +self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Red\nCapture Zone: "..ZoneName.."\nStatus: "..State) +else +self.MarkRed=Coord:MarkToCoalitionRed("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State) +self.MarkBlue=Coord:MarkToCoalitionBlue("Coalition: Neutral\nCapture Zone: "..ZoneName.."\nStatus: "..State) +end +end +end +end +ARTY={ +ClassName="ARTY", +lid=nil, +Debug=false, +targets={}, +moves={}, +currentTarget=nil, +currentMove=nil, +Nammo0=0, +Nshells0=0, +Nrockets0=0, +Nmissiles0=0, +Nukes0=0, +Nillu0=0, +Nsmoke0=0, +StatusInterval=10, +WaitForShotTime=300, +DCSdesc=nil, +Type=nil, +DisplayName=nil, +groupname=nil, +alias=nil, +clusters={}, +ismobile=true, +iscargo=false, +cargogroup=nil, +IniGroupStrength=0, +IsArtillery=nil, +RearmingDistance=100, +RearmingGroup=nil, +RearmingGroupSpeed=nil, +RearmingGroupOnRoad=false, +RearmingGroupCoord=nil, +RearmingPlaceCoord=nil, +RearmingArtyOnRoad=false, +InitialCoord=nil, +report=true, +ammoshells={}, +ammorockets={}, +ammomissiles={}, +Nshots=0, +minrange=300, +maxrange=1000000, +nukewarhead=75000, +Nukes=nil, +nukefire=false, +nukefires=nil, +nukerange=nil, +Nillu=nil, +illuPower=1000000, +illuMinalt=500, +illuMaxalt=1000, +Nsmoke=nil, +smokeColor=SMOKECOLOR.Red, +relocateafterfire=false, +relocateRmin=300, +relocateRmax=800, +markallow=false, +markkey=nil, +markreadonly=false, +autorelocate=false, +autorelocatemaxdist=50000, +autorelocateonroad=false, +coalition=nil, +respawnafterdeath=false, +respawndelay=nil +} +ARTY.WeaponType={ +Auto=1073741822, +Cannon=805306368, +Rockets=30720, +CruiseMissile=2097152, +TacticalNukes=666, +IlluminationShells=667, +SmokeShells=668, +} +ARTY.db={ +["2B11 mortar"]={ +minrange=500, +maxrange=7000, +reloadtime=30, +}, +["SPH 2S1 Gvozdika"]={ +minrange=300, +maxrange=15000, +reloadtime=nil, +}, +["SPH 2S19 Msta"]={ +minrange=300, +maxrange=23500, +reloadtime=nil, +}, +["SPH 2S3 Akatsia"]={ +minrange=300, +maxrange=17000, +reloadtime=nil, +}, +["SPH 2S9 Nona"]={ +minrange=500, +maxrange=7000, +reloadtime=nil, +}, +["SPH M109 Paladin"]={ +minrange=300, +maxrange=22000, +reloadtime=nil, +}, +["SpGH Dana"]={ +minrange=300, +maxrange=18700, +reloadtime=nil, +}, +["MLRS BM-21 Grad"]={ +minrange=5000, +maxrange=19000, +reloadtime=420, +}, +["MLRS 9K57 Uragan BM-27"]={ +minrange=11500, +maxrange=35800, +reloadtime=840, +}, +["MLRS 9A52 Smerch"]={ +minrange=20000, +maxrange=70000, +reloadtime=2160, +}, +["MLRS M270"]={ +minrange=10000, +maxrange=32000, +reloadtime=540, +}, +} +ARTY.version="1.2.0" +function ARTY:New(group,alias) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +if type(group)=="string"then +self.groupname=group +group=GROUP:FindByName(group) +if not group then +self:E(string.format("ERROR: Requested ARTY group %s does not exist! (Has to be a MOOSE group.)",self.groupname)) +return nil +end +end +if group then +self:T(string.format("ARTY script version %s. Added group %s.",ARTY.version,group:GetName())) +else +self:E("ERROR: Requested ARTY group does not exist! (Has to be a MOOSE group.)") +return nil +end +if not(group:IsGround()or group:IsShip())then +self:E(string.format("ERROR: ARTY group %s has to be a GROUND or SHIP group!",group:GetName())) +return nil +end +self:SetControllable(group) +self.groupname=group:GetName() +self.coalition=group:GetCoalition() +if alias~=nil then +self.alias=tostring(alias) +else +self.alias=self.groupname +end +self.lid=string.format("ARTY %s | ",self.alias) +self.InitialCoord=group:GetCoordinate() +local DCSgroup=Group.getByName(group:GetName()) +local DCSunit=DCSgroup:getUnit(1) +self.DCSdesc=DCSunit:getDesc() +self:T3(self.lid.."DCS descriptors for group "..group:GetName()) +for id,desc in pairs(self.DCSdesc)do +self:T3({id=id,desc=desc}) +end +self.SpeedMax=group:GetSpeedMax() +if self.SpeedMax>1 then +self.ismobile=true +else +self.ismobile=false +end +self.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:SetWaitForShotTime(waittime) +self:F({waittime=waittime}) +self.WaitForShotTime=waittime or 300 +return self +end +function ARTY:SetRearmingDistance(distance) +self:F({distance=distance}) +self.RearmingDistance=distance or 100 +return self +end +function ARTY:SetRearmingGroup(group) +self:F({group=group}) +self.RearmingGroup=group +return self +end +function ARTY:SetRearmingGroupSpeed(speed) +self:F({speed=speed}) +self.RearmingGroupSpeed=speed +return self +end +function ARTY:SetRearmingGroupOnRoad(onroad) +self:F({onroad=onroad}) +if onroad==nil then +onroad=true +end +self.RearmingGroupOnRoad=onroad +return self +end +function ARTY:SetRearmingArtyOnRoad(onroad) +self:F({onroad=onroad}) +if onroad==nil then +onroad=true +end +self.RearmingArtyOnRoad=onroad +return self +end +function ARTY:SetRearmingPlace(coord) +self:F({coord=coord}) +self.RearmingPlaceCoord=coord +return self +end +function ARTY:SetAutoRelocateToFiringRange(maxdistance,onroad) +self:F({distance=maxdistance,onroad=onroad}) +self.autorelocate=true +self.autorelocatemaxdist=maxdistance or 50 +self.autorelocatemaxdist=self.autorelocatemaxdist*1000 +if onroad==nil then +onroad=false +end +self.autorelocateonroad=onroad +return self +end +function ARTY:SetAutoRelocateAfterEngagement(rmax,rmin) +self.relocateafterfire=true +self.relocateRmax=rmax or 800 +self.relocateRmin=rmin or 300 +self.relocateRmin=math.min(self.relocateRmin,self.relocateRmax) +return self +end +function ARTY:SetReportON() +self.report=true +return self +end +function ARTY:SetReportOFF() +self.report=false +return self +end +function ARTY:SetRespawnOnDeath(delay) +self.respawnafterdeath=true +self.respawndelay=delay +return self +end +function ARTY:SetDebugON() +self.Debug=true +return self +end +function ARTY:SetDebugOFF() +self.Debug=false +return self +end +function ARTY:SetSpeed(speed) +self.Speed=speed +return self +end +function ARTY:RemoveTarget(name) +self:F2(name) +local id=self:_GetTargetIndexByName(name) +if id then +self:T(self.lid..string.format("Group %s: Removing target %s (id=%d).",self.groupname,name,id)) +table.remove(self.targets,id) +if self.markallow then +local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) +if batteryname==self.groupname and markTargetID~=nil then +COORDINATE:RemoveMark(markTargetID) +end +end +end +self:T(self.lid..string.format("Group %s: Number of targets = %d.",self.groupname,#self.targets)) +end +function ARTY:RemoveMove(name) +self:F2(name) +local id=self:_GetMoveIndexByName(name) +if id then +self:T(self.lid..string.format("Group %s: Removing move %s (id=%d).",self.groupname,name,id)) +table.remove(self.moves,id) +if self.markallow then +local batteryname,markTargetID,markMoveID=self:_GetMarkIDfromName(name) +if batteryname==self.groupname and markMoveID~=nil then +COORDINATE:RemoveMark(markMoveID) +end +end +end +self:T(self.lid..string.format("Group %s: Number of moves = %d.",self.groupname,#self.moves)) +end +function ARTY:RemoveAllTargets() +self:F2() +for _,target in pairs(self.targets)do +self:RemoveTarget(target.name) +end +end +function ARTY:SetShellTypes(tableofnames) +self:F2(tableofnames) +self.ammoshells={} +for _,_type in pairs(tableofnames)do +table.insert(self.ammoshells,_type) +end +return self +end +function ARTY:SetRocketTypes(tableofnames) +self:F2(tableofnames) +self.ammorockets={} +for _,_type in pairs(tableofnames)do +table.insert(self.ammorockets,_type) +end +return self +end +function ARTY:SetMissileTypes(tableofnames) +self:F2(tableofnames) +self.ammomissiles={} +for _,_type in pairs(tableofnames)do +table.insert(self.ammomissiles,_type) +end +return self +end +function ARTY:SetTacNukeShells(n) +self.Nukes=n +return self +end +function ARTY:SetTacNukeWarhead(strength) +self.nukewarhead=strength or 0.075 +self.nukewarhead=self.nukewarhead*1000*1000 +return self +end +function ARTY:SetIlluminationShells(n,power) +self.Nillu=n +self.illuPower=power or 1.0 +self.illuPower=self.illuPower*1000000 +return self +end +function ARTY:SetIlluminationMinMaxAlt(minalt,maxalt) +self.illuMinalt=minalt or 500 +self.illuMaxalt=maxalt or 1000 +if self.illuMinalt>self.illuMaxalt then +self.illuMinalt=self.illuMaxalt +end +return self +end +function ARTY:SetSmokeShells(n,color) +self.Nsmoke=n +self.smokeColor=color or SMOKECOLOR.Red +return self +end +function ARTY:SetTacNukeFires(nfires,range) +self.nukefire=true +self.nukefires=nfires +self.nukerange=range +return self +end +function ARTY:SetMarkAssignmentsOn(key,readonly) +self.markkey=key +self.markallow=true +if readonly==nil then +self.markreadonly=false +end +return self +end +function ARTY:SetMarkTargetsOff() +self.markallow=false +self.markkey=nil +return self +end +function ARTY:onafterStart(Controllable,From,Event,To) +self:_EventFromTo("onafterStart",Event,From,To) +local text=string.format("Started ARTY version %s for group %s.",ARTY.version,Controllable:GetName()) +self:I(self.lid..text) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self.Nammo0,self.Nshells0,self.Nrockets0,self.Nmissiles0=self:GetAmmo(self.Debug) +if self.nukerange==nil then +self.nukerange=1500/75000*self.nukewarhead +end +if self.nukefires==nil then +self.nukefires=20/1000/1000*self.nukerange*self.nukerange +end +if self.Nukes~=nil then +self.Nukes0=math.min(self.Nukes,self.Nshells0) +else +self.Nukes=0 +self.Nukes0=0 +end +if self.Nillu~=nil then +self.Nillu0=math.min(self.Nillu,self.Nshells0) +else +self.Nillu=0 +self.Nillu0=0 +end +if self.Nsmoke~=nil then +self.Nsmoke0=math.min(self.Nsmoke,self.Nshells0) +else +self.Nsmoke=0 +self.Nsmoke0=0 +end +local _dbproperties=self:_CheckDB(self.DisplayName) +self:T({dbproperties=_dbproperties}) +if _dbproperties~=nil then +for property,value in pairs(_dbproperties)do +self:T({property=property,value=value}) +self[property]=value +end +end +if not self.ismobile then +self.RearmingPlaceCoord=nil +self.relocateafterfire=false +self.autorelocate=false +end +self.Speed=math.min(self.Speed,self.SpeedMax) +if self.RearmingGroup then +local speedmax=self.RearmingGroup:GetSpeedMax() +self:T(self.lid..string.format("%s, rearming group %s max speed = %.1f km/h.",self.groupname,self.RearmingGroup:GetName(),speedmax)) +if self.RearmingGroupSpeed==nil then +self.RearmingGroupSpeed=speedmax*0.5 +else +self.RearmingGroupSpeed=math.min(self.RearmingGroupSpeed,self.RearmingGroup:GetSpeedMax()) +end +else +self.RearmingGroupSpeed=23 +end +local text=string.format("\n******************************************************\n") +text=text..string.format("Arty group = %s\n",self.groupname) +text=text..string.format("Arty alias = %s\n",self.alias) +text=text..string.format("Artillery attribute = %s\n",tostring(self.IsArtillery)) +text=text..string.format("Type = %s\n",self.Type) +text=text..string.format("Display Name = %s\n",self.DisplayName) +text=text..string.format("Number of units = %d\n",self.IniGroupStrength) +text=text..string.format("Speed max = %d km/h\n",self.SpeedMax) +text=text..string.format("Speed default = %d km/h\n",self.Speed) +text=text..string.format("Is mobile = %s\n",tostring(self.ismobile)) +text=text..string.format("Is cargo = %s\n",tostring(self.iscargo)) +text=text..string.format("Min range = %.1f km\n",self.minrange/1000) +text=text..string.format("Max range = %.1f km\n",self.maxrange/1000) +text=text..string.format("Total ammo count = %d\n",self.Nammo0) +text=text..string.format("Number of shells = %d\n",self.Nshells0) +text=text..string.format("Number of rockets = %d\n",self.Nrockets0) +text=text..string.format("Number of missiles = %d\n",self.Nmissiles0) +text=text..string.format("Number of nukes = %d\n",self.Nukes0) +text=text..string.format("Nuclear warhead = %d tons TNT\n",self.nukewarhead/1000) +text=text..string.format("Nuclear demolition = %d m\n",self.nukerange) +text=text..string.format("Nuclear fires = %d (active=%s)\n",self.nukefires,tostring(self.nukefire)) +text=text..string.format("Number of illum. = %d\n",self.Nillu0) +text=text..string.format("Illuminaton Power = %.3f mcd\n",self.illuPower/1000000) +text=text..string.format("Illuminaton Minalt = %d m\n",self.illuMinalt) +text=text..string.format("Illuminaton Maxalt = %d m\n",self.illuMaxalt) +text=text..string.format("Number of smoke = %d\n",self.Nsmoke0) +text=text..string.format("Smoke color = %d\n",self.smokeColor) +if self.RearmingGroup or self.RearmingPlaceCoord then +text=text..string.format("Rearming safe dist. = %d m\n",self.RearmingDistance) +end +if self.RearmingGroup then +text=text..string.format("Rearming group = %s\n",self.RearmingGroup:GetName()) +text=text..string.format("Rearming group speed= %d km/h\n",self.RearmingGroupSpeed) +text=text..string.format("Rearming group roads= %s\n",tostring(self.RearmingGroupOnRoad)) +end +if self.RearmingPlaceCoord then +local dist=self.InitialCoord:Get2DDistance(self.RearmingPlaceCoord) +text=text..string.format("Rearming coord dist = %d m\n",dist) +text=text..string.format("Rearming ARTY roads = %s\n",tostring(self.RearmingArtyOnRoad)) +end +text=text..string.format("Relocate after fire = %s\n",tostring(self.relocateafterfire)) +text=text..string.format("Relocate min dist. = %d m\n",self.relocateRmin) +text=text..string.format("Relocate max dist. = %d m\n",self.relocateRmax) +text=text..string.format("Auto move in range = %s\n",tostring(self.autorelocate)) +text=text..string.format("Auto move dist. max = %.1f km\n",self.autorelocatemaxdist/1000) +text=text..string.format("Auto move on road = %s\n",tostring(self.autorelocateonroad)) +text=text..string.format("Marker assignments = %s\n",tostring(self.markallow)) +text=text..string.format("Marker auth. key = %s\n",tostring(self.markkey)) +text=text..string.format("Marker readonly = %s\n",tostring(self.markreadonly)) +text=text..string.format("Clusters:\n") +for _,cluster in pairs(self.clusters)do +text=text..string.format("- %s\n",tostring(cluster)) +end +text=text..string.format("******************************************************\n") +text=text..string.format("Targets:\n") +for _,target in pairs(self.targets)do +text=text..string.format("- %s\n",self:_TargetInfo(target)) +local possible=self:_CheckWeaponTypePossible(target) +if not possible then +self:E(self.lid..string.format("WARNING: Selected weapon type %s is not possible",self:_WeaponTypeName(target.weapontype))) +end +if self.Debug then +local zone=ZONE_RADIUS:New(target.name,target.coord:GetVec2(),target.radius) +zone:BoundZone(180,coalition.side.NEUTRAL) +end +end +text=text..string.format("Moves:\n") +for i=1,#self.moves do +text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i])) +end +text=text..string.format("******************************************************\n") +text=text..string.format("Shell types:\n") +for _,_type in pairs(self.ammoshells)do +text=text..string.format("- %s\n",_type) +end +text=text..string.format("Rocket types:\n") +for _,_type in pairs(self.ammorockets)do +text=text..string.format("- %s\n",_type) +end +text=text..string.format("Missile types:\n") +for _,_type in pairs(self.ammomissiles)do +text=text..string.format("- %s\n",_type) +end +text=text..string.format("******************************************************") +if self.Debug then +self:I(self.lid..text) +else +self:T(self.lid..text) +end +self.Controllable:OptionROEHoldFire() +self:HandleEvent(EVENTS.Shot) +self:HandleEvent(EVENTS.Dead) +if self.markallow then +world.addEventHandler(self) +end +self:__Status(self.StatusInterval) +end +function ARTY:_CheckDB(displayname) +for _type,_properties in pairs(ARTY.db)do +self:T({type=_type,properties=_properties}) +if _type==displayname then +self:T({type=_type,properties=_properties}) +return _properties +end +end +return nil +end +function ARTY:_StatusReport(display) +if display==nil then +display=false +end +local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() +local Nnukes=self.Nukes +local Nillu=self.Nillu +local Nsmoke=self.Nsmoke +local Tnow=timer.getTime() +local Clock=self:_SecondsToClock(timer.getAbsTime()) +local text=string.format("\n******************* STATUS ***************************\n") +text=text..string.format("ARTY group = %s\n",self.groupname) +text=text..string.format("Clock = %s\n",Clock) +text=text..string.format("FSM state = %s\n",self:GetState()) +text=text..string.format("Total ammo count = %d\n",Nammo) +text=text..string.format("Number of shells = %d\n",Nshells) +text=text..string.format("Number of rockets = %d\n",Nrockets) +text=text..string.format("Number of missiles = %d\n",Nmissiles) +text=text..string.format("Number of nukes = %d\n",Nnukes) +text=text..string.format("Number of illum. = %d\n",Nillu) +text=text..string.format("Number of smoke = %d\n",Nsmoke) +if self.currentTarget then +text=text..string.format("Current Target = %s\n",tostring(self.currentTarget.name)) +text=text..string.format("Curr. Tgt assigned = %d\n",Tnow-self.currentTarget.Tassigned) +else +text=text..string.format("Current Target = %s\n","none") +end +text=text..string.format("Nshots curr. Target = %d\n",self.Nshots) +text=text..string.format("Targets:\n") +for i=1,#self.targets do +text=text..string.format("- %s\n",self:_TargetInfo(self.targets[i])) +end +if self.currentMove then +text=text..string.format("Current Move = %s\n",tostring(self.currentMove.name)) +else +text=text..string.format("Current Move = %s\n","none") +end +text=text..string.format("Moves:\n") +for i=1,#self.moves do +text=text..string.format("- %s\n",self:_MoveInfo(self.moves[i])) +end +text=text..string.format("******************************************************") +env.info(self.lid..text) +MESSAGE:New(text,20):Clear():ToCoalitionIf(self.coalition,display) +end +function ARTY: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 _lastpos={x=0,y=0,z=0} +local function _TrackWeapon(_data) +local _weaponalive,_currpos=pcall( +function() +return _data.weapon:getPoint() +end) +self:T3(self.lid..string.format("ARTY %s: Weapon still in air: %s",self.groupname,tostring(_weaponalive))) +local _destroyweapon=false +if _weaponalive then +_lastpos={x=_currpos.x,y=_currpos.y,z=_currpos.z} +local _coord=COORDINATE:NewFromVec3(_lastpos) +local _dist=_coord:Get2DDistance(_data.target.coord) +self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m",self.groupname,_dist)) +if _data.target.weapontype==ARTY.WeaponType.IlluminationShells then +if _dist<_data.target.radius then +local _cr=_data.target.coord:GetRandomCoordinateInRadius(_data.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 _data.target.weapontype==ARTY.WeaponType.SmokeShells then +if _dist<_data.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)) +_data.weapon:destroy() +return nil +else +local dt=0.02 +self:T3(self.lid..string.format("ARTY %s tracking weapon again in %.3f seconds",self.groupname,dt)) +return timer.getTime()+dt +end +else +local _impactcoord=COORDINATE:NewFromVec3(_lastpos) +self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.",self.groupname)) +if _data.target.weapontype==ARTY.WeaponType.TacticalNukes then +self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.",self.groupname)) +SCHEDULER:New(nil,ARTY._NuclearBlast,{self,_impactcoord},1.0) +end +return nil +end +end +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 _peter={} +_peter.weapon=EventData.weapon +_peter.target=UTILS.DeepCopy(self.currentTarget) +timer.scheduleFunction(_TrackWeapon,_peter,timer.getTime()+2.0) +end +local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() +if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then +self.Nukes=self.Nukes-1 +end +if self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells then +self.Nillu=self.Nillu-1 +end +if self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells then +self.Nsmoke=self.Nsmoke-1 +end +local _outofammo=false +if _nammo==0 then +self:T(self.lid..string.format("Group %s completely out of ammo.",self.groupname)) +_outofammo=true +end +local _partlyoutofammo=self:_CheckOutOfAmmo({self.currentTarget}) +local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype) +self:T(self.lid..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d",self.groupname,_nammo,_nshells,_nrockets,_nmissiles)) +self:T(self.lid..string.format("Group %s uses weapontype %s for current target.",self.groupname,_weapontype)) +local _ceasefire=false +local _relocate=false +if self.Nshots>=self.currentTarget.nshells then +local text=string.format("Group %s stop firing on target %s.",self.groupname,self.currentTarget.name) +self:T(self.lid..text) +MESSAGE:New(text,5):ToAllIf(self.Debug) +_ceasefire=true +_relocate=self.relocateafterfire +end +if _outofammo or _partlyoutofammo then +_ceasefire=true +end +if _relocate then +self:_Relocate() +end +if _ceasefire then +self:CeaseFire(self.currentTarget) +end +else +self:E(self.lid..string.format("WARNING: No current target for group %s?!",self.groupname)) +end +end +end +end +function ARTY:onEvent(Event) +if Event==nil or Event.idx==nil then +self:T3("Skipping onEvent. Event or Event.idx unknown.") +return true +end +self:T2(string.format("Event captured = %s",tostring(self.groupname))) +self:T2(string.format("Event id = %s",tostring(Event.id))) +self:T2(string.format("Event time = %s",tostring(Event.time))) +self:T2(string.format("Event idx = %s",tostring(Event.idx))) +self:T2(string.format("Event coalition = %s",tostring(Event.coalition))) +self:T2(string.format("Event group id = %s",tostring(Event.groupID))) +self:T2(string.format("Event text = %s",tostring(Event.text))) +if Event.initiator~=nil then +local _unitname=Event.initiator:getName() +self:T2(string.format("Event ini unit name = %s",tostring(_unitname))) +end +if Event.id==world.event.S_EVENT_MARK_ADDED then +self:T2({event="S_EVENT_MARK_ADDED",battery=self.groupname,vec3=Event.pos}) +elseif Event.id==world.event.S_EVENT_MARK_CHANGE then +self:T({event="S_EVENT_MARK_CHANGE",battery=self.groupname,vec3=Event.pos}) +self:_OnEventMarkChange(Event) +elseif Event.id==world.event.S_EVENT_MARK_REMOVED then +self:T2({event="S_EVENT_MARK_REMOVED",battery=self.groupname,vec3=Event.pos}) +self:_OnEventMarkRemove(Event) +end +end +function ARTY:_OnEventMarkRemove(Event) +local batterycoalition=self.coalition +if Event.text~=nil and Event.text:find("BATTERY")then +local _cancelmove=false +local _canceltarget=false +local _name="" +local _id=nil +if Event.text:find("Marked Relocation")then +_cancelmove=true +_name=self:_MarkMoveName(Event.idx) +_id=self:_GetMoveIndexByName(_name) +elseif Event.text:find("Marked Target")then +_canceltarget=true +_name=self:_MarkTargetName(Event.idx) +_id=self:_GetTargetIndexByName(_name) +else +return +end +if _id==nil then +return +end +if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then +local _validkey=self:_MarkerKeyAuthentification(Event.text) +if _validkey then +if _cancelmove then +if self.currentMove and self.currentMove.name==_name then +self.Controllable:ClearTasks() +self:Arrived() +else +self:RemoveMove(_name) +end +elseif _canceltarget then +if self.currentTarget and self.currentTarget.name==_name then +self:CeaseFire(self.currentTarget) +self:RemoveTarget(_name) +else +self:RemoveTarget(_name) +end +end +end +end +end +end +function ARTY:_OnEventMarkChange(Event) +if Event.text~=nil and Event.text:lower():find("arty")then +local vec3={y=Event.pos.y,x=Event.pos.x,z=Event.pos.z} +local _coord=COORDINATE:NewFromVec3(vec3) +_coord.y=_coord:GetLandHeight() +local batterycoalition=self.coalition +local batteryname=self.groupname +if(batterycoalition==Event.coalition and self.markkey==nil)or self.markkey~=nil then +local _assign=self:_Markertext(Event.text) +if _assign==nil or not(_assign.engage or _assign.move or _assign.request or _assign.cancel or _assign.set)then +self:T(self.lid..string.format("WARNING: %s, no keyword ENGAGE, MOVE, REQUEST, CANCEL or SET in mark text! Command will not be executed. Text:\n%s",self.groupname,Event.text)) +return +end +local _assigned=false +if _assign.everyone then +_assigned=true +else +for _,bat in pairs(_assign.battery)do +if self.groupname==bat then +_assigned=true +end +end +for _,alias in pairs(_assign.aliases)do +if self.alias==alias then +_assigned=true +end +end +for _,bat in pairs(_assign.cluster)do +for _,cluster in pairs(self.clusters)do +if cluster==bat then +_assigned=true +end +end +end +end +if not _assigned then +self:T3(self.lid..string.format("INFO: ARTY group %s was not addressed! Mark text:\n%s",self.groupname,Event.text)) +return +else +if self.Controllable and self.Controllable:IsAlive()then +else +self:T3(self.lid..string.format("INFO: ARTY group %s was addressed but is NOT alive! Mark text:\n%s",self.groupname,Event.text)) +return +end +end +if _assign.coord then +_coord=_assign.coord +end +local _validkey=self:_MarkerKeyAuthentification(Event.text) +if _assign.request and _validkey then +if _assign.requestammo then +self:_MarkRequestAmmo() +end +if _assign.requestmoves then +self:_MarkRequestMoves() +end +if _assign.requesttargets then +self:_MarkRequestTargets() +end +if _assign.requeststatus then +self:_MarkRequestStatus() +end +if _assign.requestrearming then +self:Rearm() +end +return +end +if _assign.cancel and _validkey then +if _assign.cancelmove and self.currentMove then +self.Controllable:ClearTasks() +self:Arrived() +elseif _assign.canceltarget and self.currentTarget then +self.currentTarget.engaged=self.currentTarget.engaged+1 +self:CeaseFire(self.currentTarget) +elseif _assign.cancelrearm and self:is("Rearming")then +local nammo=self:GetAmmo() +if nammo>0 then +self:Rearmed() +else +self:Winchester() +end +end +return +end +if _assign.set and _validkey then +if _assign.setrearmingplace and self.ismobile then +self:SetRearmingPlace(_coord) +_coord:RemoveMark(Event.idx) +_coord:MarkToCoalition(string.format("Rearming place for battery %s",self.groupname),self.coalition,false,string.format("New rearming place for battery %s defined.",self.groupname)) +if self.Debug then +_coord:SmokeOrange() +end +end +if _assign.setrearminggroup then +_coord:RemoveMark(Event.idx) +local rearminggroupcoord=_assign.setrearminggroup:GetCoordinate() +rearminggroupcoord:MarkToCoalition(string.format("Rearming group for battery %s",self.groupname),self.coalition,false,string.format("New rearming group for battery %s defined.",self.groupname)) +self:SetRearmingGroup(_assign.setrearminggroup) +if self.Debug then +rearminggroupcoord:SmokeOrange() +end +end +return +end +if _validkey then +_coord:RemoveMark(Event.idx) +local _id=UTILS._MarkID+1 +if _assign.move then +local _name=self:_MarkMoveName(_id) +local text=string.format("%s, received new relocation assignment.",self.alias) +text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +local _movename=self:AssignMoveCoord(_coord,_assign.time,_assign.speed,_assign.onroad,_assign.movecanceltarget,_name,true) +if _movename~=nil then +local _mid=self:_GetMoveIndexByName(_movename) +local _move=self.moves[_mid] +local clock=tostring(self:_SecondsToClock(_move.time)) +local _markertext=_movename..string.format(", Time=%s, Speed=%d km/h, Use Roads=%s.",clock,_move.speed,tostring(_move.onroad)) +local _randomcoord=_coord:GetRandomCoordinateInRadius(100) +_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly) +else +local text=string.format("%s, relocation not possible.",self.alias) +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +end +else +local _name=self:_MarkTargetName(_id) +local text=string.format("%s, received new target assignment.",self.alias) +text=text..string.format("\nCoordinates %s",_coord:ToStringLLDMS()) +if _assign.time then +text=text..string.format("\nTime %s",_assign.time) +end +if _assign.prio then +text=text..string.format("\nPrio %d",_assign.prio) +end +if _assign.radius then +text=text..string.format("\nRadius %d m",_assign.radius) +end +if _assign.nshells then +text=text..string.format("\nShots %d",_assign.nshells) +end +if _assign.maxengage then +text=text..string.format("\nEngagements %d",_assign.maxengage) +end +if _assign.weapontype then +text=text..string.format("\nWeapon %s",self:_WeaponTypeName(_assign.weapontype)) +end +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +local _targetname=self:AssignTargetCoord(_coord,_assign.prio,_assign.radius,_assign.nshells,_assign.maxengage,_assign.time,_assign.weapontype,_name,true) +if _targetname~=nil then +local _tid=self:_GetTargetIndexByName(_targetname) +local _target=self.targets[_tid] +local clock=tostring(self:_SecondsToClock(_target.time)) +local weapon=self:_WeaponTypeName(_target.weapontype) +local _markertext=_targetname..string.format(", Priority=%d, Radius=%d m, Shots=%d, Engagements=%d, Weapon=%s, Time=%s",_target.prio,_target.radius,_target.nshells,_target.maxengage,weapon,clock) +local _randomcoord=_coord:GetRandomCoordinateInRadius(250) +_randomcoord:MarkToCoalition(_markertext,batterycoalition,self.markreadonly or _assign.readonly) +end +end +end +end +end +end +function ARTY:OnEventDead(EventData) +self:F(EventData) +local _name=self.groupname +if EventData and EventData.IniGroupName and EventData.IniGroupName==_name then +local unitname=tostring(EventData.IniUnitName) +self:T(self.lid..string.format("%s: Captured dead event for unit %s.",_name,unitname)) +self:Dead(unitname) +end +end +function ARTY:onafterStatus(Controllable,From,Event,To) +self:_EventFromTo("onafterStatus",Event,From,To) +local nammo,nshells,nrockets,nmissiles=self:GetAmmo() +if self.iscargo and self.cargogroup then +if self.cargogroup:IsLoaded()and not self:is("InTransit")then +self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.",self.alias)) +self:Loaded() +elseif self.cargogroup:IsUnLoaded()then +self:T(self.lid..string.format("Group %s has been unloaded from the carrier.",self.alias)) +self:UnLoaded() +end +end +local fsmstate=self:GetState() +self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d",fsmstate,nammo,nshells,self.Nsmoke,self.Nillu,self.Nukes,self.nukewarhead/1000000,nrockets,nmissiles)) +if self.Controllable and self.Controllable:IsAlive()then +if self.Debug then +self:_StatusReport() +end +if self:is("Moving")then +self:T2(self.lid..string.format("%s: Moving",Controllable:GetName())) +end +if self:is("Rearming")then +local _rearmed=self:_CheckRearmed() +if _rearmed then +self:T2(self.lid..string.format("%s: Rearming ==> Rearmed",Controllable:GetName())) +self:Rearmed() +end +end +if self:is("Rearmed")then +local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) +self:T2(self.lid..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m",Controllable:GetName(),distance)) +if distance<=self.RearmingDistance then +self:T2(self.lid..string.format("%s: Rearmed ==> CombatReady",Controllable:GetName())) +self:CombatReady() +end +end +if self:is("Arrived")then +self:T2(self.lid..string.format("%s: Arrived ==> CombatReady",Controllable:GetName())) +self:CombatReady() +end +if self:is("Firing")then +self:_CheckShootingStarted() +end +self:_CheckTargetsInRange() +local notpossible={} +for i=1,#self.targets do +local _target=self.targets[i] +local possible=self:_CheckWeaponTypePossible(_target) +if not possible then +table.insert(notpossible,_target.name) +end +end +for _,targetname in pairs(notpossible)do +self:E(self.lid..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.",self.groupname,targetname)) +self:RemoveTarget(targetname) +end +local _timedTarget=self:_CheckTimedTargets() +local _normalTarget=self:_CheckNormalTargets() +local _move=self:_CheckMoves() +if _move then +self:Move(_move) +elseif _timedTarget then +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +self:OpenFire(_timedTarget) +elseif _normalTarget then +self:OpenFire(_normalTarget) +end +local gotsome=false +if#self.targets>0 then +for i=1,#self.targets do +local _target=self.targets[i] +if self:_CheckWeaponTypeAvailable(_target)>0 then +gotsome=true +end +end +else +gotsome=true +end +if(nammo==0 or not gotsome)and not(self:is("Moving")or self:is("Rearming")or self:is("OutOfAmmo"))then +self:Winchester() +end +if self:is("OutOfAmmo")then +self:T2(self.lid..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming",Controllable:GetName())) +self:Rearm() +end +self:__Status(self.StatusInterval) +elseif self.iscargo then +if self.cargogroup and self.cargogroup:IsAlive()then +if self:is("InTransit")then +self:__Status(-5) +end +end +else +self:E(self.lid..string.format("Arty group %s is not alive!",self.groupname)) +end +end +function ARTY:onbeforeLoaded(Controllable,From,Event,To) +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +return true +end +function ARTY:onafterUnLoaded(Controllable,From,Event,To) +self:CombatReady() +end +function ARTY:onenterCombatReady(Controllable,From,Event,To) +self:_EventFromTo("onenterCombatReady",Event,From,To) +self:T3(self.lid..string.format("onenterComabReady, from=%s, event=%s, to=%s",From,Event,To)) +end +function ARTY:onbeforeOpenFire(Controllable,From,Event,To,target) +self:_EventFromTo("onbeforeOpenFire",Event,From,To) +if self.currentTarget then +self:E(self.lid..string.format("ERROR: Group %s already has a target %s!",self.groupname,self.currentTarget.name)) +return false +end +if not self:_TargetInRange(target)then +self:E(self.lid..string.format("ERROR: Group %s, target %s is out of range!",self.groupname,self.currentTarget.name)) +return false +end +local nfire=self:_CheckWeaponTypeAvailable(target) +target.nshells=math.min(target.nshells,nfire) +if target.nshells<1 then +local text=string.format("%s, no ammo left to engage target %s with selected weapon type %s.") +return false +end +return true +end +function ARTY:onafterOpenFire(Controllable,From,Event,To,target) +self:_EventFromTo("onafterOpenFire",Event,From,To) +local id=self:_GetTargetIndexByName(target.name) +if id then +self.targets[id].underfire=true +self.currentTarget=target +self.currentTarget.Tassigned=timer.getTime() +end +local range=Controllable:GetCoordinate():Get2DDistance(target.coord) +local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() +local nfire=Nammo +local _type="shots" +if target.weapontype==ARTY.WeaponType.Auto then +nfire=Nammo +_type="shots" +elseif target.weapontype==ARTY.WeaponType.Cannon then +nfire=Nshells +_type="shells" +elseif target.weapontype==ARTY.WeaponType.TacticalNukes then +nfire=self.Nukes +_type="nuclear shells" +elseif target.weapontype==ARTY.WeaponType.IlluminationShells then +nfire=self.Nillu +_type="illumination shells" +elseif target.weapontype==ARTY.WeaponType.SmokeShells then +nfire=self.Nsmoke +_type="smoke shells" +elseif target.weapontype==ARTY.WeaponType.Rockets then +nfire=Nrockets +_type="rockets" +elseif target.weapontype==ARTY.WeaponType.CruiseMissile then +nfire=Nmissiles +_type="cruise missiles" +end +target.nshells=math.min(target.nshells,nfire) +local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.",Controllable:GetName(),target.name,target.nshells,_type,range/1000) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report) +if target.attackgroup then +self:_AttackGroup(target) +else +self:_FireAtCoord(target.coord,target.radius,target.nshells,target.weapontype) +end +end +function ARTY:onafterCeaseFire(Controllable,From,Event,To,target) +self:_EventFromTo("onafterCeaseFire",Event,From,To) +if target then +local text=string.format("%s, ceasing fire on target %s.",Controllable:GetName(),target.name) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report) +local id=self:_GetTargetIndexByName(target.name) +if id then +if self.Nshots>0 then +self.targets[id].engaged=self.targets[id].engaged+1 +self.targets[id].time=nil +end +self.targets[id].underfire=false +end +if target.engaged>=target.maxengage then +self:RemoveTarget(target.name) +end +self.Controllable:OptionROEHoldFire() +self.Controllable:ClearTasks() +else +self:E(self.lid..string.format("ERROR: No target in cease fire for group %s.",self.groupname)) +end +self.Nshots=0 +self.currentTarget=nil +end +function ARTY:onafterWinchester(Controllable,From,Event,To) +self:_EventFromTo("onafterWinchester",Event,From,To) +local text=string.format("%s, winchester!",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +end +function ARTY:onbeforeRearm(Controllable,From,Event,To) +self:_EventFromTo("onbeforeRearm",Event,From,To) +local _rearmed=self:_CheckRearmed() +if _rearmed then +self:T(self.lid..string.format("%s, group is already armed to the teeth. Rearming request denied!",self.groupname)) +return false +else +self:T(self.lid..string.format("%s, group might be rearmed.",self.groupname)) +end +if self.RearmingGroup and self.RearmingGroup:IsAlive()then +return true +elseif self.RearmingPlaceCoord then +return true +else +return false +end +end +function ARTY:onafterRearm(Controllable,From,Event,To) +self:_EventFromTo("onafterRearm",Event,From,To) +local coordARTY=self.Controllable:GetCoordinate() +self.InitialCoord=coordARTY +local coordRARM=nil +if self.RearmingGroup then +coordRARM=self.RearmingGroup:GetCoordinate() +self.RearmingGroupCoord=coordRARM +end +if self.RearmingGroup and self.RearmingPlaceCoord and self.ismobile then +local text=string.format("%s, %s, request rearming at rearming place.",Controllable:GetName(),self.RearmingGroup:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) +local dR=coordRARM:Get2DDistance(self.RearmingPlaceCoord) +if dA>self.RearmingDistance then +local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2) +self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true) +end +if dR>self.RearmingDistance then +local ToCoord=self:_VicinityCoord(self.RearmingPlaceCoord,self.RearmingDistance/4,self.RearmingDistance/2) +self:_Move(self.RearmingGroup,ToCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad) +end +elseif self.RearmingGroup then +local text=string.format("%s, %s, request rearming.",Controllable:GetName(),self.RearmingGroup:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +local distance=coordARTY:Get2DDistance(coordRARM) +if distance>self.RearmingDistance then +self:_Move(self.RearmingGroup,self:_VicinityCoord(coordARTY),self.RearmingGroupSpeed,self.RearmingGroupOnRoad) +end +elseif self.RearmingPlaceCoord then +local text=string.format("%s, moving to rearming place.",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) +if dA>self.RearmingDistance then +local _tocoord=self:_VicinityCoord(self.RearmingPlaceCoord) +self:AssignMoveCoord(_tocoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE TO REARMING PLACE",true) +end +end +end +function ARTY:onafterRearmed(Controllable,From,Event,To) +self:_EventFromTo("onafterRearmed",Event,From,To) +local text=string.format("%s, rearming complete.",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +self.Nukes=self.Nukes0 +self.Nillu=self.Nillu0 +self.Nsmoke=self.Nsmoke0 +local dist=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) +if dist>self.RearmingDistance then +self:AssignMoveCoord(self.InitialCoord,nil,nil,self.RearmingArtyOnRoad,false,"REARMING MOVE REARMING COMPLETE",true) +end +if self.RearmingGroup and self.RearmingGroup:IsAlive()then +local d=self.RearmingGroup:GetCoordinate():Get2DDistance(self.RearmingGroupCoord) +if d>self.RearmingDistance then +self:_Move(self.RearmingGroup,self.RearmingGroupCoord,self.RearmingGroupSpeed,self.RearmingGroupOnRoad) +else +self.RearmingGroup:ClearTasks() +end +end +end +function ARTY:_CheckRearmed() +self:F2() +local nammo,nshells,nrockets,nmissiles=self:GetAmmo() +local units=self.Controllable:GetUnits() +local nunits=0 +if units then +nunits=#units +end +local FullAmmo=self.Nammo0*nunits/self.IniGroupStrength +local _rearmpc=nammo/FullAmmo*100 +if _rearmpc>1 then +local text=string.format("%s, rearming %d %% complete.",self.alias,_rearmpc) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +end +if nammo>=FullAmmo then +return true +else +return false +end +end +function ARTY:onbeforeMove(Controllable,From,Event,To,move) +self:_EventFromTo("onbeforeMove",Event,From,To) +if not self.ismobile then +return false +end +if self.currentTarget then +if move.cancel then +self:CeaseFire(self.currentTarget) +else +return false +end +end +return true +end +function ARTY:onafterMove(Controllable,From,Event,To,move) +self:_EventFromTo("onafterMove",Event,From,To) +self.Controllable:OptionAlarmStateGreen() +self.Controllable:OptionROEHoldFire() +local _Speed=math.min(move.speed,self.SpeedMax) +if self.Debug then +move.coord:SmokeRed() +end +self.currentMove=move +self:_Move(self.Controllable,move.coord,move.speed,move.onroad) +end +function ARTY:onafterArrived(Controllable,From,Event,To) +self:_EventFromTo("onafterArrived",Event,From,To) +self.Controllable:OptionAlarmStateAuto() +local text=string.format("%s, arrived at destination.",Controllable:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToCoalitionIf(self.coalition,self.report or self.Debug) +if self.currentMove then +self:RemoveMove(self.currentMove.name) +self.currentMove=nil +end +end +function ARTY:onafterNewTarget(Controllable,From,Event,To,target) +self:_EventFromTo("onafterNewTarget",Event,From,To) +local text=string.format("Adding new target %s.",target.name) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self:T(self.lid..text) +end +function ARTY:onafterNewMove(Controllable,From,Event,To,move) +self:_EventFromTo("onafterNewTarget",Event,From,To) +local text=string.format("Adding new move %s.",move.name) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self:T(self.lid..text) +end +function ARTY:onafterDead(Controllable,From,Event,To,Unitname) +self:_EventFromTo("onafterDead",Event,From,To) +local nunits=self.Controllable:CountAliveUnits() +local text=string.format("%s, our unit %s just died! %d units left.",self.groupname,Unitname,nunits) +MESSAGE:New(text,5):ToAllIf(self.Debug) +self:I(self.lid..text) +if nunits==0 then +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +if self.respawnafterdeath then +if not self.respawning then +self.respawning=true +self:__Respawn(self.respawndelay or 1) +end +else +self:Stop() +end +end +end +function ARTY:onafterRespawn(Controllable,From,Event,To) +self:_EventFromTo("onafterRespawn",Event,From,To) +env.info("FF Respawning arty group") +local group=self.Controllable +self.Controllable=group:Respawn() +self.respawning=false +self:__Status(-1) +end +function ARTY:onafterStop(Controllable,From,Event,To) +self:_EventFromTo("onafterStop",Event,From,To) +self:I(self.lid..string.format("Stopping ARTY FSM for group %s.",tostring(Controllable:GetName()))) +if self.currentTarget then +self:CeaseFire(self.currentTarget) +end +self:UnHandleEvent(EVENTS.Shot) +self:UnHandleEvent(EVENTS.Dead) +end +function ARTY:_FireAtCoord(coord,radius,nshells,weapontype) +self:F({coord=coord,radius=radius,nshells=nshells}) +local group=self.Controllable +if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then +weapontype=ARTY.WeaponType.Cannon +end +group:OptionROEOpenFire() +local vec2=coord:GetVec2() +local fire=group:TaskFireAtPoint(vec2,radius,nshells,weapontype) +group:SetTask(fire) +end +function ARTY:_AttackGroup(target) +local group=self.Controllable +local weapontype=target.weapontype +if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then +weapontype=ARTY.WeaponType.Cannon +end +group:OptionROEOpenFire() +local targetgroup=GROUP:FindByName(target.name) +local fire=group:TaskAttackGroup(targetgroup,weapontype,AI.Task.WeaponExpend.ONE,1) +group:SetTask(fire) +end +function ARTY:_NuclearBlast(_coord) +local S0=self.nukewarhead +local R0=self.nukerange +local N0=self.nukefires +_coord:Explosion(S0) +_coord:BigSmokeAndFireHuge() +local _fires={} +for i=1,N0 do +local _fire=_coord:GetRandomCoordinateInRadius(R0) +local _dist=_fire:Get2DDistance(_coord) +table.insert(_fires,{distance=_dist,coord=_fire}) +end +local _sort=function(a,b)return a.distance_nmax +if _gotit then +self:AssignMoveCoord(_new,nil,nil,false,false,"RELOCATION MOVE AFTER FIRING") +end +end +function ARTY:GetAmmo(display) +self:F3({display=display}) +if display==nil then +display=false +end +local nammo=0 +local nshells=0 +local nrockets=0 +local nmissiles=0 +local units=self.Controllable:GetUnits() +if units==nil then +return nammo,nshells,nrockets,nmissiles +end +for _,unit in pairs(units)do +if unit and unit:IsAlive()then +local text=string.format("ARTY group %s - unit %s:\n",self.groupname,unit:GetName()) +local ammotable=unit:GetAmmo() +if ammotable~=nil then +local weapons=#ammotable +if display then +self:I(self.lid..string.format("Number of weapons %d.",weapons)) +self:I({ammotable=ammotable}) +self:I(self.lid.."Ammotable:") +for id,bla in pairs(ammotable)do +self:I({id=id,ammo=bla}) +end +end +for w=1,weapons do +local Nammo=ammotable[w]["count"] +local Tammo=ammotable[w]["desc"]["typeName"] +local _weaponString=self:_split(Tammo,"%.") +local _weaponName=_weaponString[#_weaponString] +local Category=ammotable[w].desc.category +local MissileCategory=nil +if Category==Weapon.Category.MISSILE then +MissileCategory=ammotable[w].desc.missileCategory +end +local _gotshell=false +if#self.ammoshells>0 then +for _,_type in pairs(self.ammoshells)do +if string.match(Tammo,_type)and Category==Weapon.Category.SHELL then +_gotshell=true +end +end +else +if Category==Weapon.Category.SHELL then +_gotshell=true +end +end +local _gotrocket=false +if#self.ammorockets>0 then +for _,_type in pairs(self.ammorockets)do +if string.match(Tammo,_type)and Category==Weapon.Category.ROCKET then +_gotrocket=true +end +end +else +if Category==Weapon.Category.ROCKET then +_gotrocket=true +end +end +local _gotmissile=false +if#self.ammomissiles>0 then +for _,_type in pairs(self.ammomissiles)do +if string.match(Tammo,_type)and Category==Weapon.Category.MISSILE then +_gotmissile=true +end +end +else +if Category==Weapon.Category.MISSILE then +_gotmissile=true +end +end +if _gotshell then +nshells=nshells+Nammo +text=text..string.format("- %d shells of type %s\n",Nammo,_weaponName) +elseif _gotrocket then +nrockets=nrockets+Nammo +text=text..string.format("- %d rockets of type %s\n",Nammo,_weaponName) +elseif _gotmissile then +if MissileCategory==Weapon.MissileCategory.CRUISE then +nmissiles=nmissiles+Nammo +end +text=text..string.format("- %d %s missiles of type %s\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName) +else +text=text..string.format("- %d unknown ammo of type %s (category=%d, missile category=%s)\n",Nammo,Tammo,Category,tostring(MissileCategory)) +end +end +end +if display then +self:I(self.lid..text) +else +self:T3(self.lid..text) +end +MESSAGE:New(text,10):ToAllIf(display) +end +end +nammo=nshells+nrockets+nmissiles +return nammo,nshells,nrockets,nmissiles +end +function ARTY:_MissileCategoryName(categorynumber) +local cat="unknown" +if categorynumber==Weapon.MissileCategory.AAM then +cat="air-to-air" +elseif categorynumber==Weapon.MissileCategory.SAM then +cat="surface-to-air" +elseif categorynumber==Weapon.MissileCategory.BM then +cat="ballistic" +elseif categorynumber==Weapon.MissileCategory.ANTI_SHIP then +cat="anti-ship" +elseif categorynumber==Weapon.MissileCategory.CRUISE then +cat="cruise" +elseif categorynumber==Weapon.MissileCategory.OTHER then +cat="other" +end +return cat +end +function ARTY:_MarkerKeyAuthentification(text) +local batterycoalition=self.coalition +local mykey=nil +if self.markkey~=nil then +local keywords=self:_split(text,",") +for _,key in pairs(keywords)do +local s=self:_split(key," ") +local val=s[2] +if key:lower():find("key")then +mykey=tonumber(val) +self:T(self.lid..string.format("Authorisation Key=%s.",val)) +end +end +end +local _validkey=true +if self.markkey~=nil then +_validkey=false +if mykey~=nil then +_validkey=self.markkey==mykey +end +self:T2(self.lid..string.format("%s, authkey=%s == %s=playerkey ==> valid=%s",self.groupname,tostring(self.markkey),tostring(mykey),tostring(_validkey))) +local text="" +if mykey==nil then +text=string.format("%s, authorization required but did not receive a key!",self.alias) +elseif _validkey==false then +text=string.format("%s, authorization required but did receive an incorrect key (key=%s)!",self.alias,tostring(mykey)) +elseif _validkey==true then +text=string.format("%s, authentification successful!",self.alias) +end +MESSAGE:New(text,10):ToCoalitionIf(batterycoalition,self.report or self.Debug) +end +return _validkey +end +function ARTY:_Markertext(text) +self:F(text) +local assignment={} +assignment.battery={} +assignment.aliases={} +assignment.cluster={} +assignment.everyone=false +assignment.move=false +assignment.engage=false +assignment.request=false +assignment.cancel=false +assignment.set=false +assignment.readonly=false +assignment.movecanceltarget=false +assignment.cancelmove=false +assignment.canceltarget=false +assignment.cancelrearm=false +assignment.setrearmingplace=false +assignment.setrearminggroup=false +if text:lower():find("arty engage")or text:lower():find("arty attack")then +assignment.engage=true +elseif text:lower():find("arty move")or text:lower():find("arty relocate")then +assignment.move=true +elseif text:lower():find("arty request")then +assignment.request=true +elseif text:lower():find("arty cancel")then +assignment.cancel=true +elseif text:lower():find("arty set")then +assignment.set=true +else +self:E(self.lid..'ERROR: Neither "ARTY ENGAGE" nor "ARTY MOVE" nor "ARTY RELOCATE" nor "ARTY REQUEST" nor "ARTY CANCEL" nor "ARTY SET" keyword specified!') +return nil +end +local keywords=self:_split(text,",") +self:T({keywords=keywords}) +for _,keyphrase in pairs(keywords)do +local str=self:_split(keyphrase," ") +local key=str[1] +local val=str[2] +self:T3(self.lid..string.format("%s, keyphrase = %s, key = %s, val = %s",self.groupname,tostring(keyphrase),tostring(key),tostring(val))) +if key:lower():find("battery")then +local v=self:_split(keyphrase,'"') +for i=2,#v,2 do +table.insert(assignment.battery,v[i]) +self:T2(self.lid..string.format("Key Battery=%s.",v[i])) +end +elseif key:lower():find("alias")then +local v=self:_split(keyphrase,'"') +for i=2,#v,2 do +table.insert(assignment.aliases,v[i]) +self:T2(self.lid..string.format("Key Aliases=%s.",v[i])) +end +elseif key:lower():find("cluster")then +local v=self:_split(keyphrase,'"') +for i=2,#v,2 do +table.insert(assignment.cluster,v[i]) +self:T2(self.lid..string.format("Key Cluster=%s.",v[i])) +end +elseif keyphrase:lower():find("everyone")or keyphrase:lower():find("all batteries")or keyphrase:lower():find("allbatteries")then +assignment.everyone=true +self:T(self.lid..string.format("Key Everyone=true.")) +elseif keyphrase:lower():find("irrevocable")or keyphrase:lower():find("readonly")then +assignment.readonly=true +self:T2(self.lid..string.format("Key Readonly=true.")) +elseif(assignment.engage or assignment.move)and key:lower():find("time")then +if val:lower():find("now")then +assignment.time=self:_SecondsToClock(timer.getTime0()+2) +else +assignment.time=val +end +self:T2(self.lid..string.format("Key Time=%s.",val)) +elseif assignment.engage and key:lower():find("shot")then +assignment.nshells=tonumber(val) +self:T(self.lid..string.format("Key Shot=%s.",val)) +elseif assignment.engage and key:lower():find("prio")then +assignment.prio=tonumber(val) +self:T2(string.format("Key Prio=%s.",val)) +elseif assignment.engage and key:lower():find("maxengage")then +assignment.maxengage=tonumber(val) +self:T2(self.lid..string.format("Key Maxengage=%s.",val)) +elseif assignment.engage and key:lower():find("radius")then +assignment.radius=tonumber(val) +self:T2(self.lid..string.format("Key Radius=%s.",val)) +elseif assignment.engage and key:lower():find("weapon")then +if val:lower():find("cannon")then +assignment.weapontype=ARTY.WeaponType.Cannon +elseif val:lower():find("rocket")then +assignment.weapontype=ARTY.WeaponType.Rockets +elseif val:lower():find("missile")then +assignment.weapontype=ARTY.WeaponType.CruiseMissile +elseif val:lower():find("nuke")then +assignment.weapontype=ARTY.WeaponType.TacticalNukes +elseif val:lower():find("illu")then +assignment.weapontype=ARTY.WeaponType.IlluminationShells +elseif val:lower():find("smoke")then +assignment.weapontype=ARTY.WeaponType.SmokeShells +else +assignment.weapontype=ARTY.WeaponType.Auto +end +self:T2(self.lid..string.format("Key Weapon=%s.",val)) +elseif(assignment.move or assignment.set)and key:lower():find("speed")then +assignment.speed=tonumber(val) +self:T2(self.lid..string.format("Key Speed=%s.",val)) +elseif(assignment.move or assignment.set)and(keyphrase:lower():find("on road")or keyphrase:lower():find("onroad")or keyphrase:lower():find("use road"))then +assignment.onroad=true +self:T2(self.lid..string.format("Key Onroad=true.")) +elseif assignment.move and(keyphrase:lower():find("cancel target")or keyphrase:lower():find("canceltarget"))then +assignment.movecanceltarget=true +self:T2(self.lid..string.format("Key Cancel Target (before move)=true.")) +elseif assignment.request and keyphrase:lower():find("rearm")then +assignment.requestrearming=true +self:T2(self.lid..string.format("Key Request Rearming=true.")) +elseif assignment.request and keyphrase:lower():find("ammo")then +assignment.requestammo=true +self:T2(self.lid..string.format("Key Request Ammo=true.")) +elseif assignment.request and keyphrase:lower():find("target")then +assignment.requesttargets=true +self:T2(self.lid..string.format("Key Request Targets=true.")) +elseif assignment.request and keyphrase:lower():find("status")then +assignment.requeststatus=true +self:T2(self.lid..string.format("Key Request Status=true.")) +elseif assignment.request and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then +assignment.requestmoves=true +self:T2(self.lid..string.format("Key Request Moves=true.")) +elseif assignment.cancel and(keyphrase:lower():find("engagement")or keyphrase:lower():find("attack")or keyphrase:lower():find("target"))then +assignment.canceltarget=true +self:T2(self.lid..string.format("Key Cancel Target=true.")) +elseif assignment.cancel and(keyphrase:lower():find("move")or keyphrase:lower():find("relocation"))then +assignment.cancelmove=true +self:T2(self.lid..string.format("Key Cancel Move=true.")) +elseif assignment.cancel and keyphrase:lower():find("rearm")then +assignment.cancelrearm=true +self:T2(self.lid..string.format("Key Cancel Rearm=true.")) +elseif assignment.set and keyphrase:lower():find("rearming place")then +assignment.setrearmingplace=true +self:T(self.lid..string.format("Key Set Rearming Place=true.")) +elseif assignment.set and keyphrase:lower():find("rearming group")then +local v=self:_split(keyphrase,'"') +local groupname=v[2] +local group=GROUP:FindByName(groupname) +if group and group:IsAlive()then +assignment.setrearminggroup=group +end +self:T2(self.lid..string.format("Key Set Rearming Group = %s.",tostring(groupname))) +elseif key:lower():find("lldms")then +local _flat="%d+:%d+:%d+%s*[N,S]" +local _flon="%d+:%d+:%d+%s*[W,E]" +local _lat=keyphrase:match(_flat) +local _lon=keyphrase:match(_flon) +self:T2(self.lid..string.format("Key LLDMS: lat=%s, long=%s format=DMS",_lat,_lon)) +if _lat and _lon then +local _latitude,_longitude=self:_LLDMS2DD(_lat,_lon) +self:T2(self.lid..string.format("Key LLDMS: lat=%.3f, long=%.3f format=DD",_latitude,_longitude)) +if _latitude and _longitude then +assignment.coord=COORDINATE:NewFromLLDD(_latitude,_longitude) +end +end +end +end +return assignment +end +function ARTY:_MarkRequestAmmo() +self:GetAmmo(true) +end +function ARTY:_MarkRequestStatus() +self:_StatusReport(true) +end +function ARTY:_MarkRequestMoves() +local text=string.format("%s, relocations:",self.groupname) +if#self.moves>0 then +for _,move in pairs(self.moves)do +if self.currentMove and move.name==self.currentMove.name then +text=text..string.format("\n- %s (current)",self:_MoveInfo(move)) +else +text=text..string.format("\n- %s",self:_MoveInfo(move)) +end +end +else +text=text..string.format("\n- no queued relocations") +end +MESSAGE:New(text,20):Clear():ToCoalition(self.coalition) +end +function ARTY:_MarkRequestTargets() +local text=string.format("%s, targets:",self.groupname) +if#self.targets>0 then +for _,target in pairs(self.targets)do +if self.currentTarget and target.name==self.currentTarget.name then +text=text..string.format("\n- %s (current)",self:_TargetInfo(target)) +else +text=text..string.format("\n- %s",self:_TargetInfo(target)) +end +end +else +text=text..string.format("\n- no queued targets") +end +MESSAGE:New(text,20):Clear():ToCoalition(self.coalition) +end +function ARTY:_MarkTargetName(markerid) +return string.format("BATTERY=%s, Marked Target ID=%d",self.groupname,markerid) +end +function ARTY:_MarkMoveName(markerid) +return string.format("BATTERY=%s, Marked Relocation ID=%d",self.groupname,markerid) +end +function ARTY:_GetMarkIDfromName(name) +local keywords=self:_split(name,",") +local battery=nil +local markTID=nil +local markMID=nil +for _,key in pairs(keywords)do +local str=self:_split(key,"=") +local par=str[1] +local val=str[2] +if par:find("BATTERY")then +battery=val +end +if par:find("Marked Target ID")then +markTID=tonumber(val) +end +if par:find("Marked Relocation ID")then +markMID=tonumber(val) +end +end +return battery,markTID,markMID +end +function ARTY:_SortTargetQueuePrio() +self:F2() +local function _sort(a,b) +return(a.engaged_target.engaged and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then +self:T2(self.lid..string.format("Found NORMAL target %s",self:_TargetInfo(_target))) +return _target +end +end +return nil +end +function ARTY:_CheckTimedTargets() +self:F3() +local Tnow=timer.getAbsTime() +self:_SortQueueTime(self.targets) +if self:is("Rearming")then +return nil +end +for i=1,#self.targets do +local _target=self.targets[i] +self:T3(self.lid..string.format("Check TIMED target %d: %s",i,self:_TargetInfo(_target))) +if _target.time and Tnow>=_target.time and _target.underfire==false and self:_TargetInRange(_target)and self:_CheckWeaponTypeAvailable(_target)>0 then +if self.currentTarget then +if self.currentTarget.prio>_target.prio then +self:T2(self.lid..string.format("Found TIMED HIGH PRIO target %s.",self:_TargetInfo(_target))) +return _target +end +else +self:T2(self.lid..string.format("Found TIMED target %s.",self:_TargetInfo(_target))) +return _target +end +end +end +return nil +end +function ARTY:_CheckMoves() +self:F3() +local Tnow=timer.getAbsTime() +self:_SortQueueTime(self.moves) +local firing=false +if self.currentTarget then +firing=true +end +for i=1,#self.moves do +local _move=self.moves[i] +if string.find(_move.name,"REARMING MOVE")and((self.currentMove and self.currentMove.name~=_move.name)or self.currentMove==nil)then +return _move +elseif(Tnow>=_move.time)and(firing==false or _move.cancel)and(not self.currentMove)and(not self:is("Rearming"))then +return _move +end +end +return nil +end +function ARTY:_CheckShootingStarted() +self:F2() +if self.currentTarget then +local Tnow=timer.getTime() +local name=self.currentTarget.name +local dt=Tnow-self.currentTarget.Tassigned +if self.Nshots==0 then +self:T(self.lid..string.format("%s, waiting for %d seconds for first shot on target %s.",self.groupname,dt,name)) +end +if dt>self.WaitForShotTime and(self.Nshots==0 or self.currentTarget.nshells>=self.Nshots)then +self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.",self.groupname,self.WaitForShotTime,name)) +self:CeaseFire(self.currentTarget) +self:RemoveTarget(name) +end +end +end +function ARTY:_GetTargetIndexByName(name) +self:F2(name) +for i=1,#self.targets do +local targetname=self.targets[i].name +self:T3(self.lid..string.format("Have target with name %s. Index = %d",targetname,i)) +if targetname==name then +self:T2(self.lid..string.format("Found target with name %s. Index = %d",name,i)) +return i +end +end +self:T2(self.lid..string.format("WARNING: Target with name %s could not be found. (This can happen.)",name)) +return nil +end +function ARTY:_GetMoveIndexByName(name) +self:F2(name) +for i=1,#self.moves do +local movename=self.moves[i].name +self:T3(self.lid..string.format("Have move with name %s. Index = %d",movename,i)) +if movename==name then +self:T2(self.lid..string.format("Found move with name %s. Index = %d",name,i)) +return i +end +end +self:T2(self.lid..string.format("WARNING: Move with name %s could not be found. (This can happen.)",name)) +return nil +end +function ARTY:_CheckOutOfAmmo(targets) +local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo() +local _partlyoutofammo=false +for _,Target in pairs(targets)do +if Target.weapontype==ARTY.WeaponType.Auto and _nammo==0 then +self:T(self.lid..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then +self:T(self.lid..string.format("Group %s, cannons requested for target %s but shells empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes<=0 then +self:T(self.lid..string.format("Group %s, tactical nukes requested for target %s but nukes empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu<=0 then +self:T(self.lid..string.format("Group %s, illumination shells requested for target %s but illumination shells empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke<=0 then +self:T(self.lid..string.format("Group %s, smoke shells requested for target %s but smoke shells empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.Rockets and _nrockets==0 then +self:T(self.lid..string.format("Group %s, rockets requested for target %s but rockets empty.",self.groupname,Target.name)) +_partlyoutofammo=true +elseif Target.weapontype==ARTY.WeaponType.CruiseMissile and _nmissiles==0 then +self:T(self.lid..string.format("Group %s, cruise missiles requested for target %s but all missiles empty.",self.groupname,Target.name)) +_partlyoutofammo=true +end +end +return _partlyoutofammo +end +function ARTY:_CheckWeaponTypeAvailable(target) +local Nammo,Nshells,Nrockets,Nmissiles=self:GetAmmo() +local nfire=Nammo +if target.weapontype==ARTY.WeaponType.Auto then +nfire=Nammo +elseif target.weapontype==ARTY.WeaponType.Cannon then +nfire=Nshells +elseif target.weapontype==ARTY.WeaponType.TacticalNukes then +nfire=self.Nukes +elseif target.weapontype==ARTY.WeaponType.IlluminationShells then +nfire=self.Nillu +elseif target.weapontype==ARTY.WeaponType.SmokeShells then +nfire=self.Nsmoke +elseif target.weapontype==ARTY.WeaponType.Rockets then +nfire=Nrockets +elseif target.weapontype==ARTY.WeaponType.CruiseMissile then +nfire=Nmissiles +end +return nfire +end +function ARTY:_CheckWeaponTypePossible(target) +local possible=false +if target.weapontype==ARTY.WeaponType.Auto then +possible=self.Nammo0>0 +elseif target.weapontype==ARTY.WeaponType.Cannon then +possible=self.Nshells0>0 +elseif target.weapontype==ARTY.WeaponType.TacticalNukes then +possible=self.Nukes0>0 +elseif target.weapontype==ARTY.WeaponType.IlluminationShells then +possible=self.Nillu0>0 +elseif target.weapontype==ARTY.WeaponType.SmokeShells then +possible=self.Nsmoke0>0 +elseif target.weapontype==ARTY.WeaponType.Rockets then +possible=self.Nrockets0>0 +elseif target.weapontype==ARTY.WeaponType.CruiseMissile then +possible=self.Nmissiles0>0 +end +return possible +end +function ARTY:_CheckName(givennames,name,makeunique) +self:F2({givennames=givennames,name=name}) +local newname=name +local counter=1 +local n=1 +local nmax=100 +if makeunique==nil then +makeunique=true +end +repeat +local _unique=true +for _,_target in pairs(givennames)do +local _givenname=_target.name +if _givenname==newname then +_unique=false +end +self:T3(self.lid..string.format("%d: givenname = %s, newname=%s, unique = %s, makeunique = %s",n,tostring(_givenname),newname,tostring(_unique),tostring(makeunique))) +end +if _unique==false and makeunique==true then +newname=string.format("%s #%02d",name,counter) +counter=counter+1 +end +if _unique==false and makeunique==false then +self:T3(self.lid..string.format("Name %s is not unique. Return false.",tostring(newname))) +return name,false +end +n=n+1 +until(_unique or n==nmax) +self:T3(self.lid..string.format("Original name %s, new name = %s",name,newname)) +return newname,true +end +function ARTY:_TargetInRange(target,message) +self:F3(target) +if message==nil then +message=false +end +self:T3({controllable=self.Controllable,targetcoord=target.coord}) +local _dist=self.Controllable:GetCoordinate():Get2DDistance(target.coord) +local _inrange=true +local _tooclose=false +local _toofar=false +local text="" +if _distself.maxrange then +_inrange=false +_toofar=true +text=string.format("%s, target is out of range. Distance of %.1f km is greater than max range of %.1f km.",self.alias,_dist/1000,self.maxrange/1000) +end +if not _inrange then +self:T(self.lid..text) +MESSAGE:New(text,5):ToCoalitionIf(self.coalition,(self.report and message)or(self.Debug and message)) +end +local _remove=false +if not(self.ismobile or self.iscargo)and _inrange==false then +_remove=true +end +return _inrange,_toofar,_tooclose,_remove +end +function ARTY:_WeaponTypeName(tnumber) +self:F2(tnumber) +local name="unknown" +if tnumber==ARTY.WeaponType.Auto then +name="Auto" +elseif tnumber==ARTY.WeaponType.Cannon then +name="Cannons" +elseif tnumber==ARTY.WeaponType.Rockets then +name="Rockets" +elseif tnumber==ARTY.WeaponType.CruiseMissile then +name="Cruise Missiles" +elseif tnumber==ARTY.WeaponType.TacticalNukes then +name="Tactical Nukes" +elseif tnumber==ARTY.WeaponType.IlluminationShells then +name="Illumination Shells" +elseif tnumber==ARTY.WeaponType.SmokeShells then +name="Smoke Shells" +end +return name +end +function ARTY:_VicinityCoord(coord,rmin,rmax) +self:F2({coord=coord,rmin=rmin,rmax=rmax}) +rmin=rmin or 20 +rmax=rmax or 80 +local vec2=coord:GetRandomVec2InRadius(rmax,rmin) +local pops=COORDINATE:NewFromVec2(vec2) +self:T3(self.lid..string.format("Vicinity distance = %d (rmin=%d, rmax=%d)",pops:Get2DDistance(coord),rmin,rmax)) +return pops +end +function ARTY:_EventFromTo(BA,Event,From,To) +local text=string.format("%s: %s EVENT %s: %s --> %s",BA,self.groupname,Event,From,To) +self:T3(self.lid..text) +end +function ARTY:_split(str,sep) +self:F3({str=str,sep=sep}) +local result={} +local regex=("([^%s]+)"):format(sep) +for each in str:gmatch(regex)do +table.insert(result,each) +end +return result +end +function ARTY:_TargetInfo(target) +local clock=tostring(self:_SecondsToClock(target.time)) +local weapon=self:_WeaponTypeName(target.weapontype) +local _underfire=tostring(target.underfire) +return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s, attackgroup=%s", +target.name,target.prio,target.radius,target.nshells,target.engaged,target.maxengage,weapon,clock,_underfire,tostring(target.attackgroup)) +end +function ARTY:_MoveInfo(move) +self:F3(move) +local _clock=self:_SecondsToClock(move.time) +return string.format("%s: time=%s, speed=%d, onroad=%s, cancel=%s",move.name,_clock,move.speed,tostring(move.onroad),tostring(move.cancel)) +end +function ARTY:_LLDMS2DD(l1,l2) +self:F2(l1,l2) +local _latlong={l1,l2} +local _latitude=nil +local _longitude=nil +for _,ll in pairs(_latlong)do +local _format="%d+:%d+:%d+" +local _ldms=ll:match(_format) +if _ldms then +local _dms=self:_split(_ldms,":") +local _deg=tonumber(_dms[1]) +local _min=tonumber(_dms[2]) +local _sec=tonumber(_dms[3]) +local function DMS2DD(d,m,s) +return d+m/60+s/3600 +end +if ll:match("N")then +_latitude=DMS2DD(_deg,_min,_sec) +elseif ll:match("S")then +_latitude=-DMS2DD(_deg,_min,_sec) +elseif ll:match("W")then +_longitude=-DMS2DD(_deg,_min,_sec) +elseif ll:match("E")then +_longitude=DMS2DD(_deg,_min,_sec) +end +local text=string.format("DMS %02d Deg %02d min %02d sec",_deg,_min,_sec) +self:T2(self.lid..text) +end +end +local text=string.format("\nLatitude %s",tostring(_latitude)) +text=text..string.format("\nLongitude %s",tostring(_longitude)) +self:T2(self.lid..text) +return _latitude,_longitude +end +function ARTY:_SecondsToClock(seconds) +self:F3({seconds=seconds}) +if seconds==nil then +return nil +end +local seconds=tonumber(seconds) +local _seconds=seconds%(60*60*24) +if seconds<=0 then +return nil +else +local hours=string.format("%02.f",math.floor(_seconds/3600)) +local mins=string.format("%02.f",math.floor(_seconds/60-(hours*60))) +local secs=string.format("%02.f",math.floor(_seconds-hours*3600-mins*60)) +local days=string.format("%d",seconds/(60*60*24)) +return hours..":"..mins..":"..secs.."+"..days +end +end +function ARTY:_ClockToSeconds(clock) +self:F3({clock=clock}) +if clock==nil then +return nil +end +local seconds=0 +local dsplit=self:_split(clock,"+") +if#dsplit>1 then +seconds=seconds+tonumber(dsplit[2])*60*60*24 +end +local tsplit=self:_split(dsplit[1],":") +local i=1 +for _,time in ipairs(tsplit)do +if i==1 then +seconds=seconds+tonumber(time)*60*60 +elseif i==2 then +seconds=seconds+tonumber(time)*60 +elseif i==3 then +seconds=seconds+tonumber(time) +end +i=i+1 +end +self:T3(self.lid..string.format("Clock %s = %d seconds",clock,seconds)) +return seconds +end +SUPPRESSION={ +ClassName="SUPPRESSION", +Debug=false, +lid=nil, +flare=false, +smoke=false, +DCSdesc=nil, +Type=nil, +IsInfantry=nil, +SpeedMax=nil, +Tsuppress_ave=15, +Tsuppress_min=5, +Tsuppress_max=25, +TsuppressOver=nil, +IniGroupStrength=nil, +Nhit=0, +Formation="Off road", +Speed=4, +MenuON=false, +FallbackON=false, +FallbackWait=60, +FallbackDist=100, +FallbackHeading=nil, +TakecoverON=false, +TakecoverWait=120, +TakecoverRange=300, +hideout=nil, +PminFlee=10, +PmaxFlee=90, +RetreatZone=nil, +RetreatDamage=nil, +RetreatWait=7200, +CurrentAlarmState="unknown", +CurrentROE="unknown", +DefaultAlarmState="Auto", +DefaultROE="Weapon Free", +eventmoose=true, +} +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.3" +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(self.lid.."SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group.)") +return nil +end +if group:IsGround()==false then +self:E(self.lid..string.format("SUPPRESSION fire group %s has to be a GROUND group!",group:GetName())) +return nil +end +self:SetControllable(group) +self.DCSdesc=group:GetDCSDesc(1) +self.SpeedMax=group:GetSpeedMax() +self.Speed=self.SpeedMax +self.IsInfantry=group:GetUnit(1):HasAttribute("Infantry") +self.Type=group:GetTypeName() +self.IniGroupStrength=#group:GetUnits() +self:SetDefaultROE("Free") +self:SetDefaultAlarmState("Auto") +self:AddTransition("*","Start","CombatReady") +self:AddTransition("*","Status","*") +self:AddTransition("CombatReady","Hit","Suppressed") +self:AddTransition("Suppressed","Hit","Suppressed") +self:AddTransition("Suppressed","Recovered","CombatReady") +self:AddTransition("Suppressed","TakeCover","TakingCover") +self:AddTransition("Suppressed","FallBack","FallingBack") +self:AddTransition("*","Retreat","Retreating") +self:AddTransition("TakingCover","FightBack","CombatReady") +self:AddTransition("FallingBack","FightBack","CombatReady") +self:AddTransition("Retreating","Retreated","Retreated") +self:AddTransition("*","OutOfAmmo","*") +self:AddTransition("*","Dead","*") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("TakingCover","Hit","TakingCover") +self:AddTransition("FallingBack","Hit","FallingBack") +return self +end +function SUPPRESSION:SetSuppressionTime(Tave,Tmin,Tmax) +self:F({Tave=Tave,Tmin=Tmin,Tmax=Tmax}) +self.Tsuppress_min=Tmin or self.Tsuppress_min +self.Tsuppress_min=math.max(self.Tsuppress_min,1) +self.Tsuppress_max=Tmax or self.Tsuppress_max +self.Tsuppress_max=math.max(self.Tsuppress_max,self.Tsuppress_min) +self.Tsuppress_ave=Tave or self.Tsuppress_ave +self.Tsuppress_ave=math.max(self.Tsuppress_min) +self.Tsuppress_ave=math.min(self.Tsuppress_max) +self:T(self.lid..string.format("Set ave suppression time to %d seconds.",self.Tsuppress_ave)) +self:T(self.lid..string.format("Set min suppression time to %d seconds.",self.Tsuppress_min)) +self:T(self.lid..string.format("Set max suppression time to %d seconds.",self.Tsuppress_max)) +end +function SUPPRESSION:SetRetreatZone(zone) +self:F({zone=zone}) +self.RetreatZone=zone +end +function SUPPRESSION:DebugOn() +self:F() +self.Debug=true +end +function SUPPRESSION:FlareOn() +self:F() +self.flare=true +end +function SUPPRESSION:SmokeOn() +self:F() +self.smoke=true +end +function SUPPRESSION:SetFormation(formation) +self:F(formation) +self.Formation=formation or"Vee" +end +function SUPPRESSION:SetSpeed(speed) +self:F(speed) +self.Speed=speed or self.SpeedMax +self.Speed=math.min(self.Speed,self.SpeedMax) +end +function SUPPRESSION:Fallback(switch) +self:F(switch) +if switch==nil then +switch=true +end +self.FallbackON=switch +end +function SUPPRESSION:SetFallbackDistance(distance) +self:F(distance) +self.FallbackDist=distance +end +function SUPPRESSION:SetFallbackWait(time) +self:F(time) +self.FallbackWait=time +end +function SUPPRESSION:Takecover(switch) +self:F(switch) +if switch==nil then +switch=true +end +self.TakecoverON=switch +end +function SUPPRESSION:SetTakecoverWait(time) +self:F(time) +self.TakecoverWait=time +end +function SUPPRESSION:SetTakecoverRange(range) +self:F(range) +self.TakecoverRange=range +end +function SUPPRESSION:SetTakecoverPlace(Hideout) +self.hideout=Hideout +end +function SUPPRESSION:SetMinimumFleeProbability(probability) +self:F(probability) +self.PminFlee=probability or 10 +end +function SUPPRESSION:SetMaximumFleeProbability(probability) +self:F(probability) +self.PmaxFlee=probability or 90 +end +function SUPPRESSION:SetRetreatDamage(damage) +self:F(damage) +self.RetreatDamage=damage or 50 +end +function SUPPRESSION:SetRetreatWait(time) +self:F(time) +self.RetreatWait=time or 7200 +end +function SUPPRESSION:SetDefaultAlarmState(alarmstate) +self:F(alarmstate) +if alarmstate:lower()=="auto"then +self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto +elseif alarmstate:lower()=="green"then +self.DefaultAlarmState=SUPPRESSION.AlarmState.Green +elseif alarmstate:lower()=="red"then +self.DefaultAlarmState=SUPPRESSION.AlarmState.Red +else +self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto +end +end +function SUPPRESSION:SetDefaultROE(roe) +self:F(roe) +if roe:lower()=="free"then +self.DefaultROE=SUPPRESSION.ROE.Free +elseif roe:lower()=="hold"then +self.DefaultROE=SUPPRESSION.ROE.Hold +elseif roe:lower()=="return"then +self.DefaultROE=SUPPRESSION.ROE.Return +else +self.DefaultROE=SUPPRESSION.ROE.Free +end +end +function SUPPRESSION:MenuOn(switch) +self:F(switch) +if switch==nil then +switch=true +end +self.MenuON=switch +end +function SUPPRESSION:_CreateMenuGroup() +local SubMenuName=self.Controllable:GetName() +local MenuGroup=MENU_MISSION:New(SubMenuName,SUPPRESSION.MenuF10) +MENU_MISSION_COMMAND:New("Fallback!",MenuGroup,self.OrderFallBack,self) +MENU_MISSION_COMMAND:New("Take Cover!",MenuGroup,self.OrderTakeCover,self) +MENU_MISSION_COMMAND:New("Retreat!",MenuGroup,self.OrderRetreat,self) +MENU_MISSION_COMMAND:New("Report Status",MenuGroup,self.Status,self,true) +end +function SUPPRESSION:OrderFallBack() +local group=self.Controllable +local vicinity=group:GetCoordinate():GetRandomVec2InRadius(150,100) +local coord=COORDINATE:NewFromVec2(vicinity) +self:FallBack(self.Controllable) +end +function SUPPRESSION:OrderTakeCover() +local Hideout=self.hideout +if self.hideout==nil then +Hideout=self:_SearchHideout() +end +self:TakeCover(Hideout) +end +function SUPPRESSION:OrderRetreat() +self:Retreat() +end +function SUPPRESSION:StatusReport(message) +local group=self.Controllable +local nunits=group:CountAliveUnits() +local roe=self.CurrentROE +local state=self.CurrentAlarmState +local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife() +local ammotot=group:GetAmmunition() +local detectedG=group:GetDetectedGroupSet():CountAlive() +local detectedU=group:GetDetectedUnitSet():Count() +local text=string.format("State %s, Units=%d/%d, ROE=%s, AlarmState=%s, Hits=%d, Life(min/max/ave/ave0)=%d/%d/%d/%d, Total Ammo=%d, Detected=%d/%d", +self:GetState(),nunits,self.IniGroupStrength,self.CurrentROE,self.CurrentAlarmState,self.Nhit,life_min,life_max,life_ave,life_ave0,ammotot,detectedG,detectedU) +MESSAGE:New(text,10):ToAllIf(message or self.Debug) +self:I(self.lid..text) +end +function SUPPRESSION:onafterStart(Controllable,From,Event,To) +self:_EventFromTo("onafterStart",Event,From,To) +local text=string.format("Started SUPPRESSION for group %s.",Controllable:GetName()) +self:I(self.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +local rzone="not defined" +if self.RetreatZone then +rzone=self.RetreatZone:GetName() +end +if self.RetreatDamage==nil then +if self.RetreatZone then +if self.IniGroupStrength==1 then +self.RetreatDamage=60.0 +elseif self.IniGroupStrength==2 then +self.RetreatDamage=50.0 +else +self.RetreatDamage=66.5 +end +else +self.RetreatDamage=100 +end +end +if self.MenuON then +if not SUPPRESSION.MenuF10 then +SUPPRESSION.MenuF10=MENU_MISSION:New("Suppression") +end +self:_CreateMenuGroup() +end +self:_SetAlarmState(self.DefaultAlarmState) +self:_SetROE(self.DefaultROE) +local text=string.format("\n******************************************************\n") +text=text..string.format("Suppressed group = %s\n",Controllable:GetName()) +text=text..string.format("Type = %s\n",self.Type) +text=text..string.format("IsInfantry = %s\n",tostring(self.IsInfantry)) +text=text..string.format("Group strength = %d\n",self.IniGroupStrength) +text=text..string.format("Average time = %5.1f seconds\n",self.Tsuppress_ave) +text=text..string.format("Minimum time = %5.1f seconds\n",self.Tsuppress_min) +text=text..string.format("Maximum time = %5.1f seconds\n",self.Tsuppress_max) +text=text..string.format("Default ROE = %s\n",self.DefaultROE) +text=text..string.format("Default AlarmState = %s\n",self.DefaultAlarmState) +text=text..string.format("Fall back ON = %s\n",tostring(self.FallbackON)) +text=text..string.format("Fall back distance = %5.1f m\n",self.FallbackDist) +text=text..string.format("Fall back wait = %5.1f seconds\n",self.FallbackWait) +text=text..string.format("Fall back heading = %s degrees\n",tostring(self.FallbackHeading)) +text=text..string.format("Take cover ON = %s\n",tostring(self.TakecoverON)) +text=text..string.format("Take cover search = %5.1f m\n",self.TakecoverRange) +text=text..string.format("Take cover wait = %5.1f seconds\n",self.TakecoverWait) +text=text..string.format("Min flee probability = %5.1f\n",self.PminFlee) +text=text..string.format("Max flee probability = %5.1f\n",self.PmaxFlee) +text=text..string.format("Retreat zone = %s\n",rzone) +text=text..string.format("Retreat damage = %5.1f %%\n",self.RetreatDamage) +text=text..string.format("Retreat wait = %5.1f seconds\n",self.RetreatWait) +text=text..string.format("Speed = %5.1f km/h\n",self.Speed) +text=text..string.format("Speed max = %5.1f km/h\n",self.SpeedMax) +text=text..string.format("Formation = %s\n",self.Formation) +text=text..string.format("******************************************************\n") +self:T(self.lid..text) +if self.eventmoose then +self:HandleEvent(EVENTS.Hit,self._OnEventHit) +self:HandleEvent(EVENTS.Dead,self._OnEventDead) +else +world.addEventHandler(self) +end +self:__Status(-1) +end +function SUPPRESSION:onafterStatus(Controllable,From,Event,To) +local group=self.Controllable +if group then +local nunits=group:CountAliveUnits() +if nunits>0 then +local nammo=group:GetAmmunition() +if nammo==0 then +self:OutOfAmmo() +end +self:StatusReport(false) +if self:GetState()~="Stopped"then +self:__Status(-30) +end +else +self:Stop() +end +else +self:Stop() +end +end +function SUPPRESSION:onafterHit(Controllable,From,Event,To,Unit,AttackUnit) +self:_EventFromTo("onafterHit",Event,From,To) +if From=="CombatReady"or From=="Suppressed"then +self:_Suppress() +end +local life_min,life_max,life_ave,life_ave0,groupstrength=self:_GetLife() +local Damage=100-life_ave0 +local RetreatCondition=Damage>=self.RetreatDamage-0.01 and self.RetreatZone +local Pflee=(self.PmaxFlee-self.PminFlee)/self.RetreatDamage*math.min(Damage,self.RetreatDamage)+self.PminFlee +local P=math.random(0,100) +local FleeCondition=P Prand ==> Flee)\n",Controllable:GetName(),Pflee,P) +self:T(self.lid..text) +if Damage>=99.9 then +return +end +if RetreatCondition then +self:Retreat() +elseif FleeCondition then +if self.FallbackON and AttackUnit:IsGround()then +self:FallBack(AttackUnit) +elseif self.TakecoverON then +local Hideout=self.hideout +if self.hideout==nil then +Hideout=self:_SearchHideout() +end +self:TakeCover(Hideout) +end +end +end +function SUPPRESSION:onbeforeRecovered(Controllable,From,Event,To) +self:_EventFromTo("onbeforeRecovered",Event,From,To) +local Tnow=timer.getTime() +self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d",Tnow,self.TsuppressionOver)) +if Tnow>=self.TsuppressionOver then +return true +else +return false +end +end +function SUPPRESSION:onafterRecovered(Controllable,From,Event,To) +self:_EventFromTo("onafterRecovered",Event,From,To) +if Controllable and Controllable:IsAlive()then +local text=string.format("Group %s has recovered!",Controllable:GetName()) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:T(self.lid..text) +self:_SetROE() +if self.flare or self.Debug then +Controllable:FlareGreen() +end +end +end +function SUPPRESSION:onafterFightBack(Controllable,From,Event,To) +self:_EventFromTo("onafterFightBack",Event,From,To) +self:_SetROE() +self:_SetAlarmState() +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.Green) +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:F(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"Off road" +wait=wait or 30 +local group=self.Controllable +if group and group:IsAlive()then +group:ClearTasks() +local ini=group:GetCoordinate() +local dist=ini:Get2DDistance(fin) +local heading=self:_Heading(ini,fin) +local nx +if dist<=50 then +nx=2 +elseif dist<=100 then +nx=3 +elseif dist<=500 then +nx=4 +else +nx=5 +end +local dx=dist/(nx-1) +local wp={} +local tasks={} +wp[1]=ini:WaypointGround(speed,formation) +tasks[1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint",self,1,false) +if self.Debug then +local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)",#wp,self.Controllable:GetName())) +end +self:T2(self.lid..string.format("Number of waypoints %d",nx)) +for i=1,nx-2 do +local x=dx*i +local coord=ini:Translate(x,heading) +wp[#wp+1]=coord:WaypointGround(speed,formation) +tasks[#tasks+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint",self,#wp,false) +self:T2(self.lid..string.format("%d x = %4.1f",i,x)) +if self.Debug then +local MarkerID=coord:MarkToAll(string.format("Waypoing %d of group %s",#wp,self.Controllable:GetName())) +end +end +self:T2(self.lid..string.format("Total distance: %4.1f",dist)) +wp[#wp+1]=fin:WaypointGround(speed,formation) +if self.Debug then +local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)",#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) +tasks[#tasks+1]=group:TaskCombo(TaskComboFin) +local Waypoints=group:GetTemplateRoutePoints() +for i,p in ipairs(wp)do +table.insert(Waypoints,i,wp[i]) +end +for i,wp in ipairs(Waypoints)do +group:SetTaskWaypoint(Waypoints[i],tasks[i]) +end +group:Route(Waypoints) +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(self.lid..text) +end +if final then +if Fsm:is("Retreating")then +Fsm:Retreated() +else +Fsm:FightBack() +end +end +end +function SUPPRESSION:_SearchHideout() +local Zone=ZONE_GROUP:New("Zone_Hiding",self.Controllable,self.TakecoverRange) +local gpos=self.Controllable:GetCoordinate() +Zone:Scan(Object.Category.SCENERY) +local hideouts={} +for SceneryTypeName,SceneryData in pairs(Zone:GetScannedScenery())do +for SceneryName,SceneryObject in pairs(SceneryData)do +local SceneryObject=SceneryObject +local spos=SceneryObject:GetCoordinate() +local distance=spos:Get2DDistance(gpos) +if self.Debug then +local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s",self.Controllable:GetName(),SceneryObject:GetTypeName())) +local text=string.format("%s scenery: %s, Coord %s",self.Controllable:GetName(),SceneryObject:GetTypeName(),SceneryObject:GetCoordinate():ToStringLLDMS()) +self:T2(self.lid..text) +end +table.insert(hideouts,{object=SceneryObject,distance=distance}) +end +end +local Hideout=nil +if#hideouts>0 then +self:T(self.lid.."Number of hideouts "..#hideouts) +local _sort=function(a,b)return a.distancelife_max then +life_max=life +end +life_ave=life_ave+life +if self.Debug then +local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f",n,unit:GetLife(),unit:GetLife0(),life_min,life_max,life_ave/n,groupstrength) +self:T2(self.lid..text) +end +end +end +if n==0 then +return 0,0,0,0,0 +end +life_ave0=life_ave/self.IniGroupStrength +life_ave=life_ave/n +return life_min,life_max,life_ave,life_ave0,groupstrength +else +return 0,0,0,0,0 +end +end +function SUPPRESSION:_Heading(a,b) +local dx=b.x-a.x +local dy=b.z-a.z +local angle=math.deg(math.atan2(dy,dx)) +if angle<0 then +angle=360+angle +end +return angle +end +function SUPPRESSION:_Random_Gaussian(x0,sigma,xmin,xmax) +sigma=sigma or 5 +local r +local gotit=false +local i=0 +while not gotit do +local x1=math.random() +local x2=math.random() +r=math.sqrt(-2*sigma*sigma*math.log(x1))*math.cos(2*math.pi*x2)+x0 +i=i+1 +if(r>=xmin and r<=xmax)or i>100 then +gotit=true +end +end +return r +end +function SUPPRESSION:_SetROE(roe) +local group=self.Controllable +roe=roe or self.DefaultROE +self.CurrentROE=roe +if roe==SUPPRESSION.ROE.Free then +group:OptionROEOpenFire() +elseif roe==SUPPRESSION.ROE.Hold then +group:OptionROEHoldFire() +elseif roe==SUPPRESSION.ROE.Return then +group:OptionROEReturnFire() +else +self:E(self.lid.."Unknown ROE requested: "..tostring(roe)) +group:OptionROEOpenFire() +self.CurrentROE=SUPPRESSION.ROE.Free +end +local text=string.format("Group %s now has ROE %s.",self.Controllable:GetName(),self.CurrentROE) +self:T(self.lid..text) +end +function SUPPRESSION:_SetAlarmState(state) +local group=self.Controllable +state=state or self.DefaultAlarmState +self.CurrentAlarmState=state +if state==SUPPRESSION.AlarmState.Auto then +group:OptionAlarmStateAuto() +elseif state==SUPPRESSION.AlarmState.Green then +group:OptionAlarmStateGreen() +elseif state==SUPPRESSION.AlarmState.Red then +group:OptionAlarmStateRed() +else +self:E(self.lid.."Unknown alarm state requested: "..tostring(state)) +group:OptionAlarmStateAuto() +self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto +end +local text=string.format("Group %s now has Alarm State %s.",self.Controllable:GetName(),self.CurrentAlarmState) +self:T(self.lid..text) +end +function SUPPRESSION:_EventFromTo(BA,Event,From,To) +local text=string.format("\n%s: %s EVENT %s: %s --> %s",BA,self.Controllable:GetName(),Event,From,To) +self:T2(self.lid..text) +end +PSEUDOATC={ +ClassName="PSEUDOATC", +group={}, +Debug=false, +mdur=30, +mrefresh=120, +talt=3, +chatty=true, +eventsmoose=true, +} +PSEUDOATC.id="PseudoATC | " +PSEUDOATC.version="0.9.2" +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:E(PSEUDOATC.id.."Starting PseudoATC") +if self.eventsmoose then +self:T(PSEUDOATC.id.."Events are handled by MOOSE.") +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) +else +self:T(PSEUDOATC.id.."Events are handled by DCS.") +world.addEventHandler(self) +end +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: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:onEvent(Event) +if Event==nil or Event.initiator==nil or Unit.getByName(Event.initiator:getName())==nil then +return true +end +local DCSiniunit=Event.initiator +local DCSplace=Event.place +local DCSsubplace=Event.subplace +local EventData={} +local _playerunit=nil +local _playername=nil +if Event.initiator then +EventData.IniUnitName=Event.initiator:getName() +EventData.IniDCSGroup=Event.initiator:getGroup() +EventData.IniGroupName=Event.initiator:getGroup():getName() +_playerunit,_playername=self:_GetPlayerUnitAndName(EventData.IniUnitName) +end +if Event.place then +EventData.Place=Event.place +EventData.PlaceName=Event.place:getName() +end +if Event.subplace then +EventData.SubPlace=Event.subplace +EventData.SubPlaceName=Event.subplace:getName() +end +self:T3(PSEUDOATC.id..string.format("EVENT: Event in onEvent with ID = %s",tostring(Event.id))) +self:T3(PSEUDOATC.id..string.format("EVENT: Ini unit = %s",tostring(EventData.IniUnitName))) +self:T3(PSEUDOATC.id..string.format("EVENT: Ini group = %s",tostring(EventData.IniGroupName))) +self:T3(PSEUDOATC.id..string.format("EVENT: Ini player = %s",tostring(_playername))) +self:T3(PSEUDOATC.id..string.format("EVENT: Place = %s",tostring(EventData.PlaceName))) +self:T3(PSEUDOATC.id..string.format("EVENT: SubPlace = %s",tostring(EventData.SubPlaceName))) +if Event.id==world.event.S_EVENT_BIRTH and _playername then +self:_OnBirth(EventData) +end +if Event.id==world.event.S_EVENT_TAKEOFF and _playername and EventData.Place then +self:_PlayerTakeOff(EventData) +end +if Event.id==world.event.S_EVENT_LAND and _playername and EventData.Place then +self:_PlayerLanded(EventData) +end +if Event.id==world.event.S_EVENT_PLAYER_LEAVE_UNIT and _playername then +self:_PlayerLeft(EventData) +end +if Event.id==world.event.S_EVENT_CRASH and _playername then +self:_PlayerLeft(EventData) +end +end +function PSEUDOATC:_OnBirth(EventData) +self:F({EventData=EventData}) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +self:PlayerEntered(_unit) +end +end +function PSEUDOATC:_PlayerLeft(EventData) +self:F({EventData=EventData}) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +self:PlayerLeft(_unit) +end +end +function PSEUDOATC:_PlayerLanded(EventData) +self:F({EventData=EventData}) +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +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,_playername=self:_GetPlayerUnitAndName(_unitName) +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=self.group[GID].player[UID].playername +local UnitName=self.group[GID].player[UID].unitname +local GroupName=self.group[GID].player[UID].groupname +local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.",PlayerName,UnitName,GroupName,GID,place) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +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 GID=group:GetID() +local UID=unit:GetDCSObject():getID() +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 in unit %s of group %s (id=%d) took off at %s.",PlayerName,UnitName,GroupName,GID,place) +self:T(PSEUDOATC.id..text) +MESSAGE:New(text,30):ToAllIf(self.Debug) +if place and self.chatty then +local text=string.format("%s, %s, you are airborne. Have a safe trip!",place,CallSign) +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].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) +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.CelciusToFarenheit(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 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 _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,0.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 q=AIRBASE:FindByName(name):GetCoordinate() +local d=q:Get2DDistance(pos) +table.insert(self.group[GID].player[UID].airports,{distance=d,name=name}) +end +end +local function compare(a,b) +return a.distance0 then +local samecoalition=anycoalition or Coalition==warehouse:GetCoalition() +if samecoalition and not(warehouse:IsNotReadyYet()or warehouse:IsStopped()or warehouse:IsDestroyed())then +local nassets=warehouse:GetNumberOfAssets(Descriptor,DescriptorValue) +local enough=true +if Descriptor and DescriptorValue then +enough=nassets>=MinAssets +end +if enough and(distmin==nil or dist=1 then +local FSMstate=self:GetState() +local coalition=self:GetCoalitionName() +local country=self:GetCountryName() +self:I(self.lid..string.format("State=%s %s [%s]: Assets=%d, Requests: waiting=%d, pending=%d",FSMstate,country,coalition,#self.stock,#self.queue,#self.pending)) +end +self:_JobDone() +self:_DisplayStatus() +self:_CheckConquered() +if self:IsRunwayOperational()==false then +local Trepair=self:GetRunwayRepairtime() +self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec",Trepair)) +if Trepair==0 then +self: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 +self:_PrintQueue(self.queue,"Queue waiting") +self:_PrintQueue(self.pending,"Queue pending") +self:_CheckFuel() +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.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) +group:Destroy() +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+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 +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 +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 +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.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.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 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,tostring(request.assetdescval),tostring(request.nasset),request.transporttype,tostring(request.ntransport)) +self:_DebugMessage(text,5) +end +function WAREHOUSE:onbeforeRequest(From,Event,To,Request) +self:T3({warehouse=self.alias,request=Request}) +local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate()) +local _assets=Request.cargoassets +if Request.nasset==0 then +local text=string.format("Warehouse %s: Request denied! Zero assets were requested.",self.alias) +self:_InfoMessage(text,10) +return false +end +for _,_asset in pairs(_assets)do +local asset=_asset +if asset.range=1 then +local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n",self.alias,Request.uid,Request.warehouse.alias) +text=text..string.format("Requested %s assets of %s=%s.\n",tostring(Request.nasset),Request.assetdesc,Request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST and"Asset list"or Request.assetdescval) +text=text..string.format("Transports %s of type %s.",tostring(Request.ntransport),tostring(Request.transporttype)) +self:_InfoMessage(text,5) +end +Request.timestamp=timer.getAbsTime() +self:_SpawnAssetRequest(Request) +local _assetstock=Request.transportassets +local Parking={} +if Request.transportcategory==Group.Category.AIRPLANE or Request.transportcategory==Group.Category.HELICOPTER then +Parking=self:_FindParkingForAssets(self.airbase,_assetstock) +end +local _transportassets={} +for i=1,Request.ntransport do +local _assetitem=_assetstock[i] +local _alias=_assetitem.spawngroupname +_assetitem.rid=Request.uid +_assetitem.spawned=false +_assetitem.iscargo=false +_assetitem.arrived=false +local spawngroup=nil +Request.assets[_assetitem.uid]=_assetitem +if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then +spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],true) +elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then +spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem,Request,Parking[_assetitem.uid],false) +elseif Request.transporttype==WAREHOUSE.TransportType.APC then +spawngroup=self:_SpawnAssetGroundNaval(_alias,_assetitem,Request,self.spawnzone) +elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then +self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!") +return +elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER 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 +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.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() +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 +end +function WAREHOUSE:onbeforeAssetSpawned(From,Event,To,group,asset,request) +if asset.spawned then +else +end +return true +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 +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) +local text=string.format("Asset %s from request id=%d is dead!",asset.templatename,request.uid) +self:T(self.lid..text) +self:_DebugMessage(text) +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] +asset.spawned=false +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) +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) +else +_group=self:_SpawnAssetAircraft(_alias,asset,Request,nil,UnControlled) +end +elseif asset.category==Group.Category.TRAIN then +if self.rail then +_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.spawnzone) +end +elseif asset.category==Group.Category.SHIP then +_group=self:_SpawnAssetGroundNaval(_alias,asset,Request,self.portzone) +else +self:E(self.lid.."ERROR: Unknown asset category!") +end +end +end +function WAREHOUSE:_SpawnAssetGroundNaval(alias,asset,request,spawnzone,aioff) +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.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) +if aioff then +group:SetAIOff() +end +return group +end +return nil +end +function WAREHOUSE:_SpawnAssetAircraft(alias,asset,request,parking,uncontrolled,hotstart) +if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then +local template=self:_SpawnAssetPrepareTemplate(asset,alias) +if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +if request.toself then +local wp=self.airbase:GetCoordinate():WaypointAir("RADIO",COORDINATE.WaypointType.TakeOffParking,COORDINATE.WaypointAction.FromParkingArea,0,false,self.airbase,{},"Parking") +template.route.points={wp} +else +template.route.points=self:_GetFlightplan(asset,self.airbase,request.warehouse.airbase) +end +else +local _type=COORDINATE.WaypointType.TakeOffParking +local _action=COORDINATE.WaypointAction.FromParkingArea +if hotstart then +_type=COORDINATE.WaypointType.TakeOffParkingHot +_action=COORDINATE.WaypointAction.FromParkingAreaHot +end +template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action,0,true,self.airbase,nil,"Spawnpoint") +end +local AirbaseID=self.airbase:GetID() +local AirbaseCategory=self:GetAirbaseCategory() +if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then +else +if#parking<#template.units 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 +unit.parking_id=nil +unit.parking=nil +else +local coord=parking[i].Coordinate +local terminal=parking[i].TerminalID +if self.Debug then +coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.",unit.name,terminal)) +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 +env.info(n) +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()))) +local starttime=math.random(60) +aircraft:StartUncontrolled(starttime) +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 +if not asset.spawned then +self:_DeleteStockItem(asset) +asset.spawned=true +asset.spawngroupname=group:GetName() +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) +self:AssetSpawned(group,asset,request) +end +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 +self:RunwayDestroyed() +end +end +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,request) +end +end +end +end +end +end +function WAREHOUSE:_UnitDead(deadunit,request) +if self.Debug then +deadunit:FlareRed() +end +local group=deadunit:GetGroup() +local nalive=group:CountAliveUnits() +local groupdead=true +if nalive>0 then +groupdead=false +end +local unitname=self:_GetNameWithOut(deadunit) +local groupname=self:_GetNameWithOut(group) +if groupdead then +self:T(self.lid..string.format("Group %s (transport=%s) is dead!",groupname,tostring(self:_GroupIsTransport(group,request)))) +if self.Debug then +group:SmokeWhite() +end +local asset=self:FindAssetInDB(group) +self:AssetDead(asset,request) +end +local NoTriggerEvent=true +if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +if groupdead==true then +request.cargogroupset:Remove(groupname,NoTriggerEvent) +self:T(self.lid..string.format("Removed selfpropelled cargo %s: ncargo=%d.",groupname,request.cargogroupset:Count())) +end +else +local istransport=self:_GroupIsTransport(group,request) +if istransport==true 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 +if groupdead then +request.transportgroupset:Remove(groupname,NoTriggerEvent) +self:T(self.lid..string.format("Removed transport %s: ntransport=%d",groupname,request.transportgroupset:Count())) +end +elseif istransport==false then +if groupdead==true then +request.cargogroupset:Remove(groupname,NoTriggerEvent) +self:T(self.lid..string.format("Removed transported cargo %s outside carrier: ncargo=%d",groupname,request.cargogroupset:Count())) +end +else +self:E(self.lid..string.format("ERROR: Group %s is neither cargo nor transport!",group: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 coaltion! Own coalition %s != %s of requesting warehouse.",self:GetCoalitionName(),request.warehouse:GetCoalitionName())) +valid=false +end +if request.warehouse:IsStopped()then +self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is stopped!")) +valid=false +end +if request.warehouse:IsDestroyed()and not self.respawnafterdestroyed then +self:E(self.lid..string.format("ERROR: INVALID request. Requesting warehouse is destroyed!")) +valid=false +end +if valid==false then +self:E(self.lid..string.format("Got invalid request id=%d.",request.uid)) +table.insert(invalid,request) +else +self:T3(self.lid..string.format("Got valid request id=%d.",request.uid)) +end +end +for _,_request in pairs(invalid)do +self:E(self.lid..string.format("Deleting INVALID request id=%d.",_request.uid)) +self:_DeleteQueueItem(_request,self.queue) +end +end +function WAREHOUSE:_CheckRequestValid(request) +local _assets,_nassets,_enough=self:_FilterStock(self.stock,request.assetdesc,request.assetdescval,request.nasset) +if#_assets==0 then +return true +end +local nasset=request.nasset +if type(request.nasset)=="string"then +nasset=self:_QuantityRel2Abs(request.nasset,_nassets) +end +local text=string.format("Request valid? Number of assets: requested=%s=%d, selected=%d, total=%d, enough=%s.",tostring(request.nasset),nasset,#_assets,_nassets,tostring(_enough)) +self:T(text) +local asset=_assets[1] +local asset_plane=asset.category==Group.Category.AIRPLANE +local asset_helo=asset.category==Group.Category.HELICOPTER +local asset_ground=asset.category==Group.Category.GROUND +local asset_train=asset.category==Group.Category.TRAIN +local asset_naval=asset.category==Group.Category.SHIP +local asset_air=asset_helo or asset_plane +local valid=true +local requestcategory=request.warehouse:GetAirbaseCategory() +if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then +if asset_air then +if asset_plane then +if requestcategory==Airbase.Category.HELIPAD or self:GetAirbaseCategory()==Airbase.Category.HELIPAD then +self:E("ERROR: Incorrect request. Asset airplane requested but warehouse or requestor is HELIPAD/FARP!") +valid=false +end +elseif asset_helo then +if self:GetAirbaseCategory()==-1 or requestcategory==-1 then +self:E("ERROR: Incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destination base!") +valid=false +end +end +if self.airbase==nil or request.airbase==nil then +self:E("ERROR: Incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!") +valid=false +else +local termtype_dep=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory()) +local termtype_des=asset.terminalType or self:_GetTerminal(asset.attribute,request.warehouse:GetAirbaseCategory()) +local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep) +local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des) +self:T(string.format("Asset attribute = %s, DEPARTURE: terminal type = %d, spots = %d, DESTINATION: terminal type = %d, spots = %d",asset.attribute,termtype_dep,np_departure,termtype_des,np_destination)) +if np_departure0 then +_assetattribute=_assets[1].attribute +_assetcategory=_assets[1].category +if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then +if self.airbase and self.airbase:GetCoalition()==self:GetCoalition()then +if self:IsRunwayOperational()then +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 +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 +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 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 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 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() +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"}) +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 +local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute,self:GetAirbaseCategory()) +parking[_asset.uid]={} +for i=1,_asset.nunits do +local gotit=false +for _,_parkingspot in pairs(parkingdata)do +local parkingspot=_parkingspot +if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)and self:_CheckParkingValid(parkingspot,airbase)and airbase:_CheckParkingLists(parkingspot.TerminalID)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 +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 id=%d!",_termid,_asset.uid)) +table.insert(obstacles,{coord=_spot,size=_asset.size,name=_asset.templatename,type="asset"}) +gotit=true +break +else +self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!",_termid)) +if self.Debug then +local coord=problem.coord +local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.",_termid,problem.name,problem.type,problem.size,problem.dist) +coord:MarkToAll(string.format(text)) +end +end +end +end +if not gotit then +self:I(self.lid..string.format("WARNING: No free parking spot for asset id=%d",_asset.uid)) +return nil +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) +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 +if group then +local name=group:GetName() +local wid,aid,rid=analyse(name) +local asset=self:GetAssetByID(aid) +if asset then +wid=asset.wid +rid=asset.rid +end +self:T(self.lid..string.format("Group Name = %s",tostring(name))) +self:T(self.lid..string.format("Warehouse ID = %s",tostring(wid))) +self:T(self.lid..string.format("Asset ID = %s",tostring(aid))) +self:T(self.lid..string.format("Request ID = %s",tostring(rid))) +return wid,aid,rid +else +self:E("WARNING: Group not found in GetIDsFromGroup() function!") +end +end +function WAREHOUSE:_GetIDsFromGroupOLD(group) +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 +if group then +local name=group:GetName() +local wid,aid,rid=analyse(name) +self:T3(self.lid..string.format("Group Name = %s",tostring(name))) +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 +else +self:E("WARNING: Group not found in GetIDsFromGroup() function!") +end +end +function WAREHOUSE:FilterStock(descriptor,attribute,nmax,mobile) +return self:_FilterStock(self.stock,descriptor,attribute,nmax,mobile) +end +function WAREHOUSE:_FilterStock(stock,descriptor,attribute,nmax,mobile) +nmax=nmax or WAREHOUSE.Quantity.ALL +if mobile==nil then +mobile=false +end +local filtered={} +if descriptor==WAREHOUSE.Descriptor.ASSETLIST then +local ntot=0 +for _,_rasset in pairs(attribute)do +local rasset=_rasset +for _,_asset in ipairs(stock)do +local asset=_asset +if rasset.uid==asset.uid then +table.insert(filtered,asset) +break +end +end +end +return filtered,#filtered,#filtered>=#attribute +end +local ntot=0 +for _,_asset in ipairs(stock)do +local asset=_asset +local ismobile=asset.speedmax>0 +if asset[descriptor]==attribute then +if(mobile==true and ismobile)or mobile==false then +ntot=ntot+1 +end +end +end +if ntot==0 then +return filtered,ntot,false +end +nmax=self:_QuantityRel2Abs(nmax,ntot) +for _i,_asset in ipairs(stock)do +local asset=_asset +if asset[descriptor]==attribute then +if(mobile and asset.speedmax>0)or(not mobile)then +table.insert(filtered,asset) +if nmax~=nil and#filtered>=nmax then +return filtered,ntot,true +end +end +end +end +return filtered,ntot,ntot>=nmax +end +function WAREHOUSE:_HasAttribute(group,attribute) +if group then +local groupattribute=self:_GetAttribute(group) +return groupattribute==attribute +end +return false +end +function WAREHOUSE:_GetAttribute(group) +local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN +if group then +local transportplane=group:HasAttribute("Transports")and group:HasAttribute("Planes") +local awacs=group:HasAttribute("AWACS") +local fighter=group:HasAttribute("Fighters")or group:HasAttribute("Interceptors")or group:HasAttribute("Multirole fighters")or(group:HasAttribute("Bombers")and not group:HasAttribute("Strategic bombers")) +local bomber=group:HasAttribute("Strategic bombers") +local tanker=group:HasAttribute("Tankers") +local uav=group:HasAttribute("UAVs") +local transporthelo=group:HasAttribute("Transport helicopters") +local attackhelicopter=group:HasAttribute("Attack helicopters") +local apc=group:HasAttribute("Infantry carriers") +local truck=group:HasAttribute("Trucks")and group:GetCategory()==Group.Category.GROUND +local infantry=group:HasAttribute("Infantry") +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 infantry then +attribute=WAREHOUSE.Attribute.GROUND_INFANTRY +elseif artillery then +attribute=WAREHOUSE.Attribute.GROUND_ARTILLERY +elseif tank then +attribute=WAREHOUSE.Attribute.GROUND_TANK +elseif aaa then +attribute=WAREHOUSE.Attribute.GROUND_AAA +elseif ewr then +attribute=WAREHOUSE.Attribute.GROUND_EWR +elseif sam then +attribute=WAREHOUSE.Attribute.GROUND_SAM +elseif truck then +attribute=WAREHOUSE.Attribute.GROUND_TRUCK +elseif train then +attribute=WAREHOUSE.Attribute.GROUND_TRAIN +elseif aircraftcarrier then +attribute=WAREHOUSE.Attribute.NAVAL_AIRCRAFTCARRIER +elseif warship then +attribute=WAREHOUSE.Attribute.NAVAL_WARSHIP +elseif armedship then +attribute=WAREHOUSE.Attribute.NAVAL_ARMEDSHIP +elseif unarmedship then +attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP +else +if group:IsGround()then +attribute=WAREHOUSE.Attribute.GROUND_OTHER +elseif group:IsShip()then +attribute=WAREHOUSE.Attribute.NAVAL_OTHER +elseif group:IsAir()then +attribute=WAREHOUSE.Attribute.AIR_OTHER +else +attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN +end +end +end +return attribute +end +function WAREHOUSE:_GetObjectSize(DCSobject) +local DCSdesc=DCSobject:getDesc() +if DCSdesc.box then +local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) +local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) +local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) +return math.max(x,z),x,y,z +end +return 0,0,0,0 +end +function WAREHOUSE:GetStockInfo(stock) +local _data={} +for _j,_attribute in pairs(WAREHOUSE.Attribute)do +local n=0 +for _i,_item in pairs(stock)do +local _ite=_item +if _ite.attribute==_attribute then +n=n+1 +end +end +_data[_attribute]=n +end +return _data +end +function WAREHOUSE:_DeleteStockItem(stockitem) +for i=1,#self.stock do +local item=self.stock[i] +if item.uid==stockitem.uid then +table.remove(self.stock,i) +break +end +end +end +function WAREHOUSE:_DeleteQueueItem(qitem,queue) +self:F({qitem=qitem,queue=queue}) +for i=1,#queue do +local _item=queue[i] +if _item.uid==qitem.uid then +self:T(self.lid..string.format("Deleting queue item id=%d.",qitem.uid)) +table.remove(queue,i) +break +end +end +end +function WAREHOUSE:_DeleteQueueItemByID(qitemID,queue) +for i=1,#queue do +local _item=queue[i] +if _item.uid==qitemID then +self:T(self.lid..string.format("Deleting queue item id=%d.",qitemID)) +table.remove(queue,i) +break +end +end +end +function WAREHOUSE:_SortQueue() +self:F3() +local function _sort(a,b) +return(a.prio=2 then +local total="Empty" +if#queue>0 then +total=string.format("Total = %d",#queue) +end +local text=string.format("%s at %s: %s",name,self.alias,total) +for i,qitem in ipairs(queue)do +local qitem=qitem +local uid=qitem.uid +local prio=qitem.prio +local clock="N/A" +if qitem.timestamp then +clock=tostring(UTILS.SecondsToClock(qitem.timestamp)) +end +local assignment=tostring(qitem.assignment) +local requestor=qitem.warehouse.alias +local airbasename=qitem.warehouse:GetAirbaseName() +local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory() +local assetdesc=qitem.assetdesc +local assetdescval=qitem.assetdescval +if assetdesc==WAREHOUSE.Descriptor.ASSETLIST then +assetdescval="Asset list" +end +local nasset=tostring(qitem.nasset) +local ndelivered=tostring(qitem.ndelivered) +local ncargogroupset="N/A" +if qitem.cargogroupset then +ncargogroupset=tostring(qitem.cargogroupset:Count()) +end +local transporttype="N/A" +if qitem.transporttype then +transporttype=qitem.transporttype +end +local ntransport="N/A" +if qitem.ntransport then +ntransport=tostring(qitem.ntransport) +end +local ntransportalive="N/A" +if qitem.transportgroupset then +ntransportalive=tostring(qitem.transportgroupset:Count()) +end +local ntransporthome="N/A" +if qitem.ntransporthome then +ntransporthome=tostring(qitem.ntransporthome) +end +text=text..string.format( +"\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s", +i,uid,prio,clock,assignment,requestor,airbasename,requestorAirbaseCat,assetdesc,assetdescval,nasset,ncargogroupset,ndelivered,transporttype,ntransport,ntransportalive,ntransporthome) +end +if#queue==0 then +self:I(self.lid..text) +else +if total~="Empty"then +self:I(self.lid..text) +end +end +end +end +function WAREHOUSE:_DisplayStatus() +if self.verbosity>=3 then +local text=string.format("\n------------------------------------------------------\n") +text=text..string.format("Warehouse %s status: %s\n",self.alias,self:GetState()) +text=text..string.format("------------------------------------------------------\n") +text=text..string.format("Coalition name = %s\n",self:GetCoalitionName()) +text=text..string.format("Country name = %s\n",self:GetCountryName()) +text=text..string.format("Airbase name = %s (category=%d)\n",self:GetAirbaseName(),self:GetAirbaseCategory()) +text=text..string.format("Queued requests = %d\n",#self.queue) +text=text..string.format("Pending requests = %d\n",#self.pending) +text=text..string.format("------------------------------------------------------\n") +text=text..self:_GetStockAssetsText() +self:I(text) +end +end +function WAREHOUSE:_GetStockAssetsText(messagetoall) +local _data=self:GetStockInfo(self.stock) +local text="Stock:\n" +local total=0 +for _attribute,_count in pairs(_data)do +if _count>0 then +local attribute=tostring(UTILS.Split(_attribute,"_")[2]) +text=text..string.format("%s = %d\n",attribute,_count) +total=total+_count +end +end +text=text..string.format("===================\n") +text=text..string.format("Total = %d\n",total) +text=text..string.format("------------------------------------------------------\n") +MESSAGE:New(text,10):ToAllIf(messagetoall) +return text +end +function WAREHOUSE:_UpdateWarehouseMarkText() +if self.markerOn then +local text=string.format("Warehouse state: %s\nTotal assets in stock %d:\n",self:GetState(),#self.stock) +for _attribute,_count in pairs(self:GetStockInfo(self.stock)or{})do +if _count>0 then +local attribute=tostring(UTILS.Split(_attribute,"_")[2]) +text=text..string.format("%s=%d, ",attribute,_count) +end +end +local coordinate=self:GetCoordinate() +local coalition=self:GetCoalition() +if not self.markerWarehouse then +self.markerWarehouse=MARKER:New(coordinate,text):ToCoalition(coalition) +else +local refresh=false +if self.markerWarehouse.text~=text then +self.markerWarehouse.text=text +refresh=true +end +if self.markerWarehouse.coordinate~=coordinate then +self.markerWarehouse.coordinate=coordinate +refresh=true +end +if self.markerWarehouse.coalition~=coalition then +self.markerWarehouse.coalition=coalition +refresh=true +end +if refresh then +self.markerWarehouse:Refresh() +end +end +end +end +function WAREHOUSE:_DisplayStockItems(stock) +local text=self.lid..string.format("Warehouse %s stock assets:",self.alias) +for _i,_stock in pairs(stock)do +local mystock=_stock +local name=mystock.templatename +local category=mystock.category +local cargobaymax=mystock.cargobaymax +local cargobaytot=mystock.cargobaytot +local nunits=mystock.nunits +local range=mystock.range +local size=mystock.size +local speed=mystock.speedmax +local uid=mystock.uid +local unittype=mystock.unittype +local weight=mystock.weight +local attribute=mystock.attribute +text=text..string.format("\n%02d) uid=%d, name=%s, unittype=%s, category=%d, attribute=%s, nunits=%d, speed=%.1f km/h, range=%.1f km, size=%.1f m, weight=%.1f kg, cargobax max=%.1f kg tot=%.1f kg", +_i,uid,name,unittype,category,attribute,nunits,speed,range/1000,size,weight,cargobaymax,cargobaytot) +end +self:T3(text) +end +function WAREHOUSE:_Fireworks(coord) +coord=coord or self:GetCoordinate() +for i=1,91 do +local color=math.random(0,3) +coord:Flare(color,i-1) +end +end +function WAREHOUSE:_InfoMessage(text,duration) +duration=duration or 20 +if duration>0 and self.Debug or self.Report then +MESSAGE:New(text,duration):ToCoalition(self:GetCoalition()) +end +self:I(self.lid..text) +end +function WAREHOUSE:_DebugMessage(text,duration) +duration=duration or 20 +if duration>0 then +MESSAGE:New(text,duration):ToAllIf(self.Debug) +end +self:T(self.lid..text) +end +function WAREHOUSE:_ErrorMessage(text,duration) +duration=duration or 20 +if duration>0 then +MESSAGE:New(text,duration):ToAll() +end +self:E(self.lid..text) +end +function WAREHOUSE:_GetMaxHeight(D,alphaC,alphaD,Hdep,Hdest,Deltahhold) +local Hhold=Hdest+Deltahhold +local hdest=Hdest-Hdep +local hhold=hdest+Deltahhold +local Dp=math.sqrt(D^2+hhold^2) +local alphaS=math.atan(hdest/D) +local alphaH=math.atan(hhold/D) +local alphaCp=alphaC-alphaH +local alphaDp=alphaD+alphaH +local gammap=math.pi-alphaCp-alphaDp +local sCp=Dp*math.sin(alphaDp)/math.sin(gammap) +local sDp=Dp*math.sin(alphaCp)/math.sin(gammap) +local hmax=sCp*math.sin(alphaC) +if self.Debug then +env.info(string.format("Hdep = %.3f km",Hdep/1000)) +env.info(string.format("Hdest = %.3f km",Hdest/1000)) +env.info(string.format("DetaHold= %.3f km",Deltahhold/1000)) +env.info() +env.info(string.format("D = %.3f km",D/1000)) +env.info(string.format("Dp = %.3f km",Dp/1000)) +env.info() +env.info(string.format("alphaC = %.3f Deg",math.deg(alphaC))) +env.info(string.format("alphaCp = %.3f Deg",math.deg(alphaCp))) +env.info() +env.info(string.format("alphaD = %.3f Deg",math.deg(alphaD))) +env.info(string.format("alphaDp = %.3f Deg",math.deg(alphaDp))) +env.info() +env.info(string.format("alphaS = %.3f Deg",math.deg(alphaS))) +env.info(string.format("alphaH = %.3f Deg",math.deg(alphaH))) +env.info() +env.info(string.format("sCp = %.3f km",sCp/1000)) +env.info(string.format("sDp = %.3f km",sDp/1000)) +env.info() +env.info(string.format("hmax = %.3f km",hmax/1000)) +env.info() +local hdescent=hmax-hhold +local dClimb=hmax/math.tan(alphaC) +local dDescent=(hmax-hhold)/math.tan(alphaD) +local dCruise=D-dClimb-dDescent +env.info(string.format("hmax = %.3f km",hmax/1000)) +env.info(string.format("hdescent = %.3f km",hdescent/1000)) +env.info(string.format("Dclimb = %.3f km",dClimb/1000)) +env.info(string.format("Dcruise = %.3f km",dCruise/1000)) +env.info(string.format("Ddescent = %.3f km",dDescent/1000)) +env.info() +end +return hmax +end +function WAREHOUSE:_GetFlightplan(asset,departure,destination) +local Vmax=asset.speedmax/3.6 +local Range=asset.range +local category=asset.category +local ceiling=asset.DCSdesc.Hmax +local Vymax=asset.DCSdesc.VyMax +local VxCruiseMax=0.90*Vmax +local VxCruiseMin=math.min(VxCruiseMax*0.70,166) +local VxCruise=UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin,(VxCruiseMax-VxCruiseMax)/4,VxCruiseMin,VxCruiseMax) +local VxClimb=math.min(Vmax*0.90,200) +local VxDescent=math.min(Vmax*0.60,140) +local VxHolding=VxDescent*0.9 +local VxFinal=VxHolding*0.9 +local VyClimb=math.min(7.6,Vymax) +local AlphaClimb=math.rad(4) +local AlphaDescent=math.rad(4) +local FLcruise_expect=150*RAT.unit.FL2m +if category==Group.Category.HELICOPTER then +FLcruise_expect=1000 +end +local Pdeparture=departure:GetCoordinate() +local H_departure=Pdeparture.y +local Pdestination=destination:GetCoordinate() +local H_destination=Pdestination.y +local Rhmin=5000 +local Rhmax=10000 +if category==Group.Category.HELICOPTER then +Rhmin=500 +Rhmax=1000 +end +local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax,Rhmin) +local d_holding=Pholding:Get2DDistance(Pdestination) +local H_holding=Pholding.y +local heading=Pdeparture:HeadingTo(Pholding) +local d_total=Pdeparture:Get2DDistance(Pholding) +local h_holding=1200 +if category==Group.Category.HELICOPTER then +h_holding=150 +end +h_holding=UTILS.Randomize(h_holding,0.2) +local DeltaholdingMax=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,0) +if h_holding>DeltaholdingMax then +h_holding=math.abs(DeltaholdingMax) +end +local Hh_holding=H_holding+h_holding +local h_max=self:_GetMaxHeight(d_total,AlphaClimb,AlphaDescent,H_departure,H_holding,h_holding) +local FLmax=h_max+H_departure +local FLmin=math.max(H_departure,Hh_holding) +FLmax=math.min(FLmax,ceiling) +if FLmin>FLmax then +FLmin=FLmax +end +if FLcruise_expectFLmax then +FLcruise_expect=FLmax +end +local FLcruise=UTILS.RandomGaussian(FLcruise_expect,math.abs(FLmax-FLmin)/4,FLmin,FLmax) +local h_climb=FLcruise-H_departure +local h_descent=FLcruise-Hh_holding +local d_climb=h_climb/math.tan(AlphaClimb) +local d_descent=h_descent/math.tan(AlphaDescent) +local d_cruise=d_total-d_climb-d_descent +local text=string.format("Flight plan:\n") +text=text..string.format("Vx max = %.2f km/h\n",Vmax*3.6) +text=text..string.format("Vx climb = %.2f km/h\n",VxClimb*3.6) +text=text..string.format("Vx cruise = %.2f km/h\n",VxCruise*3.6) +text=text..string.format("Vx descent = %.2f km/h\n",VxDescent*3.6) +text=text..string.format("Vx holding = %.2f km/h\n",VxHolding*3.6) +text=text..string.format("Vx final = %.2f km/h\n",VxFinal*3.6) +text=text..string.format("Vy max = %.2f m/s\n",Vymax) +text=text..string.format("Vy climb = %.2f m/s\n",VyClimb) +text=text..string.format("Alpha Climb = %.2f Deg\n",math.deg(AlphaClimb)) +text=text..string.format("Alpha Descent = %.2f Deg\n",math.deg(AlphaDescent)) +text=text..string.format("Dist climb = %.3f km\n",d_climb/1000) +text=text..string.format("Dist cruise = %.3f km\n",d_cruise/1000) +text=text..string.format("Dist descent = %.3f km\n",d_descent/1000) +text=text..string.format("Dist total = %.3f km\n",d_total/1000) +text=text..string.format("h_climb = %.3f km\n",h_climb/1000) +text=text..string.format("h_desc = %.3f km\n",h_descent/1000) +text=text..string.format("h_holding = %.3f km\n",h_holding/1000) +text=text..string.format("h_max = %.3f km\n",h_max/1000) +text=text..string.format("FL min = %.3f km\n",FLmin/1000) +text=text..string.format("FL expect = %.3f km\n",FLcruise_expect/1000) +text=text..string.format("FL cruise * = %.3f km\n",FLcruise/1000) +text=text..string.format("FL max = %.3f km\n",FLmax/1000) +text=text..string.format("Ceiling = %.3f km\n",ceiling/1000) +text=text..string.format("Max range = %.3f km\n",Range/1000) +self:T(self.lid..text) +if d_cruise<0 then +d_cruise=100 +end +local wp={} +local c={} +c[#c+1]=Pdeparture +wp[#wp+1]=Pdeparture:WaypointAir("RADIO",COORDINATE.WaypointType.TakeOffParking,COORDINATE.WaypointAction.FromParkingArea,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", +Debug=false, +lid=nil, +menuadded={}, +menudisabled=nil, +destroy=nil, +launchalert=nil, +marklaunch=nil, +missiles={}, +players={}, +safezones={}, +launchzones={}, +protectedset=nil, +explosionpower=0.1, +explosiondist=200, +explosiondist2=500, +bigmissilemass=50, +destroy=nil, +dt50=5, +dt10=1, +dt05=0.5, +dt01=0.1, +dt00=0.01, +} +FOX.MenuF10={} +FOX.MenuF10Root=nil +FOX.version="0.6.1" +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: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) +self:I(self.lid..string.format("Missile trainer status %s: %s",clock,fsmstate)) +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 +self:I(self.lid..text) +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:GetSetObjects())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:onafterMissileLaunch(From,Event,To,missile) +local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s",missile.missileType,missile.missileName,tostring(missile.targetName),missile.shooterName) +self:I(FOX.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +for _,_player in pairs(self.players)do +local player=_player +local playerUnit=player.unit +if playerUnit and playerUnit:IsAlive()and player.coalition~=missile.shooterCoalition then +local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord) +local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord) +if player.launchalert then +if(missile.targetPlayer and player.unitname==missile.targetPlayer.unitname)or(distance=self.bigmissilemass +end +if destroymissile and self:_CheckCoordSafe(targetCoord)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)) +_ordnance:destroy() +missile.active=false +if self.Debug then +missileCoord:SmokeRed() +targetCoord:SmokeGreen() +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 +return nil +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 +return timer.getTime()+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)) +return timer.getTime()+0.1 +end +else +if target then +local player=self:_GetPlayerFromUnit(target) +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.")) +return nil +end +end +self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds.")) +timer.scheduleFunction(trackMissile,missile.weapon,timer.getTime()+0.0001) +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 +SCHEDULER:New(nil,self._AddF10Commands,{self,_unitName},0.1) +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 then +return +end +if EventData.IniDCSUnit==nil then +return +end +local _weapon=EventData.WeaponName +local _target=EventData.Weapon:getTarget() +local _targetName="unknown" +local _targetUnit=nil +local desc=EventData.Weapon:getDesc() +self:T2({desc=desc}) +local weaponcategory=desc.category +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))) +self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s",tostring(weaponcategory))) +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=weaponcategory==1 and missilecategory and(missilecategory==1 or missilecategory==2 or missilecategory==6) +if _track then +local missile={} +missile.active=true +missile.weapon=EventData.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 +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)) +end +else +self:E(self.lid..string.format("ERROR: Player unit does not exist in AddF10Menu() function. Unit name: %s.",_unitName)) +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 inzone=zone:IsCoordinateInZone(coord) +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 inzone=zone:IsCoordinateInZone(coord) +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={}, +lid="", +Detection=nil, +AWACS_Detection=nil, +debug=false, +checkradius=25000, +grouping=5000, +acceptrange=80000, +detectinterval=30, +engagerange=75, +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=15000, +UseEmOnOff=true, +} +do +function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs,EmOnOff) +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=coaltion or"red" +self.SAM_Table={} +self.dynamic=dynamic or false +self.checkradius=25000 +self.grouping=5000 +self.acceptrange=80000 +self.detectinterval=30 +self.engagerange=75 +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=15000 +if EmOnOff then +if EmOnOff==false then +self.UseEmOnOff=false +end +end +if type(awacs)=="string"then +self.advAwacs=true +else +self.advAwacs=false +end +local self=BASE:Inherit(self,BASE:New()) +self.lid=string.format("MANTIS %s | ",self.name) +if self.debug then +BASE:TraceOnOff(true) +BASE:TraceClass(self.ClassName) +BASE:TraceLevel(1) +end +if self.dynamic then +self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() +self.EWR_Group=SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterStart() +else +self.SAM_Group=SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() +self.EWR_Group=SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterOnce() +end +if self.HQ_Template_CC then +self.HQ_CC=GROUP:FindByName(self.HQ_Template_CC) +end +self.version="0.4.1" +self:I(string.format("***** Starting MANTIS Version %s *****",self.version)) +return self +end +function MANTIS:_GetSAMTable() +return self.SAM_Table +end +function MANTIS:_SetSAMTable(table) +self.SAM_Table=table +return self +end +function MANTIS:SetEWRGrouping(radius) +local radius=radius or 5000 +self.grouping=radius +end +function MANTIS:SetEWRRange(radius) +local radius=radius or 80000 +self.acceptrange=radius +end +function MANTIS:SetSAMRadius(radius) +local radius=radius or 25000 +self.checkradius=radius +end +function MANTIS:SetSAMRange(range) +local range=range or 75 +if range<0 or range>100 then +range=75 +end +self.engagerange=range +end +function MANTIS:SetNewSAMRangeWhileRunning(range) +local range=range or 75 +if range<0 or range>100 then +range=75 +end +self.engagerange=range +self:_RefreshSAMTable() +self.mysead.EngagementRange=range +end +function MANTIS:Debug(onoff) +local onoff=onoff or false +self.debug=onoff +end +function MANTIS:GetCommandCenter() +if self.HQ_CC then +return self.HQ_CC +else +return nil +end +end +function MANTIS:SetAwacs(prefix) +if prefix~=nil then +if type(prefix)=="string"then +self.AWACS_Prefix=prefix +self.advAwacs=true +end +end +end +function MANTIS:SetAwacsRange(range) +local range=range or 250000 +self.awacsrange=range +end +function MANTIS:SetCommandCenter(group) +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 +end +function MANTIS:SetDetectInterval(interval) +local interval=interval or 30 +self.detectinterval=interval +end +function MANTIS:SetAdvancedMode(onoff,ratio) +self:F({onoff,ratio}) +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() +env.info(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() +BASE:E(text) +end +end +function MANTIS:SetUsingEmOnOff(switch) +self.UseEmOnOff=switch or false +end +function MANTIS:_CheckHQState() +local text=self.lid.." Checking HQ State" +self:T(text) +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then env.info(text)end +if self.advanced then +local hq=self.HQ_Template_CC +local hqgrp=GROUP:FindByName(hq) +if hqgrp then +if hqgrp:IsAlive()then +env.info(self.lid.." HQ is alive!") +return true +else +env.info(self.lid.." HQ is dead!") +return false +end +end +end +end +function MANTIS:_CheckEWRState() +local text=self.lid.." Checking EWR State" +self:F(text) +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then env.info(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 +env.info(self.lid..string.format(" No of EWR alive is %d",nalive)) +if nalive>0 then +return true +else +return false +end +end +end +function MANTIS:_CheckAdvState() +local text=self.lid.." Checking Advanced State" +self:F(text) +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then env.info(text)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) +local text=self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d",currstate,self.adv_state,newinterval) +self:F(text) +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then env.info(text)end +return newinterval,currstate +end +function MANTIS:SetAutoRelocate(hq,ewr) +self:F({hq,ewr}) +local hqrel=hq or false +local ewrel=ewr or false +if hqrel or ewrel then +self.autorelocate=true +self.autorelocateunits={HQ=hqrel,EWR=ewrel} +self:T({self.autorelocate,self.autorelocateunits}) +end +end +function MANTIS:_RelocateGroups() +self:T(self.lid.." Relocating Groups") +local text=self.lid.." Relocating Groups" +local m=MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug) +if self.verbose then env.info(text)end +if self.autorelocate then +if self.autorelocateunits.HQ and self.HQ_CC then +local _hqgrp=self.HQ_CC +self:T(self.lid.." Relocating HQ") +local text=self.lid.." Relocating HQ" +local m=MESSAGE:New(text,10,"MANTIS"):ToAll() +_hqgrp:RelocateGroundRandomInRadius(20,500,true,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:IsGround()then +self:T(self.lid.." Relocating EWR ".._grp:GetName()) +local text=self.lid.." Relocating EWR ".._grp:GetName() +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +if self.verbose then env.info(text)end +_grp:RelocateGroundRandomInRadius(20,500,true,true) +end +end +end +end +end +function MANTIS:CheckObjectInZone(dectset,samcoordinate) +self:F(self.lid.."CheckObjectInZone Called") +local radius=self.checkradius +local set=dectset +for _,_coord in pairs(set)do +local coord=_coord +local dectstring=coord:ToStringLLDMS() +local samstring=samcoordinate:ToStringLLDMS() +local targetdistance=samcoordinate:DistanceFromPointVec2(coord) +local text=string.format("Checking SAM at % s - Distance %d m - Target %s",samstring,targetdistance,dectstring) +local m=MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) +if self.verbose then env.info(self.lid..text)end +if targetdistance<=radius then +return true,targetdistance +end +end +return false,0 +end +function MANTIS:StartDetection() +self:F(self.lid.."Starting Detection") +local groupset=self.EWR_Group +local grouping=self.grouping or 5000 +local acceptrange=self.acceptrange or 80000 +local interval=self.detectinterval or 60 +_MANTISdetection=DETECTION_AREAS:New(groupset,grouping) +_MANTISdetection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) +_MANTISdetection:SetAcceptRange(acceptrange) +_MANTISdetection:SetRefreshTimeInterval(interval) +_MANTISdetection:Start() +function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) +local debug=false +if DetectedItem.IsDetected and debug then +local Coordinate=DetectedItem.Coordinate +local text="MANTIS: Detection at "..Coordinate:ToStringLLDMS() +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +end +end +return _MANTISdetection +end +function MANTIS:StartAwacsDetection() +self:F(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 +_MANTISAwacs=DETECTION_AREAS:New(groupset,grouping) +_MANTISAwacs:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) +_MANTISAwacs:SetAcceptRange(self.awacsrange) +_MANTISAwacs:SetRefreshTimeInterval(interval) +_MANTISAwacs:Start() +function _MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) +local debug=false +if DetectedItem.IsDetected and debug then +local Coordinate=DetectedItem.Coordinate +local text="Awacs Detection at "..Coordinate:ToStringLLDMS() +local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) +end +end +return _MANTISAwacs +end +function MANTIS:SetSAMStartState() +self:F(self.lid.."Setting SAM Start States") +local SAM_SET=self.SAM_Group +local SAM_Grps=SAM_SET.Set +local SAM_Tbl={} +local SEAD_Grps={} +local engagerange=self.engagerange +for _i,_group in pairs(SAM_Grps)do +local group=_group +if self.UseEmOnOff then +group:EnableEmission(false) +else +group:OptionAlarmStateGreen() +end +group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) +if group:IsGround()then +local grpname=group:GetName() +local grpcoord=group:GetCoordinate() +table.insert(SAM_Tbl,{grpname,grpcoord}) +table.insert(SEAD_Grps,grpname) +end +end +self.SAM_Table=SAM_Tbl +local mysead=SEAD:New(SEAD_Grps) +mysead:SetEngagementRange(engagerange) +self.mysead=mysead +return self +end +function MANTIS:_RefreshSAMTable() +self:F(self.lid.."Setting SAM Start States") +local SAM_SET=self.SAM_Group +local SAM_Grps=SAM_SET.Set +local SAM_Tbl={} +local SEAD_Grps={} +local engagerange=self.engagerange +for _i,_group in pairs(SAM_Grps)do +local group=_group +group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) +if group:IsGround()then +local grpname=group:GetName() +local grpcoord=group:GetCoordinate() +table.insert(SAM_Tbl,{grpname,grpcoord}) +table.insert(SEAD_Grps,grpname) +end +end +self.SAM_Table=SAM_Tbl +if self.mysead~=nil then +local mysead=self.mysead +mysead:UpdateSet(SEAD_Grps) +end +return self +end +function MANTIS:AddShorad(Shorad,Shoradtime) +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 +end +function MANTIS:RemoveShorad() +self.ShoradLink=false +end +function MANTIS:Start() +self:F(self.lid.."Starting MANTIS") +self:SetSAMStartState() +self.Detection=self:StartDetection() +if self.advAwacs then +self.AWACS_Detection=self:StartAwacsDetection() +end +local function check(detection) +local detset=detection:GetDetectedItemCoordinates() +self:F("Check:",{detset}) +local rand=math.random(1,100) +if rand>65 then +self:_RefreshSAMTable() +end +local samset=self:_GetSAMTable() +for _,_data in pairs(samset)do +local samcoordinate=_data[2] +local name=_data[1] +local samgroup=GROUP:FindByName(name) +local IsInZone,Distance=self:CheckObjectInZone(detset,samcoordinate) +if IsInZone then +if samgroup:IsAlive()then +if self.UseEmOnOff then +samgroup:EnableEmission(true) +end +samgroup:OptionAlarmStateRed() +if self.ShoradLink and Distance100)or(low>high)then +low=70 +end +if(high<0)or(high>100)or(highTstop 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 +self:I(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(interval) +self.dTqueue=interval or 30 +return self +end +function AIRBOSS:SetLSOCallInterval(timeinterval) +self.LSOdT=timeinterval or 4 +return self +end +function AIRBOSS:SetAirbossNiceGuy(switch) +if switch==true or switch==nil then +self.airbossnice=true +else +self.airbossnice=false +end +return self +end +function AIRBOSS:SetEmergencyLandings(switch) +if switch==true or switch==nil then +self.emergency=true +else +self.emergency=false +end +return self +end +function AIRBOSS:SetDespawnOnEngineShutdown(switch) +if switch==true or switch==nil then +self.despawnshutdown=true +else +self.despawnshutdown=false +end +return self +end +function AIRBOSS:SetRespawnAI(switch) +if switch==true or switch==nil then +self.respawnAI=true +else +self.respawnAI=false +end +return self +end +function AIRBOSS:SetRefuelAI(lowfuelthreshold) +self.lowfuelAI=lowfuelthreshold or 10 +return self +end +function AIRBOSS:SetInitialMaxAlt(altitude) +self.initialmaxalt=UTILS.FeetToMeters(altitude 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(interval) +self.dTstatus=interval 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) +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 +return self +end +function AIRBOSS:SetLineupErrorThresholds(_max,_min,Left,LeftMed,LEFT,Right,RightMed,RIGHT) +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 +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(interval) +self.dTbeacon=interval or 20*60 +return self +end +function AIRBOSS:SetLSORadio(frequency,modulation) +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" +return self +end +function AIRBOSS:SetMarshalRadio(frequency,modulation) +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" +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:I(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:I(self.lid..text) +end +end +function AIRBOSS:SetMaxLandingPattern(nmax) +nmax=nmax or 4 +nmax=math.max(nmax,1) +nmax=math.min(nmax,6) +self.Nmaxpattern=nmax +return self +end +function AIRBOSS:SetMaxMarshalStacks(nmax) +self.Nmaxmarshal=nmax or 3 +self.Nmaxmarshal=math.max(self.Nmaxmarshal,1) +return self +end +function AIRBOSS:SetMaxSectionSize(nmax) +nmax=nmax or 2 +nmax=math.max(nmax,1) +nmax=math.min(nmax,4) +self.NmaxSection=nmax-1 +return self +end +function AIRBOSS:SetMaxFlightsPerStack(nmax) +nmax=nmax or 2 +nmax=math.max(nmax,1) +nmax=math.min(nmax,4) +self.NmaxStack=nmax +return self +end +function AIRBOSS:SetHandleAION() +self.handleai=true +return self +end +function AIRBOSS: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:GetNextRecoveryTime(InSeconds) +if self.recoverywindow then +if InSeconds then +return self.recoverywindow.START,self.recoverywindow.STOP +else +return UTILS.SecondsToClock(self.recoverywindow.START),UTILS.SecondsToClock(self.recoverywindow.STOP) +end +else +if InSeconds then +return-1,-1 +else +return"?","?" +end +end +end +function AIRBOSS:IsRecovering() +return self:is("Recovering") +end +function AIRBOSS:IsIdle() +return self:is("Idle") +end +function AIRBOSS:IsPaused() +return self:is("Paused") +end +function AIRBOSS:_ActivateBeacons() +self:T(self.lid..string.format("Activating Beacons (TACAN=%s, ICLS=%s)",tostring(self.TACANon),tostring(self.ICLSon))) +if self.TACANon then +self:I(self.lid..string.format("Activating TACAN Channel %d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse)) +self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true) +end +if self.ICLSon then +self:I(self.lid..string.format("Activating ICLS Channel %d (%s)",self.ICLSchannel,self.ICLSmorse)) +self.beacon:ActivateICLS(self.ICLSchannel,self.ICLSmorse) +end +self.Tbeacon=timer.getTime() +end +function AIRBOSS:onafterStart(From,Event,To) +self:I(self.lid..string.format("Starting AIRBOSS v%s for carrier unit %s of type %s on map %s",AIRBOSS.version,self.carrier:GetName(),self.carriertype,self.theatre)) +self:_ActivateBeacons() +self.Cposition=self:GetCoordinate() +self.Corientation=self.carrier:GetOrientationX() +self.Corientlast=self.Corientation +self.Tpupdate=timer.getTime() +if#self.recoverytimes==0 and false then +local Topen=timer.getAbsTime()+15*60 +local Tclose=Topen+3*60*60 +self:AddRecoveryWindow(UTILS.SecondsToClock(Topen),UTILS.SecondsToClock(Tclose)) +end +self:_CheckRecoveryTimes() +self.Tqueue=timer.getTime()-60 +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.Land) +self:HandleEvent(EVENTS.EngineShutdown) +self:HandleEvent(EVENTS.Takeoff) +self:HandleEvent(EVENTS.Crash) +self:HandleEvent(EVENTS.Ejection) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self._PlayerLeft) +self:HandleEvent(EVENTS.MissionEnd) +self:HandleEvent(EVENTS.RemoveUnit) +self.StatusTimer=TIMER:New(self._Status,self):Start(2,0.5) +self:__Status(1) +end +function AIRBOSS:onafterStatus(From,Event,To) +local time=timer.getTime() +if time-self.Tqueue>self.dTqueue then +local clock=UTILS.SecondsToClock(timer.getAbsTime()) +local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) +local hdg=self:GetHeading() +local pos=self:GetCoordinate() +local speed=self.carrier:GetVelocityKNOTS() +local collision=false +local holdtime=0 +if self.holdtimestamp then +holdtime=timer.getTime()-self.holdtimestamp +end +local NextWP=self:_GetNextWaypoint() +local ExpectedSpeed=UTILS.MpsToKnots(NextWP:GetVelocity()) +if speed<0.5 and ExpectedSpeed>0 and not(self.detour or self.turnintowind)then +if not self.holdtimestamp then +self:E(self.lid..string.format("Carrier came to an unexpected standstill. Trying to re-route in 3 min. Speed=%.1f knots, expected=%.1f knots",speed,ExpectedSpeed)) +self.holdtimestamp=timer.getTime() +else +if holdtime>3*60 then +local coord=self:GetCoordinate():Translate(500,hdg+10) +self:CarrierResumeRoute(coord) +self.holdtimestamp=nil +end +end +end +local text=string.format("Time %s - Status %s (case=%d) - Speed=%.1f kts - Heading=%d - WP=%d - ETA=%s - Turning=%s - Collision Warning=%s - Detour=%s - Turn Into Wind=%s - Holdtime=%d sec", +clock,self:GetState(),self.case,speed,hdg,self.currentwp,eta,tostring(self.turning),tostring(collision),tostring(self.detour),tostring(self.turnintowind),holdtime) +self:T(self.lid..text) +text="Players:" +local i=0 +for _name,_player in pairs(self.players)do +i=i+1 +local player=_player +text=text..string.format("\n%d.) %s: Step=%s, Unit=%s, Airframe=%s",i,tostring(player.name),tostring(player.step),tostring(player.unitname),tostring(player.actype)) +end +if i==0 then +text=text.." none" +end +self:I(self.lid..text) +if collision then +if self.turnintowind then +self:CarrierResumeRoute(self.Creturnto) +if self:IsRecovering()and self.recoverywindow and self.recoverywindow.WIND then +self.recoverywindow.WIND=false +end +end +end +self:_CheckRecoveryTimes() +self:_ScanCarrierZone() +self:_CheckQueue() +self:_CheckCarrierTurning() +self:_CheckPatternUpdate() +self.Tqueue=time +end +if time-self.Tbeacon>self.dTbeacon then +self:_ActivateBeacons() +end +self:__Status(-30) +end +function AIRBOSS:_Status() +self:_CheckPlayerStatus() +self:_CheckAIStatus() +end +function AIRBOSS:_CheckAIStatus() +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if flight.ai then +local fuel=flight.group:GetFuelMin()*100 +local text=string.format("Group %s fuel=%.1f %%",flight.groupname,fuel) +self:T3(self.lid..text) +if self.lowfuelAI and fuel=recovery.START then +if time0 then +local extmin=5*npattern +recovery.STOP=recovery.STOP+extmin*60 +local text=string.format("We still got flights in the pattern.\nRecovery time prolonged by %d minutes.\nNow get your act together and no more bolters!",extmin) +self:MessageToPattern(text,"AIRBOSS","99",10,false,nil) +else +self:RecoveryStop() +state="closing now" +recovery.OPEN=false +recovery.OVER=true +end +else +state="closed" +end +end +else +state="in the future" +if nextwindow==nil then +nextwindow=recovery +state="next in line" +end +end +text=text..string.format("\n- Start=%s Stop=%s Case=%d Offset=%d Open=%s Closed=%s Status=\"%s\"",Cstart,Cstop,recovery.CASE,recovery.OFFSET,tostring(recovery.OPEN),tostring(recovery.OVER),state) +end +self:T(self.lid..text) +self.recoverywindow=nil +if self:IsIdle()then +if nextwindow then +self:RecoveryCase(nextwindow.CASE,nextwindow.OFFSET) +if nextwindow.WIND and nextwindow.START-time5 +local _,vwind=self:GetWind() +if vwind<0.1 then +uturn=false +end +if not nextwindow.UTURN then +uturn=false +end +self:T(self.lid..string.format("Heading=%03d°, Wind=%03d° %.1f kts, Delta=%03d° ==> U-turn=%s",hdg,wind,UTILS.MpsToKnots(vwind),delta,tostring(uturn))) +local t=math.max(nextwindow.STOP-nextwindow.START+self.dTturn,60*60*24) +local v=UTILS.KnotsToMps(nextwindow.SPEED) +local vmax=self.carrier:GetSpeedMax()/3.6 +v=math.min(v,vmax) +self:CarrierTurnIntoWind(t,v,uturn) +end +self.recoverywindow=nextwindow +else +self:RecoveryCase() +end +else +if currwindow then +self.recoverywindow=currwindow +else +self.recoverywindow=nextwindow +end +end +self:T2({"FF",recoverywindow=self.recoverywindow}) +end +function AIRBOSS:_GetFlightLead(flight) +if flight.name~=flight.seclead then +local lead=self.players[flight.seclead] +return lead,false +else +return flight,true +end +end +function AIRBOSS:onbeforeRecoveryCase(From,Event,To,Case,Offset) +Case=Case or self.defaultcase +Offset=Offset or self.defaultoffset +if Case==self.case and Offset==self.holdingoffset then +return false +end +return true +end +function AIRBOSS:onafterRecoveryCase(From,Event,To,Case,Offset) +Case=Case or self.defaultcase +Offset=Offset or self.defaultoffset +local text=string.format("Switching recovery case %d ==> %d",self.case,Case) +if Case>1 then +text=text..string.format(" Holding offset angle %d degrees.",Offset) +end +MESSAGE:New(text,20,self.alias):ToAllIf(self.Debug) +self:T(self.lid..text) +self.case=Case +self.holdingoffset=Offset +for _,_flight in pairs(self.flights)do +local flight=_flight +if not(self:_InQueue(self.Qmarshal,flight.group)or self:_InQueue(self.Qpattern,flight.group))then +if flight.name~=flight.seclead then +local lead=self.players[flight.seclead] +if lead and not(self:_InQueue(self.Qmarshal,lead.group)or self:_InQueue(self.Qpattern,lead.group))then +flight.case=self.case +end +else +flight.case=self.case +end +end +end +end +function AIRBOSS:onafterRecoveryStart(From,Event,To,Case,Offset) +Case=Case or self.defaultcase +Offset=Offset or self.defaultoffset +self:_MarshalCallRecoveryStart(Case) +self:RecoveryCase(Case,Offset) +end +function AIRBOSS:onafterRecoveryStop(From,Event,To) +self:T(self.lid..string.format("Stopping aircraft recovery.")) +self:_MarshalCallRecoveryStopped(self.case) +if self.turnintowind then +local coord=self.Creturnto +if self.recoverywindow and self.recoverywindow.UTURN==false then +coord=nil +end +self:CarrierResumeRoute(coord) +end +if self.recoverywindow and self.recoverywindow.OPEN==true then +self.recoverywindow.OPEN=false +self.recoverywindow.OVER=true +self:DeleteRecoveryWindow(self.recoverywindow) +end +self:_CheckRecoveryTimes() +end +function AIRBOSS:onafterRecoveryPause(From,Event,To,duration) +self:T(self.lid..string.format("Pausing aircraft recovery.")) +if duration then +self:__RecoveryUnpause(duration) +local clock=UTILS.SecondsToClock(timer.getAbsTime()+duration) +self:_MarshalCallRecoveryPausedResumedAt(clock) +else +local text=string.format("aircraft recovery is paused until further notice.") +self:_MarshalCallRecoveryPausedNotice() +end +end +function AIRBOSS:onafterRecoveryUnpause(From,Event,To) +self:T(self.lid..string.format("Unpausing aircraft recovery.")) +self:_MarshalCallResumeRecovery() +end +function AIRBOSS:onafterPassingWaypoint(From,Event,To,n) +self:I(self.lid..string.format("Carrier passed waypoint %d.",n)) +end +function AIRBOSS:onafterIdle(From,Event,To) +self:T(self.lid..string.format("Carrier goes to idle.")) +end +function AIRBOSS:onafterStop(From,Event,To) +self:I(self.lid..string.format("Stopping airboss script.")) +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.Land) +self:UnHandleEvent(EVENTS.EngineShutdown) +self:UnHandleEvent(EVENTS.Takeoff) +self:UnHandleEvent(EVENTS.Crash) +self:UnHandleEvent(EVENTS.Ejection) +self:UnHandleEvent(EVENTS.PlayerLeaveUnit) +self:UnHandleEvent(EVENTS.MissionEnd) +self.CallScheduler:Clear() +end +function AIRBOSS:_InitStennis() +self.carrierparam.sterndist=-153 +self.carrierparam.deckheight=19.06 +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.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 +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.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: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.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.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, +}, +STABILIZED={ +file="LSO-Stabilized", +suffix="ogg", +loud=false, +subtitle="Stabilized", +duration=0.9, +subduration=5, +}, +IDLE={ +file="LSO-Idle", +suffix="ogg", +loud=false, +subtitle="Idle", +duration=0.45, +subduration=5, +}, +N0={ +file="LSO-N0", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N1={ +file="LSO-N1", +suffix="ogg", +loud=false, +subtitle="", +duration=0.25, +}, +N2={ +file="LSO-N2", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N3={ +file="LSO-N3", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N4={ +file="LSO-N4", +suffix="ogg", +loud=false, +subtitle="", +duration=0.39, +}, +N5={ +file="LSO-N5", +suffix="ogg", +loud=false, +subtitle="", +duration=0.39, +}, +N6={ +file="LSO-N6", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N7={ +file="LSO-N7", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N8={ +file="LSO-N8", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N9={ +file="LSO-N9", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +CLICK={ +file="AIRBOSS-RadioClick", +suffix="ogg", +loud=false, +subtitle="", +duration=0.35, +}, +NOISE={ +file="AIRBOSS-Noise", +suffix="ogg", +loud=false, +subtitle="", +duration=3.6, +}, +SPINIT={ +file="AIRBOSS-SpinIt", +suffix="ogg", +loud=false, +subtitle="", +duration=0.73, +subduration=5, +}, +} +self.PilotCall={ +N0={ +file="PILOT-N0", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N1={ +file="PILOT-N1", +suffix="ogg", +loud=false, +subtitle="", +duration=0.25, +}, +N2={ +file="PILOT-N2", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N3={ +file="PILOT-N3", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N4={ +file="PILOT-N4", +suffix="ogg", +loud=false, +subtitle="", +duration=0.39, +}, +N5={ +file="PILOT-N5", +suffix="ogg", +loud=false, +subtitle="", +duration=0.39, +}, +N6={ +file="PILOT-N6", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N7={ +file="PILOT-N7", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N8={ +file="PILOT-N8", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N9={ +file="PILOT-N9", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +POINT={ +file="PILOT-Point", +suffix="ogg", +loud=false, +subtitle="", +duration=0.33, +}, +SKYHAWK={ +file="PILOT-Skyhawk", +suffix="ogg", +loud=false, +subtitle="", +duration=0.95, +subduration=5, +}, +HARRIER={ +file="PILOT-Harrier", +suffix="ogg", +loud=false, +subtitle="", +duration=0.58, +subduration=5, +}, +HAWKEYE={ +file="PILOT-Hawkeye", +suffix="ogg", +loud=false, +subtitle="", +duration=0.63, +subduration=5, +}, +TOMCAT={ +file="PILOT-Tomcat", +suffix="ogg", +loud=false, +subtitle="", +duration=0.66, +subduration=5, +}, +HORNET={ +file="PILOT-Hornet", +suffix="ogg", +loud=false, +subtitle="", +duration=0.56, +subduration=5, +}, +VIKING={ +file="PILOT-Viking", +suffix="ogg", +loud=false, +subtitle="", +duration=0.61, +subduration=5, +}, +BALL={ +file="PILOT-Ball", +suffix="ogg", +loud=false, +subtitle="", +duration=0.50, +subduration=5, +}, +BINGOFUEL={ +file="PILOT-BingoFuel", +suffix="ogg", +loud=false, +subtitle="", +duration=0.80, +}, +GASATDIVERT={ +file="PILOT-GasAtDivert", +suffix="ogg", +loud=false, +subtitle="", +duration=1.80, +}, +GASATTANKER={ +file="PILOT-GasAtTanker", +suffix="ogg", +loud=false, +subtitle="", +duration=1.95, +}, +} +self.MarshalCall={ +AFFIRMATIVE={ +file="MARSHAL-Affirmative", +suffix="ogg", +loud=false, +subtitle="", +duration=0.90, +}, +ALTIMETER={ +file="MARSHAL-Altimeter", +suffix="ogg", +loud=false, +subtitle="", +duration=0.85, +}, +BRC={ +file="MARSHAL-BRC", +suffix="ogg", +loud=false, +subtitle="", +duration=0.80, +}, +CARRIERTURNTOHEADING={ +file="MARSHAL-CarrierTurnToHeading", +suffix="ogg", +loud=false, +subtitle="", +duration=2.48, +subduration=5, +}, +CASE={ +file="MARSHAL-Case", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +CHARLIETIME={ +file="MARSHAL-CharlieTime", +suffix="ogg", +loud=false, +subtitle="", +duration=0.90, +}, +CLEAREDFORRECOVERY={ +file="MARSHAL-ClearedForRecovery", +suffix="ogg", +loud=false, +subtitle="", +duration=1.25, +}, +DECKCLOSED={ +file="MARSHAL-DeckClosed", +suffix="ogg", +loud=false, +subtitle="", +duration=1.10, +subduration=5, +}, +DEGREES={ +file="MARSHAL-Degrees", +suffix="ogg", +loud=false, +subtitle="", +duration=0.60, +}, +EXPECTED={ +file="MARSHAL-Expected", +suffix="ogg", +loud=false, +subtitle="", +duration=0.55, +}, +FLYNEEDLES={ +file="MARSHAL-FlyYourNeedles", +suffix="ogg", +loud=false, +subtitle="Fly your needles", +duration=0.9, +subduration=5, +}, +HOLDATANGELS={ +file="MARSHAL-HoldAtAngels", +suffix="ogg", +loud=false, +subtitle="", +duration=1.10, +}, +HOURS={ +file="MARSHAL-Hours", +suffix="ogg", +loud=false, +subtitle="", +duration=0.60, +subduration=5, +}, +MARSHALRADIAL={ +file="MARSHAL-MarshalRadial", +suffix="ogg", +loud=false, +subtitle="", +duration=1.10, +}, +N0={ +file="MARSHAL-N0", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N1={ +file="MARSHAL-N1", +suffix="ogg", +loud=false, +subtitle="", +duration=0.25, +}, +N2={ +file="MARSHAL-N2", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N3={ +file="MARSHAL-N3", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N4={ +file="MARSHAL-N4", +suffix="ogg", +loud=false, +subtitle="", +duration=0.39, +}, +N5={ +file="MARSHAL-N5", +suffix="ogg", +loud=false, +subtitle="", +duration=0.39, +}, +N6={ +file="MARSHAL-N6", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N7={ +file="MARSHAL-N7", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +N8={ +file="MARSHAL-N8", +suffix="ogg", +loud=false, +subtitle="", +duration=0.37, +}, +N9={ +file="MARSHAL-N9", +suffix="ogg", +loud=false, +subtitle="", +duration=0.40, +}, +NEGATIVE={ +file="MARSHAL-Negative", +suffix="ogg", +loud=false, +subtitle="", +duration=0.80, +subduration=5, +}, +NEWFB={ +file="MARSHAL-NewFB", +suffix="ogg", +loud=false, +subtitle="", +duration=1.35, +}, +OPS={ +file="MARSHAL-Ops", +suffix="ogg", +loud=false, +subtitle="", +duration=0.48, +}, +POINT={ +file="MARSHAL-Point", +suffix="ogg", +loud=false, +subtitle="", +duration=0.33, +}, +RADIOCHECK={ +file="MARSHAL-RadioCheck", +suffix="ogg", +loud=false, +subtitle="Radio check", +duration=1.20, +subduration=5, +}, +RECOVERY={ +file="MARSHAL-Recovery", +suffix="ogg", +loud=false, +subtitle="", +duration=0.70, +subduration=5, +}, +RECOVERYOPSSTOPPED={ +file="MARSHAL-RecoveryOpsStopped", +suffix="ogg", +loud=false, +subtitle="", +duration=1.65, +subduration=5, +}, +RECOVERYPAUSEDNOTICE={ +file="MARSHAL-RecoveryPausedNotice", +suffix="ogg", +loud=false, +subtitle="aircraft recovery paused until further notice", +duration=2.90, +subduration=5, +}, +RECOVERYPAUSEDRESUMED={ +file="MARSHAL-RecoveryPausedResumed", +suffix="ogg", +loud=false, +subtitle="", +duration=3.40, +subduration=5, +}, +REPORTSEEME={ +file="MARSHAL-ReportSeeMe", +suffix="ogg", +loud=false, +subtitle="", +duration=0.95, +}, +RESUMERECOVERY={ +file="MARSHAL-ResumeRecovery", +suffix="ogg", +loud=false, +subtitle="resuming aircraft recovery", +duration=1.75, +subduraction=5, +}, +ROGER={ +file="MARSHAL-Roger", +suffix="ogg", +loud=false, +subtitle="", +duration=0.53, +subduration=5, +}, +SAYNEEDLES={ +file="MARSHAL-SayNeedles", +suffix="ogg", +loud=false, +subtitle="Say needles", +duration=0.90, +subduration=5, +}, +STACKFULL={ +file="MARSHAL-StackFull", +suffix="ogg", +loud=false, +subtitle="Marshal Stack is currently full. Hold outside 10 NM zone and wait for further instructions", +duration=6.35, +subduration=10, +}, +STARTINGRECOVERY={ +file="MARSHAL-StartingRecovery", +suffix="ogg", +loud=false, +subtitle="", +duration=2.65, +subduration=5, +}, +CLICK={ +file="AIRBOSS-RadioClick", +suffix="ogg", +loud=false, +subtitle="", +duration=0.35, +}, +NOISE={ +file="AIRBOSS-Noise", +suffix="ogg", +loud=false, +subtitle="", +duration=3.6, +}, +} +self:SetVoiceOversLSOByRaynor() +self:SetVoiceOversMarshalByRaynor() +end +function AIRBOSS:SetVoiceOver(radiocall,duration,subtitle,subduration,filename,suffix) +radiocall.duration=duration +radiocall.subtitle=subtitle or radiocall.subtitle +radiocall.file=filename +radiocall.suffix=suffix or".ogg" +end +function AIRBOSS:_GetAircraftAoA(playerData) +local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET +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=14.0 +aoa.Slow=13.0 +aoa.OnSpeedMax=12.0 +aoa.OnSpeed=11.0 +aoa.OnSpeedMin=10.0 +aoa.Fast=9.0 +aoa.FAST=8.0 +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 +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 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 harrier then +dist=UTILS.NMToMeters(0.9) +else +dist=UTILS.NMToMeters(1.2) +end +if goshawk 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(300) +end +aoa=aoaac.OnSpeed +end +return alt,aoa,dist,speed +end +function AIRBOSS:_GetNextMarshalFight() +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +local stack=flight.flag +local Tmarshal=timer.getAbsTime()-flight.time +local TmarshalMin=2*60 +if flight.ai then +TmarshalMin=3*60 +end +if flight.holding~=nil and Tmarshal>=TmarshalMin then +if flight.case==1 and stack==1 or flight.case>1 then +if flight.ai then +return flight +else +if flight.step~=AIRBOSS.PatternStep.COMMENCING then +return flight +end +end +end +end +end +return nil +end +function AIRBOSS:_CheckQueue() +if self.Debug then +self:_PrintQueue(self.flights,"All Flights") +end +self:_PrintQueue(self.Qmarshal,"Marshal") +self:_PrintQueue(self.Qpattern,"Pattern") +self:_PrintQueue(self.Qwaiting,"Waiting") +self:_PrintQueue(self.Qspinning,"Spinning") +if self.case>1 then +for _,_flight in pairs(self.Qwaiting)do +local flight=_flight +local removed=self:_RemoveFlightFromQueue(self.Qwaiting,flight) +if removed then +local stack=self:_GetFreeStack(flight.ai) +self:T(self.lid..string.format("Moving flight %s onboard %s from Waiting queue to Case %d Marshal stack %d",flight.groupname,flight.onboard,self.case,stack)) +if flight.ai then +self:_MarshalAI(flight,stack) +else +self:_MarshalPlayer(flight,stack) +end +break +end +end +end +if not self:IsRecovering()then +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if(flight.case==1 and self.case>1)or(flight.case>1 and self.case==1)then +local removed=self:_RemoveFlightFromQueue(self.Qmarshal,flight) +if removed then +local stack=self:_GetFreeStack(flight.ai) +self:T(self.lid..string.format("Moving flight %s onboard %s from Marshal Case %d ==> %d Marshal stack %d",flight.groupname,flight.onboard,flight.case,self.case,stack)) +if flight.ai then +self:_MarshalAI(flight,stack) +else +self:_MarshalPlayer(flight,stack) +end +break +elseif flight.case~=self.case then +flight.case=self.case +end +end +end +return +end +local _,npattern=self:_GetQueueInfo(self.Qpattern) +local _,nspinning=self:_GetQueueInfo(self.Qspinning) +local marshalflight=self:_GetNextMarshalFight() +if marshalflight and npattern0 then +local patternflight=self.Qpattern[#self.Qpattern] +pcase=patternflight.case +local npunits=self:_GetFlightUnits(patternflight,false) +Tpattern=timer.getAbsTime()-patternflight.time +self:T(self.lid..string.format("Pattern time of last group %s = %d seconds. # of units=%d.",patternflight.groupname,Tpattern,npunits)) +end +local TpatternMin +if pcase==1 then +TpatternMin=2*60*npunits +else +TpatternMin=2*60*npunits +end +if Tpattern>TpatternMin then +self:T(self.lid..string.format("Sending marshal flight %s to pattern.",marshalflight.groupname)) +self:_ClearForLanding(marshalflight) +end +end +end +function AIRBOSS:_ClearForLanding(flight) +if flight.ai then +self:_RemoveFlightFromMarshalQueue(flight,false) +self:_LandAI(flight) +self:_MarshalCallClearedForRecovery(flight.onboard,flight.case) +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 +if knownflight.ai and knownflight.flag==-100 and self.handleai then +local putintomarshal=false +local flight=_DATABASE:GetFlightGroup(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 +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):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.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 then +Speed=UTILS.KnotsToKmph(200) +elseif flight.actype==AIRBOSS.AircraftCarrier.E2D then +Speed=UTILS.KnotsToKmph(150) +elseif flight.actype==AIRBOSS.AircraftCarrier.F14A_AI or flight.actype==AIRBOSS.AircraftCarrier.F14A or flight.actype==AIRBOSS.AircraftCarrier.F14B then +Speed=UTILS.KnotsToKmph(175) +elseif flight.actype==AIRBOSS.AircraftCarrier.S3B or flight.actype==AIRBOSS.AircraftCarrier.S3BTANKER then +Speed=UTILS.KnotsToKmph(140) +end +local Carrier=self:GetCoordinate() +local hdg=self:GetHeading() +local wp={} +local CurrentSpeed=flight.group:GetVelocityKMH() +wp[#wp+1]=flight.group:GetCoordinate():WaypointAirTurningPoint(nil,CurrentSpeed,{},"Current position") +local alt=UTILS.FeetToMeters(800) +wp[#wp+1]=Carrier:Translate(UTILS.NMToMeters(4),hdg-160):SetAltitude(alt):WaypointAirLanding(Speed,self.airbase,nil,"Landing") +flight.group:WayPointInitialize(wp) +flight.group:Route(wp,0) +end +function AIRBOSS:_GetMarshalAltitude(stack,case) +if stack<=0 then +return 0,nil,nil +end +case=case or self.case +local Carrier=self:GetCoordinate() +local angels0 +local Dist +local p1=nil +local p2=nil +local nstack=stack-1 +if case==1 then +angels0=2 +local hdg=self.carrier:GetHeading() +p1=Carrier +p2=Carrier:Translate(UTILS.NMToMeters(1.5),hdg) +if self.carriertype==AIRBOSS.CarrierType.TARAWA 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() +end +flight.Tcharlie=self:_GetCharlieTime(flight) +local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +self:_MarshalCallArrived(flight.onboard,flight.case,brc,alt,Ccharlie,P) +if self.TACANon and(not flight.ai)and flight.difficulty==AIRBOSS.Difficulty.EASY then +local radial=self:GetRadial(flight.case,true,true,true) +if flight.case==1 then +radial=self:GetBRC() +end +local text=string.format("Select TACAN %03d°, channel %d%s (%s)",radial,self.TACANchannel,self.TACANmode,self.TACANmorse) +self:MessageToPlayer(flight,text,nil,"") +end +end +function AIRBOSS:_CollapseMarshalStack(flight,nopattern) +self:F2({flight=flight,nopattern=nopattern}) +local case=flight.case +local stack=flight.flag +if stack<=0 then +self:E(self.lid..string.format("ERROR: Flight %s is has stack value %d<0. Cannot collapse stack!",flight.groupname,stack)) +return +end +self.Tcollapse=timer.getTime() +for _,_flight in pairs(self.Qmarshal)do +local mflight=_flight +if(case==1 and mflight.case==1)then +local mstack=mflight.flag +if mstack>stack then +local newstack=self:_GetFreeStack(mflight.ai,mflight.case,true) +if newstack and newstack %d.",mflight.groupname,mflight.case,mstack,newstack)) +if mflight.ai then +self:_MarshalAI(mflight,newstack) +else +mflight.flag=newstack +local angels=self:_GetAngels(self:_GetMarshalAltitude(newstack,case)) +if mflight.difficulty~=AIRBOSS.Difficulty.HARD then +local text=string.format("descent to stack at Angels %d.",angels) +self:MessageToPlayer(mflight,text,"MARSHAL") +end +mflight.time=timer.getAbsTime() +for _,_sec in pairs(mflight.section)do +local sec=_sec +sec.flag=newstack +sec.time=timer.getAbsTime() +if sec.difficulty~=AIRBOSS.Difficulty.HARD then +local text=string.format("descent to stack at Angels %d.",angels) +self:MessageToPlayer(sec,text,"MARSHAL") +end +end +end +end +end +end +end +if nopattern then +self:T(self.lid..string.format("Flight %s is leaving stack but not going to pattern.",flight.groupname)) +else +local Tmarshal=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) +self:T(self.lid..string.format("Flight %s is leaving marshal after %s and going pattern.",flight.groupname,Tmarshal)) +self:_AddFlightToPatternQueue(flight) +end +flight.flag=-1 +flight.time=timer.getAbsTime() +end +function AIRBOSS:_GetFreeStack(ai,case,empty) +case=case or self.case +if case==1 then +return self:_GetFreeStack_Old(ai,case,empty) +end +local nmaxstacks=100 +if case==1 then +nmaxstacks=self.Nmaxmarshal +end +local stack={} +for i=1,nmaxstacks do +stack[i]=self.NmaxStack +end +local nmax=1 +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if flight.case==case then +local n=flight.flag +if n>nmax then +nmax=n +end +if n>0 then +if flight.ai or flight.case>1 then +stack[n]=0 +else +stack[n]=stack[n]-1 +end +else +self:E(string.format("ERROR: Flight %s in marshal stack has stack value <= 0. Stack value is %d.",flight.groupname,n)) +end +end +end +local nfree=nil +if stack[nmax]==0 then +if case==1 then +if nmax>=nmaxstacks then +nfree=nil +else +nfree=nmax+1 +end +else +nfree=nmax+1 +end +elseif stack[nmax]==self.NmaxStack then +self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d",nmax,stack[nmax])) +nfree=nmax +else +if ai or empty or case>1 then +nfree=nmax+1 +else +nfree=nmax +end +end +self:I(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.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:I(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 then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName)) +self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName)) +self:T(self.lid.."BIRTH: player = "..tostring(_playername)) +if _unit and _playername then +local _uid=_unit:GetID() +local _group=_unit:GetGroup() +local _callsign=_unit:GetCallsign() +local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.",_playername,_callsign,_unitName,_group:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,5):ToAllIf(self.Debug) +local rightaircraft=self:_IsCarrierAircraft(_unit) +if rightaircraft==false then +local text=string.format("Player aircraft type %s not supported by AIRBOSS class.",_unit:GetTypeName()) +MESSAGE:New(text,30):ToAllIf(self.Debug) +self:T2(self.lid..text) +return +end +if self:GetCoalition()~=_unit:GetCoalition()then +local text=string.format("Player entered aircraft of other coalition.") +MESSAGE:New(text,30):ToAllIf(self.Debug) +self:T(self.lid..text) +return +end +self:_AddF10Commands(_unitName) +self:ScheduleOnce(1,self._NewPlayer,self,_unitName) +end +end +function AIRBOSS:OnEventLand(EventData) +self:F3({eventland=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event LAND!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event LAND!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T(self.lid.."LAND: unit = "..tostring(EventData.IniUnitName)) +self:T(self.lid.."LAND: group = "..tostring(EventData.IniGroupName)) +self:T(self.lid.."LAND: player = "..tostring(_playername)) +local airbase=EventData.Place +if airbase==nil then +return +end +local airbasename=tostring(airbase:GetName()) +if airbasename==self.airbase:GetName()then +local stern=self:_GetSternCoord() +local zoneCarrier=self:_GetZoneCarrierBox() +if _unit and _playername then +local _uid=_unit:GetID() +local _group=_unit:GetGroup() +local _callsign=_unit:GetCallsign() +local text=string.format("Player %s, callsign %s unit %s (ID=%d) of group %s landed at airbase %s",_playername,_callsign,_unitName,_uid,_group:GetName(),airbasename) +self:T(self.lid..text) +MESSAGE:New(text,5,"DEBUG"):ToAllIf(self.Debug) +local playerData=self.players[_playername] +if playerData==nil then +self:E(self.lid..string.format("ERROR: playerData nil in landing event. unit=%s player=%s",tostring(_unitName),tostring(_playername))) +return +end +if _unit:IsInZone(zoneCarrier)then +if not playerData.valid then +local text=string.format("you missed at least one important step in the pattern!\nYour next step would have been %s.\nThis pass is INVALID.",playerData.step) +self:MessageToPlayer(playerData,text,"AIRBOSS",nil,30,true,5) +self:_RemoveFlightFromMarshalQueue(playerData,true) +self:_RemoveFlightFromQueue(self.Qpattern,playerData) +self:_RemoveFlightFromQueue(self.Qwaiting,playerData) +self:_RemoveFlightFromQueue(self.Qspinning,playerData) +self:_InitPlayer(playerData) +return +end +if playerData.landed then +self:E(self.lid..string.format("Player %s just landed a second time.",_playername)) +else +playerData.landed=true +playerData.attitudemonitor=false +local coord=playerData.unit:GetCoordinate() +local X,Z,rho,phi=self:_GetDistances(_unit) +local dist=coord:Get2DDistance(stern) +if self.Debug and false then +local lp=coord:MarkToAll("Landing coord.") +coord:SmokeGreen() +end +self:_SetTimeInGroove(playerData) +local text=string.format("Player %s AC type %s landed at dist=%.1f m. Tgroove=%.1f sec.",playerData.name,playerData.actype,dist,self:_GetTimeInGroove(playerData)) +text=text..string.format(" X=%.1f m, Z=%.1f m, rho=%.1f m.",X,Z,rho) +self:T(self.lid..text) +if self.carriertype==AIRBOSS.CarrierType.TARAWA 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.TARAWA then +local coord=EventData.IniUnit:GetCoordinate() +local dist=coord:Get2DDistance(self:GetCoordinate()) +local wire=self:_GetWire(coord,0) +local _type=EventData.IniUnit:GetTypeName() +local text=string.format("AI unit %s of type %s landed at dist=%.1f m. Trapped wire=%d.",_unitName,_type,dist,wire) +self:T(self.lid..text) +end +local flight=self:_RecoveredElement(EventData.IniUnit) +self:_CheckSectionRecovered(flight) +end +end +end +function AIRBOSS:OnEventEngineShutdown(EventData) +self:F3({eventengineshutdown=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event ENGINESHUTDOWN!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event ENGINESHUTDOWN!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."ENGINESHUTDOWN: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."ENGINESHUTDOWN: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."ENGINESHUTDOWN: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s shut down its engines!",_playername)) +else +self:T(self.lid..string.format("AI unit %s shut down its engines!",_unitName)) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +if flight and flight.ai then +local recovered=self:_CheckSectionRecovered(flight) +if recovered then +self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.",tostring(EventData.IniGroupName))) +self:_RemoveFlight(flight) +local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName +local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName +if self.despawnshutdown and not(istanker or isawacs)then +EventData.IniGroup:Destroy(nil,5) +end +end +end +end +end +function AIRBOSS:OnEventTakeoff(EventData) +self:F3({eventtakeoff=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event TAKEOFF!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event TAKEOFF!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."TAKEOFF: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."TAKEOFF: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."TAKEOFF: player = "..tostring(_playername)) +local airbase=EventData.Place +local airbasename="unknown" +if airbase then +airbasename=airbase:GetName() +end +if airbasename==self.airbase:GetName()then +if _unit and _playername then +self:T(self.lid..string.format("Player %s took off at %s!",_playername,airbasename)) +else +self:T2(self.lid..string.format("AI unit %s took off at %s!",_unitName,airbasename)) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +if flight then +for _,elem in pairs(flight.elements)do +local element=elem +element.ballcall=false +element.recovered=nil +end +end +end +end +end +function AIRBOSS:OnEventCrash(EventData) +self:F3({eventcrash=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event CRASH!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event CRASH!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."CRASH: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."CRASH: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."CARSH: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s crashed!",_playername)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +else +self:T2(self.lid..string.format("AI unit %s crashed!",EventData.IniUnitName)) +self:_RemoveUnitFromFlight(EventData.IniUnit) +end +end +function AIRBOSS:OnEventEjection(EventData) +self:F3({eventland=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event EJECTION!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event EJECTION!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."EJECT: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s ejected!",_playername)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +else +self:T(self.lid..string.format("AI unit %s ejected!",EventData.IniUnitName)) +self:_RemoveUnitFromFlight(EventData.IniUnit) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +self:_CheckSectionRecovered(flight) +end +end +function AIRBOSS:OnEventRemoveUnit(EventData) +self:F3({eventland=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."EJECT: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s removed!",_playername)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +else +self:T(self.lid..string.format("AI unit %s removed!",EventData.IniUnitName)) +self:_RemoveUnitFromFlight(EventData.IniUnit) +local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup,self.flights) +self:_CheckSectionRecovered(flight) +end +end +function AIRBOSS:_PlayerLeft(EventData) +self:F3({eventleave=EventData}) +if EventData==nil then +self:E(self.lid.."ERROR: EventData=nil in event PLAYERLEFTUNIT!") +self:E(EventData) +return +end +if EventData.IniUnit==nil then +self:E(self.lid.."ERROR: EventData.IniUnit=nil in event PLAYERLEFTUNIT!") +self:E(EventData) +return +end +local _unitName=EventData.IniUnitName +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +self:T3(self.lid.."PLAYERLEAVEUNIT: unit = "..tostring(EventData.IniUnitName)) +self:T3(self.lid.."PLAYERLEAVEUNIT: group = "..tostring(EventData.IniGroupName)) +self:T3(self.lid.."PLAYERLEAVEUNIT: player = "..tostring(_playername)) +if _unit and _playername then +self:T(self.lid..string.format("Player %s left unit %s!",_playername,_unitName)) +local flight=self.players[_playername] +if flight then +self:_RemoveFlight(flight,true) +end +end +end +function AIRBOSS:OnEventMissionEnd(EventData) +self:T3(self.lid.."Mission Ended") +end +function AIRBOSS:_Spinning(playerData) +local SpinIt={} +SpinIt.name="Spinning" +SpinIt.Xmin=-UTILS.NMToMeters(6) +SpinIt.Xmax=UTILS.NMToMeters(5) +SpinIt.Zmin=-UTILS.NMToMeters(6) +SpinIt.Zmax=UTILS.NMToMeters(2) +SpinIt.LimitXmin=-100 +SpinIt.LimitXmax=nil +SpinIt.LimitZmin=-UTILS.NMToMeters(1) +SpinIt.LimitZmax=nil +local X,Z,rho,phi=self:_GetDistances(playerData.unit) +if self:_CheckLimits(X,Z,SpinIt)then +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.INITIAL) +self:_RemoveFlightFromQueue(self.Qspinning,playerData) +end +end +function AIRBOSS:_Waiting(playerData) +local radius=UTILS.NMToMeters(10) +local zone=ZONE_RADIUS:New("Carrier 10 NM Zone",self.carrier:GetVec2(),radius) +local inzone=playerData.unit:IsInZone(zone) +local Twaiting=timer.getAbsTime()-playerData.time +if inzone and Twaiting>3*60 and not playerData.warning then +local text=string.format("You are supposed to wait outside the 10 NM zone.") +self:MessageToPlayer(playerData,text,"AIRBOSS") +playerData.warning=true +end +if inzone==false and playerData.warning==true then +playerData.warning=nil +end +end +function AIRBOSS:_Holding(playerData) +local unit=playerData.unit +local stack=playerData.flag +if stack<=0 then +local text=string.format("ERROR: player %s in step %s is holding but has stack=%s (<=0)",playerData.name,playerData.step,tostring(stack)) +self:E(self.lid..text) +end +local patternalt=self:_GetMarshalAltitude(stack,playerData.case) +local playeralt=unit:GetAltitude() +local zoneHolding=self:_GetZoneHolding(playerData.case,stack) +if zoneHolding==nil then +self:E(self.lid.."ERROR: zoneHolding is nil!") +self:E({playerData=playerData}) +return +end +local inholdingzone=unit:IsInZone(zoneHolding) +local altdiff=playeralt-patternalt +local altgood=UTILS.FeetToMeters(500) +if playerData.difficulty==AIRBOSS.Difficulty.HARD then +altgood=UTILS.FeetToMeters(200) +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +altgood=UTILS.FeetToMeters(350) +elseif playerData.difficulty==AIRBOSS.Difficulty.EASY then +altgood=UTILS.FeetToMeters(500) +end +local altback=altgood*0.5 +local justcollapsed=false +if self.Tcollapse then +local dT=timer.getTime()-self.Tcollapse +if dT<=90 then +justcollapsed=true +end +end +local goodalt=math.abs(altdiff)altgood then +if not playerData.warning then +text=text..string.format("You left your assigned altitude. Descent to angels %d.",angels) +playerData.warning=true +end +elseif altdiff<-altgood then +if not playerData.warning then +text=text..string.format("You left your assigned altitude. Climb to angels %d.",angels) +playerData.warning=true +end +end +end +if playerData.warning and math.abs(altdiff)<=altback then +text=text..string.format("Altitude is looking good again.") +playerData.warning=nil +end +elseif playerData.holding==false then +if inholdingzone then +text=text..string.format("You are back in the holding zone. Now stay there!") +playerData.holding=true +else +self:T3("Player still outside the holding zone. What are you doing man?!") +end +elseif playerData.holding==nil then +if inholdingzone then +playerData.holding=true +text=text..string.format("You arrived at the holding zone.") +if goodalt then +text=text..string.format(" Altitude is good.") +else +if altdiff<0 then +text=text..string.format(" But you're too low.") +else +text=text..string.format(" But you're too high.") +end +text=text..string.format("\nCurrently assigned altitude is %d ft.",UTILS.MetersToFeet(patternalt)) +playerData.warning=true +end +else +self:T3("Waiting for player to arrive in the holding zone.") +end +end +if playerData.showhints then +self:MessageToPlayer(playerData,text,"MARSHAL") +end +end +function AIRBOSS:_Commencing(playerData,zonecheck) +if zonecheck then +local zoneCommence=self:_GetZoneCommence(playerData.case,playerData.flag) +local inzone=playerData.unit:IsInZone(zoneCommence) +if not inzone then +if timer.getAbsTime()-playerData.time>180 then +self:_MarshalCallClearedForRecovery(playerData.onboard,playerData.case) +playerData.time=timer.getAbsTime() +end +return +end +end +self:_RemoveFlightFromMarshalQueue(playerData) +self:_InitPlayer(playerData) +if playerData.difficulty~=AIRBOSS.Difficulty.HARD then +local text="" +if playerData.case==1 then +text=text.."Proceed to initial." +else +text=text.."Descent to platform." +if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.showhints then +text=text.." VSI 4000 ft/min until you reach 5000 ft." +end +end +self:MessageToPlayer(playerData,text,"MARSHAL") +end +local nextstep +if playerData.case==1 then +nextstep=AIRBOSS.PatternStep.INITIAL +else +nextstep=AIRBOSS.PatternStep.PLATFORM +end +self:_SetPlayerStep(playerData,nextstep) +for i,_flight in pairs(playerData.section)do +local flight=_flight +self:_Commencing(flight,false) +end +end +function AIRBOSS:_Initial(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneInitial(playerData.case)) +local relheading=self:_GetRelativeHeading(playerData.unit,false) +local altitude=playerData.unit:GetAltitude() +if inzone and math.abs(relheading)<60 and altitude<=self.initialmaxalt then +if playerData.showhints then +local hint=string.format("Initial") +if playerData.difficulty==AIRBOSS.Difficulty.EASY and playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then +if playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B then +hint=hint.." - Hook down, SAS on, Wing Sweep 68°!" +else +hint=hint.." - Hook down!" +end +end +self:MessageToPlayer(playerData,hint,"MARSHAL") +end +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.BREAKENTRY) +return true +end +return false +end +function AIRBOSS:_CheckCorridor(playerData) +local validzone=self:_GetZoneCorridor(playerData.case) +local invalid=playerData.unit:IsNotInZone(validzone) +if invalid and(not playerData.warning)then +self:MessageToPlayer(playerData,"you left the approach corridor!","AIRBOSS") +playerData.warning=true +end +if(not invalid)and playerData.warning then +self:MessageToPlayer(playerData,"you're back in the approach corridor.","AIRBOSS") +playerData.warning=false +end +end +function AIRBOSS:_Platform(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZonePlatform(playerData.case)) +if inzone then +self:_PlayerHint(playerData) +local nextstep +if math.abs(self.holdingoffset)>0 and playerData.case>1 then +nextstep=AIRBOSS.PatternStep.ARCIN +else +if playerData.case==2 then +nextstep=AIRBOSS.PatternStep.INITIAL +elseif playerData.case==3 then +nextstep=AIRBOSS.PatternStep.DIRTYUP +end +end +self:_SetPlayerStep(playerData,nextstep) +end +end +function AIRBOSS:_ArcInTurn(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneArcIn(playerData.case)) +if inzone then +self:_PlayerHint(playerData) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.ARCOUT) +end +end +function AIRBOSS:_ArcOutTurn(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneArcOut(playerData.case)) +if inzone then +self:_PlayerHint(playerData) +local nextstep +if playerData.case==3 then +nextstep=AIRBOSS.PatternStep.DIRTYUP +else +nextstep=AIRBOSS.PatternStep.INITIAL +end +self:_SetPlayerStep(playerData,nextstep) +end +end +function AIRBOSS:_DirtyUp(playerData) +self:_CheckCorridor(playerData) +local inzone=playerData.unit:IsInZone(self:_GetZoneDirtyUp(playerData.case)) +if inzone then +self:_PlayerHint(playerData) +if playerData.actype==AIRBOSS.AircraftCarrier.HORNET or playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B 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 then +self:RadioTransmission(self.LSORadio,self.LSOCall.EXPECTSPOT75,nil,nil,nil,true) +end +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.GROOVE_XX) +end +end +function AIRBOSS:_BolterPattern(playerData) +local X,Z,rho,phi=self:_GetDistances(playerData.unit) +local Bolter={} +Bolter.name="Bolter Pattern" +Bolter.Xmin=-UTILS.NMToMeters(5) +Bolter.Xmax=UTILS.NMToMeters(3) +Bolter.Zmin=-UTILS.NMToMeters(5) +Bolter.Zmax=UTILS.NMToMeters(1) +Bolter.LimitXmin=100 +Bolter.LimitXmax=nil +Bolter.LimitZmin=nil +Bolter.LimitZmax=nil +if self:_CheckLimits(X,Z,Bolter)then +local nextstep +if playerData.case<3 then +nextstep=AIRBOSS.PatternStep.ABEAM +else +nextstep=AIRBOSS.PatternStep.BULLSEYE +end +self:_SetPlayerStep(playerData,nextstep) +end +end +function AIRBOSS:_BreakEntry(playerData) +local X,Z=self:_GetDistances(playerData.unit) +if self:_CheckAbort(X,Z,self.BreakEntry)then +self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) +return +end +if self:_CheckLimits(X,Z,self.BreakEntry)then +self:_PlayerHint(playerData) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) +end +end +function AIRBOSS:_Break(playerData,part) +local X,Z=self:_GetDistances(playerData.unit) +local breakpoint=self.BreakEarly +if part==AIRBOSS.PatternStep.LATEBREAK then +breakpoint=self.BreakLate +end +if self:_CheckAbort(X,Z,breakpoint)then +self:_AbortPattern(playerData,X,Z,breakpoint,true) +return +end +local tooclose=false +if part==AIRBOSS.PatternStep.LATEBREAK then +local close=0.8 +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +close=0.5 +end +if X<0 and Z90 and self:_CheckLimits(X,Z,self.Wake)then +self:MessageToPlayer(playerData,"you are already at the wake and have not passed the 90. Turn faster next time!","LSO") +self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,nil,nil,nil,true) +playerData.wop=true +self:_AddToDebrief(playerData,"Overshoot at wake - Pattern Waveoff!") +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) +end +end +function AIRBOSS:_Wake(playerData) +local X,Z=self:_GetDistances(playerData.unit) +if self:_CheckAbort(X,Z,self.Wake)then +self:_AbortPattern(playerData,X,Z,self.Wake,true) +return +end +if self:_CheckLimits(X,Z,self.Wake)then +self:_PlayerHint(playerData) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.FINAL) +end +end +function AIRBOSS:_GetGrooveData(playerData) +local X,Z=self:_GetDistances(playerData.unit) +local stern=self:_GetSternCoord() +local rho=stern:Get2DDistance(playerData.unit:GetCoordinate()) +local astern=X=RAR and rho<=RIC and not playerData.waveoff then +local waveoff=self:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData) +if waveoff then +self:T3(self.lid..string.format("Waveoff distance rho=%.1f m",rho)) +self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,nil,nil,nil,true) +playerData.Tlso=timer.getTime() +playerData.waveoff=true +return +end +end +groovedata.Step=playerData.step +if rho>=RAR and rho=RAR and rho<=RIM then +if gd.LUE>0.22 and lineupError<-0.22 then +env.info" Drift Right across centre ==> DR-" +gd.Drift=" DR" +self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +elseif gd.LUE<-0.22 and lineupError>0.22 then +env.info" Drift Left ==> DL-" +gd.Drift=" DL" +self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +elseif gd.LUE>0.13 and lineupError<-0.14 then +env.info" Little Drift Right across centre ==> (DR-)" +gd.Drift=" (DR)" +self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +elseif gd.LUE<-0.13 and lineupError>0.14 then +env.info" Little Drift Left across centre ==> (DL-)" +gd.Drift=" (DL)" +self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) +end +end +if math.abs(lineupError)>math.abs(gd.LUE)then +self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f",gs,d,lineupError,gd.LUE)) +gd.LUE=lineupError +end +if gd.GSE>0.4 and glideslopeError<-0.3 then +gd.FlyThrough="\\" +self:T(self.lid..string.format("Got Fly through DOWN at step %s, d=%.3f: Max GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError)) +elseif gd.GSE<-0.3 and glideslopeError>0.4 then +gd.FlyThrough="/" +self:E(self.lid..string.format("Got Fly through UP at step %s, d=%.3f: Min GSE=%.3f, lower GSE=%.3f",gs,d,gd.GSE,glideslopeError)) +end +if math.abs(glideslopeError)>math.abs(gd.GSE)then +self:T(self.lid..string.format("Got bigger GSE at step %s, d=%.3f: GSE |%.3f|>|%.3f|",gs,d,glideslopeError,gd.GSE)) +gd.GSE=glideslopeError +end +local aircraftaoa=self:_GetAircraftAoA(playerData) +local aoaopt=aircraftaoa.OnSpeed +if math.abs(AoA-aoaopt)>math.abs(gd.AoA-aoaopt)then +self:T(self.lid..string.format("Got bigger AoA error at step %s, d=%.3f: AoA %.3f>%.3f.",gs,d,AoA,gd.AoA)) +gd.AoA=AoA +end +end +local deltaT=timer.getTime()-playerData.Tlso +local _advice=true +if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then +_advice=false +end +if deltaT>=self.LSOdT and _advice then +self:_LSOadvice(playerData,glideslopeError,lineupError) +end +end +if X>self.carrierparam.totlength+self.carrierparam.sterndist then +if playerData.waveoff then +if playerData.landed then +self:_AddToDebrief(playerData,"You were waved off but landed anyway. Airboss wants to talk to you!") +else +self:_AddToDebrief(playerData,"You were waved off.") +end +elseif playerData.boltered then +self:_AddToDebrief(playerData,"You boltered.") +else +self:T("Player was not waved off but flew past the carrier without landing ==> Own wave off!") +self:_AddToDebrief(playerData,"Own waveoff.") +playerData.owo=true +end +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.DEBRIEF) +end +end +function AIRBOSS:_CheckWaveOff(glideslopeError,lineupError,AoA,playerData) +local waveoff=false +local glMax=1.8 +local glMin=-1.2 +local luAbs=3.0 +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +glMax=4.0 +glMin=-3.0 +luAbs=5.0 +return false +end +if glideslopeError>glMax then +local text=string.format("\n- Waveoff due to glideslope error %.2f > %.1f degrees!",glideslopeError,glMax) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +waveoff=true +elseif glideslopeErrorluAbs then +local text=string.format("\n- Waveoff due to line up error |%.1f| > %.1f degrees!",lineupError,luAbs) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +waveoff=true +end +if playerData.difficulty==AIRBOSS.Difficulty.HARD then +local aoaac=self:_GetAircraftAoA(playerData) +if AoAaoaac.SLOW then +local text=string.format("\n- Waveoff due to AoA %.1f > %.1f!",AoA,aoaac.SLOW) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +waveoff=true +end +end +return waveoff +end +function AIRBOSS:_CheckFoulDeck(playerData) +local check=false +if playerData.step==AIRBOSS.PatternStep.GROOVE_IM or +playerData.step==AIRBOSS.PatternStep.GROOVE_IC then +check=true +end +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +if playerData.step==AIRBOSS.PatternStep.GROOVE_AR or +playerData.step==AIRBOSS.PatternStep.GROOVE_AL then +check=true +end +end +if playerData.wofd==true or check==false then +return +end +local runway=self:_GetZoneRunwayBox() +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +runway=self:_GetZoneLandingSpot() +end +local R=250 +self:T(self.lid..string.format("Foul deck check: Scanning Carrier Runway Area. Radius=%.1f m.",R)) +local _,_,_,unitscan=self:GetCoordinate():ScanObjects(R,true,false,false) +local fouldeck=false +local foulunit=nil +for _,_unit in pairs(unitscan)do +local unit=_unit +local inzone=unit:IsInZone(runway) +local isaircraft=unit:IsAir() +local isairborn=unit:InAir() +if inzone and isaircraft and not isairborn then +local text=string.format("Unit %s on landing runway ==> Foul deck!",unit:GetName()) +self:T(self.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +if self.Debug then +runway:FlareZone(FLARECOLOR.Red,30) +end +fouldeck=true +foulunit=unit +end +end +if playerData and fouldeck then +local text=string.format("Foul deck waveoff due to aircraft %s!",foulunit:GetName()) +self:T(self.lid..string.format("%s: %s",playerData.name,text)) +self:_AddToDebrief(playerData,text) +self:RadioTransmission(self.LSORadio,self.LSOCall.FOULDECK,false,1) +self:RadioTransmission(self.LSORadio,self.LSOCall.WAVEOFF,false,1.2,nil,true) +if playerData.showhints then +local text=string.format("overfly landing area and enter bolter pattern.") +self:MessageToPlayer(playerData,text,"LSO",nil,nil,false,3) +end +playerData.wofd=true +playerData.step=AIRBOSS.PatternStep.DEBRIEF +playerData.warning=nil +playerData.valid=false +if foulunit then +local foulflight=self:_GetFlightFromGroupInQueue(foulunit:GetGroup(),self.flights) +if foulflight and not foulflight.ai then +self:MessageToPlayer(foulflight,"move your ass from my runway. NOW!","AIRBOSS") +end +end +end +return fouldeck +end +function AIRBOSS:_GetSternCoord() +local hdg=self.carrier:GetHeading() +local FB=self:GetFinalBearing() +self.sterncoord:UpdateFromCoordinate(self:GetCoordinate()) +if self.carriertype==AIRBOSS.CarrierType.TARAWA then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(8,FB-90,true,true) +elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then +self.sterncoord:Translate(self.carrierparam.sterndist,hdg,true,true):Translate(7,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:_GetWire(Lcoord,dc) +local FB=self:GetFinalBearing() +local Scoord=self:_GetSternCoord() +local Ldist=Lcoord:Get2DDistance(Scoord) +dc=dc or 65 +local d=Ldist-dc +if self.mpWireCorrection then +d=d-self.mpWireCorrection +end +local w1=self.carrierparam.wire1 +local w2=self.carrierparam.wire2 +local w3=self.carrierparam.wire3 +local w4=self.carrierparam.wire4 +local wire +if d wire=%d (dc=%.1f)",Ldist,Ldist-dc,wire,dc)) +return wire +end +function AIRBOSS:_Trapped(playerData) +if playerData.unit:InAir()==false then +local unit=playerData.unit +local coord=unit:GetCoordinate() +local v=unit:GetVelocityKMH()-self.carrier:GetVelocityKMH() +local stern=self:_GetSternCoord() +local s=stern:Get2DDistance(coord) +local dcorr=100 +if playerData.actype==AIRBOSS.AircraftCarrier.HORNET 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(-15,FB):Translate(15,FB+90) +p[3]=S:Translate(-15,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.TARAWA 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.TARAWA 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) +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) +if self.carriertype==AIRBOSS.CarrierType.TARAWA then +self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35,FB-90,true,true) +self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) +else +if self.carrierparam.wire3 then +local w3=self.carrierparam.wire3 +self.landingcoord:Translate(w3,FB,true,true) +end +self.landingcoord.y=self.landingcoord.y+2 +end +return self.landingcoord +end +function AIRBOSS:_GetLandingSpotCoordinate() +self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) +if self.carriertype==AIRBOSS.CarrierType.TARAWA then +local hdg=self:GetHeading() +self.landingspotcoord:Translate(57,hdg,true,true):SetAltitude(self.carrierparam.deckheight) +end +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 50) +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 50) +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(magnetic,coord) +local windfrom,vwind=self:GetWind(nil,nil,coord) +local intowind=windfrom-self.carrierparam.rwyangle +if vwind<0.1 then +intowind=self:GetHeading() +end +if magnetic then +intowind=intowind-self.magvar +end +if intowind<0 then +intowind=intowind+360 +end +return intowind +end +function AIRBOSS:GetBRCintoWind() +return self:GetHeadingIntoWind(true) +end +function AIRBOSS:GetFinalBearing(magnetic) +local fb=self:GetHeading(magnetic) +fb=fb+self.carrierparam.rwyangle +if fb<0 then +fb=fb+360 +end +return fb +end +function AIRBOSS:GetRadial(case,magnetic,offset,inverse) +case=case or self.case +local radial +if case==1 then +radial=self:GetFinalBearing(magnetic)-180 +elseif case==2 then +radial=self:GetHeading(magnetic)-180 +if offset then +radial=radial+self.holdingoffset +end +elseif case==3 then +radial=self:GetFinalBearing(magnetic)-180 +if offset then +radial=radial+self.holdingoffset +end +end +if radial<0 then +radial=radial+360 +end +if inverse then +radial=radial-180 +if radial<0 then +radial=radial+360 +end +end +return radial +end +function AIRBOSS:_GetDeltaHeading(hdg1,hdg2) +local V={} +V.x=math.cos(math.rad(hdg1)) +V.y=0 +V.z=math.sin(math.rad(hdg1)) +local W={} +W.x=math.cos(math.rad(hdg2)) +W.y=0 +W.z=math.sin(math.rad(hdg2)) +local alpha=UTILS.VecAngle(V,W) +return alpha +end +function AIRBOSS:_GetRelativeHeading(unit,runway) +local vC=self.carrier:GetOrientationX() +if runway then +vC=UTILS.Rotate2D(vC,-self.carrierparam.rwyangle) +end +local vP=unit:GetOrientationX() +vC.y=0;vP.y=0 +local rhdg=UTILS.VecAngle(vC,vP) +return rhdg +end +function AIRBOSS:_GetRelativeVelocity(unit) +local vC=self.carrier:GetVelocityVec3() +local vP=unit:GetVelocityVec3() +vC.y=0;vP.y=0 +local v=UTILS.VecSubstract(vP,vC) +return UTILS.VecNorm(v),v +end +function AIRBOSS:_GetDistances(unit) +local a=self.carrier:GetVec3() +local b=unit:GetVec3() +local c={x=b.x-a.x,y=0,z=b.z-a.z} +local x=self.carrier:GetOrientationX() +local dx=UTILS.VecDot(x,c) +local z=self.carrier:GetOrientationZ() +local dz=UTILS.VecDot(z,c) +local rho=math.sqrt(dx*dx+dz*dz) +local phi=math.deg(math.atan2(dz,dx)) +if phi<0 then +phi=phi+360 +end +return dx,dz,rho,phi +end +function AIRBOSS:_CheckLimits(X,Z,check) +local nextXmin=check.LimitXmin==nil or(check.LimitXmin and(check.LimitXmin<0 and X<=check.LimitXmin or check.LimitXmin>=0 and X>=check.LimitXmin)) +local nextXmax=check.LimitXmax==nil or(check.LimitXmax and(check.LimitXmax<0 and X>=check.LimitXmax or check.LimitXmax>=0 and X<=check.LimitXmax)) +local nextZmin=check.LimitZmin==nil or(check.LimitZmin and(check.LimitZmin<0 and Z<=check.LimitZmin or check.LimitZmin>=0 and Z>=check.LimitZmin)) +local nextZmax=check.LimitZmax==nil or(check.LimitZmax and(check.LimitZmax<0 and Z>=check.LimitZmax or check.LimitZmax>=0 and Z<=check.LimitZmax)) +local next=nextXmin and nextXmax and nextZmin and nextZmax +local text=string.format("step=%s: next=%s: X=%d Xmin=%s Xmax=%s | Z=%d Zmin=%s Zmax=%s", +check.name,tostring(next),X,tostring(check.LimitXmin),tostring(check.LimitXmax),Z,tostring(check.LimitZmin),tostring(check.LimitZmax)) +self:T3(self.lid..text) +return next +end +function AIRBOSS:_LSOadvice(playerData,glideslopeError,lineupError) +local advice=0 +if glideslopeError>self.gle.HIGH then +self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,true,nil,nil,true) +advice=advice+self.LSOCall.HIGH.duration +elseif glideslopeError>self.gle.High then +self:RadioTransmission(self.LSORadio,self.LSOCall.HIGH,false,nil,nil,true) +advice=advice+self.LSOCall.HIGH.duration +elseif glideslopeErrorself.lue.RIGHT then +self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,true,nil,nil,true) +advice=advice+self.LSOCall.RIGHTFORLINEUP.duration +elseif lineupError>self.lue.Right then +self:RadioTransmission(self.LSORadio,self.LSOCall.RIGHTFORLINEUP,false,nil,nil,true) +advice=advice+self.LSOCall.RIGHTFORLINEUP.duration +else +end +local AOA=playerData.unit:GetAoA() +local acaoa=self:_GetAircraftAoA(playerData) +if playerData.actype~=AIRBOSS.AircraftCarrier.AV8B then +if AOA>acaoa.SLOW then +self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,true,nil,nil,true) +advice=advice+self.LSOCall.SLOW.duration +elseif AOA>acaoa.Slow then +self:RadioTransmission(self.LSORadio,self.LSOCall.SLOW,false,nil,nil,true) +advice=advice+self.LSOCall.SLOW.duration +elseif AOA>acaoa.OnSpeedMax then +elseif AOA=16.4 and t<=16.6 then +grade="_OK_" +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 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 grade +local points +if N==0 and TgrooveUnicorn then +grade="_OK_" +points=5.0 +G="Unicorn" +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 +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 +end +return grade,points,G +end +function AIRBOSS:_Flightdata2Text(playerData,groovestep) +local function little(text) +return string.format("(%s)",text) +end +local function underline(text) +return string.format("_%s_",text) +end +local fdata=playerData.groove[groovestep] +if fdata==nil then +self:T3(self.lid.."Flight data is nil.") +return"",0 +end +local step=fdata.Step +local AOA=fdata.AoA +local GSE=fdata.GSE +local LUE=fdata.LUE +local ROL=fdata.Roll +local acaoa=self:_GetAircraftAoA(playerData) +local P=nil +if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then +if LUE>self.lue.RIGHT then +P=underline("AA") +elseif +LUE>self.lue.RightMed then +P="AA " +elseif +LUE>self.lue.Right then +P=little("AA") +end +end +local O=nil +if step==AIRBOSS.PatternStep.GROOVE_XX then +if LUEacaoa.SLOW then +S=underline("SLO") +elseif AOA>acaoa.Slow then +S="SLO" +elseif AOA>acaoa.OnSpeedMax then +S=little("SLO") +elseif AOAself.gle.HIGH then +A=underline("H") +elseif GSE>self.gle.High then +A="H" +elseif GSE>self.gle._max then +A=little("H") +elseif GSEself.lue.RIGHT then +D=underline("LUL") +elseif LUE>self.lue.Right then +D="LUL" +elseif LUE>self.lue._max then +D=little("LUL") +elseif playerData.case<3 then +if LUEpos.Xmax then +self:T(string.format("Xmax: X=%d > %d=Xmax",X,pos.Xmax)) +abort=true +elseif pos.Zmin and Zpos.Zmax then +self:T(string.format("Zmax: Z=%d > %d=Zmax",Z,pos.Zmax)) +abort=true +end +return abort +end +function AIRBOSS:_TooFarOutText(X,Z,posData) +local text="you are too " +local xtext=nil +if posData.Xmin and XposData.Xmax then +if posData.Xmax>=0 then +xtext="far ahead of " +else +xtext="close to " +end +end +local ztext=nil +if posData.Zmin and ZposData.Zmax then +if posData.Zmax>=0 then +ztext="far starboard of " +else +ztext="too close to " +end +end +if xtext and ztext then +text=text..xtext.." and "..ztext +elseif xtext then +text=text..xtext +elseif ztext then +text=text..ztext +end +text=text.."the carrier." +if xtext==nil and ztext==nil then +text="you are too far from where you should be!" +end +return text +end +function AIRBOSS:_AbortPattern(playerData,X,Z,posData,patternwo) +local text=self:_TooFarOutText(X,Z,posData) +local dtext=string.format("Abort: X=%d Xmin=%s, Xmax=%s | Z=%d Zmin=%s Zmax=%s",X,tostring(posData.Xmin),tostring(posData.Xmax),Z,tostring(posData.Zmin),tostring(posData.Zmax)) +self:T(self.lid..dtext) +self:MessageToPlayer(playerData,text,"LSO") +if patternwo then +playerData.wop=true +self:_AddToDebrief(playerData,string.format("Pattern wave off: %s",text)) +self:RadioTransmission(self.LSORadio,self.LSOCall.DEPARTANDREENTER,false,3,nil,nil,true) +playerData.step=AIRBOSS.PatternStep.DEBRIEF +playerData.warning=nil +end +end +function AIRBOSS:_PlayerHint(playerData,delay,soundoff) +if not playerData.showhints then +return +end +local alt,aoa,dist,speed=self:_GetAircraftParameters(playerData) +local hintAlt,debriefAlt,callAlt=self:_AltitudeCheck(playerData,alt) +local hintSpeed,debriefSpeed,callSpeed=self:_SpeedCheck(playerData,speed) +local hintAoA,debriefAoA,callAoA=self:_AoACheck(playerData,aoa) +local hintDist,debriefDist,callDist=self:_DistanceCheck(playerData,dist) +local hint="" +if hintAlt and hintAlt~=""then +hint=hint.."\n"..hintAlt +end +if hintSpeed and hintSpeed~=""then +hint=hint.."\n"..hintSpeed +end +if hintAoA and hintAoA~=""then +hint=hint.."\n"..hintAoA +end +if hintDist and hintDist~=""then +hint=hint.."\n"..hintDist +end +local debrief="" +if debriefAlt and debriefAlt~=""then +debrief=debrief.."\n- "..debriefAlt +end +if debriefSpeed and debriefSpeed~=""then +debrief=debrief.."\n- "..debriefSpeed +end +if debriefAoA and debriefAoA~=""then +debrief=debrief.."\n- "..debriefAoA +end +if debriefDist and debriefDist~=""then +debrief=debrief.."\n- "..debriefDist +end +if debrief~=""then +self:_AddToDebrief(playerData,debrief) +end +delay=delay or 0 +if not soundoff then +if callAlt then +self:Sound2Player(playerData,self.LSORadio,callAlt,false,delay) +delay=delay+callAlt.duration+0.5 +end +if callSpeed then +self:Sound2Player(playerData,self.LSORadio,callSpeed,false,delay) +delay=delay+callSpeed.duration+0.5 +end +if callAoA then +self:Sound2Player(playerData,self.LSORadio,callAoA,false,delay) +delay=delay+callAoA.duration+0.5 +end +if callDist then +self:Sound2Player(playerData,self.LSORadio,callDist,false,delay) +delay=delay+callDist.duration+0.5 +end +end +if playerData.step==AIRBOSS.PatternStep.ARCIN then +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +local radial=self:GetRadial(playerData.case,true,false,true) +local turn="right" +if self.holdingoffset<0 then +turn="left" +end +hint=hint..string.format("\nTurn %s and select TACAN %03d°.",turn,radial) +end +end +if playerData.step==AIRBOSS.PatternStep.DIRTYUP then +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +if playerData.actype==AIRBOSS.AircraftCarrier.AV8B then +hint=hint.."\nFAF! Checks completed. Nozzles 50°." +else +hint=hint.."\nDirty up! Hook, gear and flaps down." +end +end +end +if playerData.step==AIRBOSS.PatternStep.BULLSEYE then +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +if playerData.actype==AIRBOSS.AircraftCarrier.HORNET 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=tostring(UTILS.Round(UTILS.MpsToKnots(windondeck),1)) +mygrade.modex=playerData.onboard +mygrade.airframe=playerData.actype +mygrade.carriertype=self.carriertype +mygrade.carriername=self.alias +mygrade.theatre=self.theatre +mygrade.mitime=UTILS.SecondsToClock(timer.getAbsTime()) +mygrade.midate=UTILS.GetDCSMissionDate() +mygrade.osdate="n/a" +if os then +mygrade.osdate=os.date() +end +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 vtot=math.max(vdeck-vwind,UTILS.KnotsToMps(2)) +local dist=vtot*time +local speedknots=UTILS.MpsToKnots(vtot) +local distNM=UTILS.MetersToNM(dist) +self:I(self.lid..string.format("Carrier steaming into the wind (%.1f kts). Distance=%.1f NM, Speed=%.1f knots, Time=%d sec.",UTILS.MpsToKnots(vwind),distNM,speedknots,time)) +local hiw=self:GetHeadingIntoWind() +local hdg=self:GetHeading() +local deltaH=self:_GetDeltaHeading(hdg,hiw) +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(false,Csoo) +Ctiw=Csoo:Translate(dist,hsw) +elseif deltaH<90 then +Csoo=Cv:Translate(900,hdg):Translate(900,hiw) +local hsw=self:GetHeadingIntoWind(false,Csoo) +Ctiw=Csoo:Translate(dist,hsw) +elseif deltaH<135 then +Csoo=Cv:Translate(1100,hdg-90):Translate(1000,hiw) +local hsw=self:GetHeadingIntoWind(false,Csoo) +Ctiw=Csoo:Translate(dist,hsw) +else +Csoo=Cv:Translate(1200,hdg-90):Translate(1000,hiw) +local hsw=self:GetHeadingIntoWind(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 +hdg=self:GetHeadingIntoWind(false) +else +hdg=self:GetCoordinate():HeadingTo(self:_GetNextWaypoint()) +end +hdg=hdg-self.magvar +if hdg<0 then +hdg=360+hdg +end +self:_MarshalCallCarrierTurnTo(hdg) +end +self.turning=turning +end +function AIRBOSS:_CheckPatternUpdate() +local dTPupdate=10*60 +local Dupdate=UTILS.NMToMeters(2.5) +local Hupdate=5 +local dt=timer.getTime()-self.Tpupdate +if dt=Hupdate then +self:T(self.lid..string.format("Carrier heading changed by %d°.",deltaHeading)) +Hchange=true +end +local pos=self:GetCoordinate() +local dist=pos:Get2DDistance(self.Cposition) +local Dchange=false +if dist>=Dupdate then +self:T(self.lid..string.format("Carrier position changed by %.1f NM.",UTILS.MetersToNM(dist))) +Dchange=true +end +if Hchange or Dchange then +for _,_flight in pairs(self.Qmarshal)do +local flight=_flight +if flight.ai then +self:_MarshalAI(flight,flight.flag) +end +end +self.Corientation=vNew +self.Cposition=pos +self.Tpupdate=timer.getTime() +end +end +function AIRBOSS._PassingWaypoint(group,airboss,i,final) +local text=string.format("Group %s passing waypoint %d of %d.",group:GetName(),i,final) +if airboss.Debug and false then +local pos=group:GetCoordinate() +pos:SmokeRed() +local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d",group:GetName(),i)) +end +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:T(airboss.lid..text) +airboss.currentwp=i +airboss:PassingWaypoint(i) +if i==final and final>1 and airboss.adinfinitum then +airboss:_PatrolRoute() +end +end +function AIRBOSS._ResumeRoute(group,airboss,gotocoord) +local nextwp,Nextwp=airboss:_GetNextWaypoint() +local speedkmh=nextwp.Velocity*3.6 +if speedkmh<1 then +speedkmh=UTILS.KnotsToKmph(10) +end +local waypoints={} +local c0=group:GetCoordinate() +local wp0=c0:WaypointGround(speedkmh) +table.insert(waypoints,wp0) +if gotocoord then +local headingto=c0:HeadingTo(gotocoord) +local hdg1=airboss:GetHeading() +local hdg2=c0:HeadingTo(gotocoord) +local delta=airboss:_GetDeltaHeading(hdg1,hdg2) +if delta>90 then +local turnradius=UTILS.NMToMeters(3) +local gotocoordh=c0:Translate(turnradius,hdg1+45) +local wp=gotocoordh:WaypointGround(speedkmh) +table.insert(waypoints,wp) +gotocoordh=c0:Translate(turnradius,hdg1+90) +wp=gotocoordh:WaypointGround(speedkmh) +table.insert(waypoints,wp) +end +local wp1=gotocoord:WaypointGround(speedkmh) +table.insert(waypoints,wp1) +end +local text=string.format("Carrier is resuming route. Next waypoint %d, Speed=%.1f knots.",Nextwp,UTILS.KmphToKnots(speedkmh)) +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:I(airboss.lid..text) +for i=Nextwp,#airboss.waypoints do +local coord=airboss.waypoints[i] +local speed=coord.Velocity*3.6 +if speed<1 then +speed=UTILS.KnotsToKmph(10) +end +local wp=coord:WaypointGround(speed) +local TaskPassingWP=group:TaskFunction("AIRBOSS._PassingWaypoint",airboss,i,#airboss.waypoints) +group:SetTaskWaypoint(wp,TaskPassingWP) +table.insert(waypoints,wp) +end +airboss.turnintowind=false +airboss.detour=false +group:Route(waypoints) +end +function AIRBOSS._ReachedHoldingZone(group,airboss,flight) +local text=string.format("Flight %s reached holding zone.",group:GetName()) +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:T(airboss.lid..text) +if airboss.Debug then +group:GetCoordinate():MarkToAll(text) +end +if flight then +flight.holding=true +flight.time=timer.getAbsTime() +end +end +function AIRBOSS._TaskFunctionMarshalAI(group,airboss,flight) +local text=string.format("Flight %s is send to marshal.",group:GetName()) +MESSAGE:New(text,10):ToAllIf(airboss.Debug) +airboss:T(airboss.lid..text) +local stack=airboss:_GetFreeStack(flight.ai) +if stack then +airboss:_MarshalAI(flight,stack) +else +if not airboss:_InQueue(airboss.Qwaiting,flight.group)then +airboss:_WaitAI(flight) +end +end +if flight.refueling==true then +airboss:I(airboss.lid..string.format("Flight group %s finished refueling task.",flight.groupname)) +end +flight.refueling=false +end +function AIRBOSS:_GetACNickname(actype) +local nickname="unknown" +if actype==AIRBOSS.AircraftCarrier.A4EC then +nickname="Skyhawk" +elseif actype==AIRBOSS.AircraftCarrier.T45C then +nickname="Goshawk" +elseif actype==AIRBOSS.AircraftCarrier.AV8B then +nickname="Harrier" +elseif actype==AIRBOSS.AircraftCarrier.E2D then +nickname="Hawkeye" +elseif actype==AIRBOSS.AircraftCarrier.F14A_AI or actype==AIRBOSS.AircraftCarrier.F14A or actype==AIRBOSS.AircraftCarrier.F14B then +nickname="Tomcat" +elseif actype==AIRBOSS.AircraftCarrier.FA18C or actype==AIRBOSS.AircraftCarrier.HORNET then +nickname="Hornet" +elseif actype==AIRBOSS.AircraftCarrier.S3B or actype==AIRBOSS.AircraftCarrier.S3BTANKER then +nickname="Viking" +end +return nickname +end +function AIRBOSS:_GetOnboardNumberPlayer(group) +return self:_GetOnboardNumbers(group,true) +end +function AIRBOSS:_GetOnboardNumbers(group,playeronly) +local groupname=group:GetName() +local text=string.format("Onboard numbers of group %s:",groupname) +local units=group:GetTemplate().units +local numbers={} +for _,unit in pairs(units)do +local n=tostring(unit.onboard_num) +local name=unit.name +local skill=unit.skill or"Unknown" +text=text..string.format("\n- unit %s: onboard #=%s skill=%s",name,n,tostring(skill)) +if playeronly and skill=="Client"or skill=="Player"then +return n +end +numbers[name]=n +end +self:T2(self.lid..text) +return numbers +end +function AIRBOSS:_GetTowerFrequency() +self.TowerFreq=0 +local striketemplate=self.carrier:GetGroup():GetTemplate() +for _,unit in pairs(striketemplate.units)do +if self.carrier:GetName()==unit.name then +self.TowerFreq=unit.frequency/1000000 +return +end +end +end +function AIRBOSS:_GetGoodBadScore(playerData) +local lowscore +local badscore +if playerData.difficulty==AIRBOSS.Difficulty.EASY then +lowscore=10 +badscore=20 +elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then +lowscore=5 +badscore=10 +elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then +lowscore=2.5 +badscore=5 +end +return lowscore,badscore +end +function AIRBOSS:_IsCarrierAircraft(unit) +local aircrafttype=unit:GetTypeName() +if aircrafttype==AIRBOSS.AircraftCarrier.AV8B then +if self.carriertype==AIRBOSS.CarrierType.TARAWA then +return true +else +return false +end +end +if self.carriertype==AIRBOSS.CarrierType.TARAWA then +if aircrafttype~=AIRBOSS.AircraftCarrier.AV8B then +return false +end +end +for _,actype in pairs(AIRBOSS.AircraftCarrier)do +if actype==aircrafttype then +return true +end +end +return false +end +function AIRBOSS:_IsHumanUnit(unit) +local playerunit=self:_GetPlayerUnitAndName(unit:GetName()) +if playerunit then +return true +else +return false +end +end +function AIRBOSS:_IsHuman(group) +local units=group:GetUnits() +for _,_unit in pairs(units)do +local human=self:_IsHumanUnit(_unit) +if human then +return true +end +end +return false +end +function AIRBOSS:_GetFuelState(unit) +local fuel=unit:GetFuel() +local maxfuel=self:_GetUnitMasses(unit) +local fuelstate=fuel*maxfuel +self:T2(self.lid..string.format("Unit %s fuel state = %.1f kg = %.1f lbs",unit:GetName(),fuelstate,UTILS.kg2lbs(fuelstate))) +return UTILS.kg2lbs(fuelstate) +end +function AIRBOSS:_GetAngels(alt) +if alt then +local angels=UTILS.Round(UTILS.MetersToFeet(alt)/1000,0) +return angels +else +return 0 +end +end +function AIRBOSS:_GetUnitMasses(unit) +local Desc=unit:GetDesc() +local massfuel=Desc.fuelMassMax or 0 +local massempty=Desc.massEmpty or 0 +local massmax=Desc.massMax or 0 +local masscargo=massmax-massfuel-massempty +self:T2(self.lid..string.format("Unit %s mass fuel=%.1f kg, empty=%.1f kg, max=%.1f kg, cargo=%.1f kg",unit:GetName(),massfuel,massempty,massmax,masscargo)) +return massfuel,massempty,massmax,masscargo +end +function AIRBOSS:_GetPlayerDataUnit(unit) +if unit:IsAlive()then +local unitname=unit:GetName() +local playerunit,playername=self:_GetPlayerUnitAndName(unitname) +if playerunit and playername then +return self.players[playername] +end +end +return nil +end +function AIRBOSS:_GetPlayerDataGroup(group) +local units=group:GetUnits() +for _,unit in pairs(units)do +local playerdata=self:_GetPlayerDataUnit(unit) +if playerdata then +return playerdata +end +end +return nil +end +function AIRBOSS:_GetPlayerUnit(_unitName) +for _,_player in pairs(self.players)do +local player=_player +if player.unit and player.unit:GetName()==_unitName then +self:T(self.lid..string.format("Found player=%s unit=%s in players table.",tostring(player.name),tostring(_unitName))) +return player.unit,player.name +end +end +return nil,nil +end +function AIRBOSS:_GetPlayerUnitAndName(_unitName) +self:F2(_unitName) +if _unitName~=nil then +local u,pn=self:_GetPlayerUnit(_unitName) +if u and pn then +return u,pn +end +local DCSunit=Unit.getByName(_unitName) +if DCSunit then +local playername=DCSunit:getPlayerName() +local unit=UNIT:Find(DCSunit) +self:T2({DCSunit=DCSunit,unit=unit,playername=playername}) +if DCSunit and unit and playername then +self:T(self.lid..string.format("Found DCS unit %s with player %s.",tostring(_unitName),tostring(playername))) +return unit,playername +end +end +end +return nil,nil +end +function AIRBOSS:GetCoalition() +return self.carrier:GetCoalition() +end +function AIRBOSS:GetCoordinate() +return self.carrier:GetCoord() +end +function AIRBOSS:GetCoord() +return self.carrier:GetCoord() +end +function AIRBOSS:_GetStaticWeather() +local weather=env.mission.weather +local clouds=weather.clouds +local visibility=weather.visibility.distance +local dust=nil +if weather.enable_dust==true then +dust=weather.dust_density +end +local fog=nil +if weather.enable_fog==true then +fog=weather.fog +end +return clouds,visibility,fog,dust +end +function AIRBOSS._CheckRadioQueueT(param,time) +AIRBOSS._CheckRadioQueue(param.airboss,param.radioqueue,param.name) +return time+0.05 +end +function AIRBOSS:_CheckRadioQueue(radioqueue,name) +if#radioqueue==0 then +if name=="LSO"then +self:T(self.lid..string.format("Stopping LSO radio queue.")) +self.radiotimer:Stop(self.RQLid) +self.RQLid=nil +elseif name=="MARSHAL"then +self:T(self.lid..string.format("Stopping Marshal radio queue.")) +self.radiotimer:Stop(self.RQMid) +self.RQMid=nil +end +return +end +local _time=timer.getAbsTime() +local playing=false +local next=nil +local _remove=nil +for i,_transmission in ipairs(radioqueue)do +local transmission=_transmission +if _time>=transmission.Tplay then +if transmission.isplaying then +if _time>=transmission.Tstarted+transmission.call.duration then +transmission.isplaying=false +_remove=i +if transmission.radio.alias=="LSO"then +self.TQLSO=_time +elseif transmission.radio.alias=="MARSHAL"then +self.TQMarshal=_time +end +else +playing=true +end +else +local Tlast=nil +if transmission.interval then +if transmission.radio.alias=="LSO"then +Tlast=self.TQLSO +elseif transmission.radio.alias=="MARSHAL"then +Tlast=self.TQMarshal +end +end +if transmission.interval==nil then +if next==nil then +next=transmission +end +else +if _time-Tlast>=transmission.interval then +next=transmission +else +end +end +if next or Tlast then +break +end +end +else +end +end +if next~=nil and not playing then +self:Broadcast(next.radio,next.call,next.loud) +next.isplaying=true +next.Tstarted=_time +end +if _remove then +table.remove(radioqueue,_remove) +end +return +end +function AIRBOSS:RadioTransmission(radio,call,loud,delay,interval,click,pilotcall) +self:F2({radio=radio,call=call,loud=loud,delay=delay,interval=interval,click=click}) +if radio==nil or call==nil then +return +end +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 +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:MessageToPlayer(playerData,message,sender,receiver,duration,clear,delay) +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 +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 +if playerData.client then +MESSAGE:New(text,duration,sender,clear):ToClient(playerData.client) +end +end +end +end +function AIRBOSS:MessageToPattern(message,sender,receiver,duration,clear,delay) +local call=self:_NewRadioCall(self.LSOCall.NOISE,sender or"LSO",message,duration,receiver,sender) +self:RadioTransmission(self.LSORadio,call,false,delay,nil,true) +end +function AIRBOSS:MessageToMarshal(message,sender,receiver,duration,clear,delay) +local call=self:_NewRadioCall(self.MarshalCall.NOISE,sender or"MARSHAL",message,duration,receiver,sender) +self:RadioTransmission(self.MarshalRadio,call,false,delay,nil,true) +end +function AIRBOSS:_NewRadioCall(call,sender,subtitle,subduration,modexreceiver,modexsender) +local newcall=UTILS.DeepCopy(call) +newcall.sender=sender +newcall.subtitle=subtitle or call.subtitle +newcall.subduration=subduration or self.Tmessage +if self:_IsOnboard(modexreceiver)then +newcall.modexreceiver=modexreceiver +end +if self:_IsOnboard(modexsender)then +newcall.modexsender=modexsender +end +return newcall +end +function AIRBOSS:_GetRadioSender(radio) +local sender=nil +if self.senderac then +sender=UNIT:FindByName(self.senderac) +end +if radio.alias=="MARSHAL"then +if self.radiorelayMSH then +sender=UNIT:FindByName(self.radiorelayMSH) +end +end +if radio.alias=="LSO"then +if self.radiorelayLSO then +sender=UNIT:FindByName(self.radiorelayLSO) +end +end +if sender and sender:IsAlive()and sender:IsAir()then +return sender +end +return nil +end +function AIRBOSS:_IsOnboard(text) +if text==nil then +return false +end +if text=="99"then +return true +end +for _,_flight in pairs(self.flights)do +local flight=_flight +for _,onboard in pairs(flight.onboardnumbers)do +if text==onboard then +return true +end +end +end +return false +end +function AIRBOSS:_Number2Sound(playerData,sender,number,delay) +delay=delay or 0 +local function _split(str) +local chars={} +for i=1,#str do +local c=str:sub(i,i) +table.insert(chars,c) +end +return chars +end +local Sender +if sender=="LSO"then +Sender="LSOCall" +elseif sender=="MARSHAL"or sender=="AIRBOSS"then +Sender="MarshalCall" +else +self:E(self.lid..string.format("ERROR: Unknown radio sender %s!",tostring(sender))) +return +end +local numbers=_split(number) +local wait=0 +for i=1,#numbers do +local n=numbers[i] +local N=string.format("N%s",n) +local call=self[Sender][N] +local filename=self:_RadioFilename(call,false,Sender) +USERSOUND:New(filename):ToGroup(playerData.group,delay+wait) +wait=wait+call.duration +end +return wait +end +function AIRBOSS:_Number2Radio(radio,number,delay,interval,pilotcall) +local function _split(str) +local chars={} +for i=1,#str do +local c=str:sub(i,i) +table.insert(chars,c) +end +return chars +end +local Sender="" +if radio.alias=="LSO"then +Sender="LSOCall" +elseif radio.alias=="MARSHAL"then +Sender="MarshalCall" +else +self:E(self.lid..string.format("ERROR: Unknown radio alias %s!",tostring(radio.alias))) +end +if pilotcall then +Sender="PilotCall" +end +local numbers=_split(number) +local wait=0 +for i=1,#numbers do +local n=numbers[i] +local N=string.format("N%s",n) +local call=self[Sender][N] +if interval and i==1 then +self:RadioTransmission(radio,call,false,delay,interval) +else +self:RadioTransmission(radio,call,false,delay) +end +wait=wait+call.duration +end +return wait +end +function AIRBOSS:_LSOCallAircraftBall(modex,nickname,fuelstate) +local text=string.format("%s Ball, %.1f.",nickname,fuelstate) +self:I(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:I(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:I(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:I(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:I(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:I(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:I(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:I(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:I(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(" Marshal radial %03d°.",radial) +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:I(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 +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,"INSTRUCTOR","") +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 +local text="" +local cleared=false +if _unit:IsInZone(self.zoneCCA)then +local stack=playerData.flag +local _,npattern=self:_GetQueueInfo(self.Qpattern) +if self:_InQueue(self.Qpattern,playerData.group)then +text=string.format("negative, %s, you are already in the Pattern queue.",playerData.name) +elseif not _unit:InAir()then +text=string.format("negative, %s, you are not airborne.",playerData.name) +elseif playerData.seclead~=playerData.name then +text=string.format("negative, %s, your section leader %s has to request commence!",playerData.name,playerData.seclead) +elseif stack>1 then +text=string.format("negative, %s, it's not your turn yet! You are in stack no. %s.",playerData.name,stack) +elseif npattern>=self.Nmaxpattern then +text=string.format("negative ghostrider, pattern is full!\nThere are %d aircraft currently in the pattern.",npattern) +elseif self:IsRecovering()==false and not self.airbossnice then +if self.recoverywindow then +local clock=UTILS.SecondsToClock(self.recoverywindow.START) +text=string.format("negative, carrier is currently not recovery. Next window will open at %s.",clock) +else +text=string.format("negative, carrier is not recovering. No future windows planned.") +end +elseif not self:_InQueue(self.Qmarshal,playerData.group)and not self.airbossnice then +text="negative, you have to request Marshal before you can commence." +else +text=text.."roger." +if not self:IsRecovering()then +text=text.." Carrier is not recovering currently! However, you are cleared anyway as I have a nice day." +end +if not self:_InQueue(self.Qmarshal,playerData.group)then +playerData.case=self.case +if self.TACANon and playerData.difficulty~=AIRBOSS.Difficulty.HARD then +local radial=self:GetRadial(playerData.case,true,true,true) +if playerData.case==1 then +radial=self:GetBRC() +end +text=text..string.format("\nSelect TACAN %03d°, Channel %d%s (%s).\n",radial,self.TACANchannel,self.TACANmode,self.TACANmorse) +end +for _,flight in pairs(playerData.section)do +flight.case=playerData.case +end +self:_AddFlightToPatternQueue(playerData) +end +cleared=true +end +else +text=string.format("negative, %s, you are not inside the CCA!",playerData.name) +end +self:T(self.lid..text) +self:MessageToPlayer(playerData,text,"MARSHAL") +if cleared then +self:_Commencing(playerData,false) +end +end +end +end +function AIRBOSS:_RequestRefueling(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text +if self.tanker then +if _unit:IsInZone(self.zoneCCA)then +if self.tanker:IsRunning()or self.tanker:IsRefueling()then +local angels=self:_GetAngels(self.tanker.altitude) +text=string.format("affirmative, proceed to tanker at angels %d.",angels) +if self.tanker.TACANon then +text=text..string.format("\nTanker TACAN channel %d%s (%s).",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse) +text=text..string.format("\nRadio frequency %.3f MHz AM.",self.tanker.RadioFreq) +end +if self.tanker:IsRefueling()then +text=text.."\nTanker is currently refueling. You might have to queue up." +end +self:_RemoveFlightFromMarshalQueue(playerData,true) +self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.REFUELING) +for _,sec in pairs(playerData.section)do +local sectext="follow your section leader to the tanker." +self:MessageToPlayer(sec,sectext,"MARSHAL") +self:_SetPlayerStep(sec,AIRBOSS.PatternStep.REFUELING) +end +elseif self.tanker:IsReturning()then +text="negative, tanker is RTB. Request denied!\nWait for the tanker to be back on station if you can." +end +else +text="negative, you are not inside the CCA yet." +end +else +text="negative, no refueling tanker available." +end +self:MessageToPlayer(playerData,text,"MARSHAL") +end +end +end +function AIRBOSS:_RemoveSectionMember(playerData,sectionmember) +for i,_flight in pairs(playerData.section)do +local flight=_flight +if flight.name==sectionmember.name then +table.remove(playerData.section,i) +return true +end +end +return false +end +function AIRBOSS:_SetSection(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local mycoord=_unit:GetCoordinate() +local dmax=100 +local text +if self.NmaxSection==0 then +text=string.format("negative, setting sections is disabled in this mission. You stay alone.") +elseif self:_InQueue(self.Qmarshal,playerData.group)then +text=string.format("negative, you are already in the Marshal queue. Setting section not possible any more!") +elseif self:_InQueue(self.Qpattern,playerData.group)then +text=string.format("negative, you are already in the Pattern queue. Setting section not possible any more!") +else +if playerData.seclead~=playerData.name then +local lead=self.players[playerData.seclead] +if lead then +local removed=self:_RemoveSectionMember(lead,playerData) +if removed then +self:MessageToPlayer(lead,string.format("Flight %s has been removed from your section.",playerData.name),"AIRBOSS","",5) +self:MessageToPlayer(playerData,string.format("You have been removed from %s's section.",lead.name),"AIRBOSS","",5) +end +end +end +local section={} +for _,_flight in pairs(self.flights)do +local flight=_flight +if flight.ai==false and flight.groupname~=playerData.groupname and#flight.section==0 and flight.seclead==flight.name then +local distance=flight.group:GetCoordinate():Get3DDistance(mycoord) +if distance0 then +_playerResults[playerName]=Paverage/n +end +end +end +local text=string.format("Greenie Board (top ten):") +local i=1 +for _playerName,_points in UTILS.spairs(_playerResults,function(t,a,b)return t[b]=0 then +text=text..string.format("(%.1f)",grade.points) +end +end +i=i+1 +if i>10 then +break +end +end +if i==1 then +text=text.."\nNo results yet." +end +local playerData=self.players[_playername] +if playerData.client then +MESSAGE:New(text,30,nil,true):ToClient(playerData.client) +end +end +end +function AIRBOSS:_DisplayPlayerGrades(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("Your last 10 grades, %s:",_playername) +local playerGrades=self.playerscores[_playername]or{} +local p=0 +local n=0 +local m=0 +for i=#playerGrades,1,-1 do +local grade=playerGrades[i] +if grade.points>=0 then +local points=grade.finalscore or grade.points +if m<10 then +text=text..string.format("\n[%d] %s %.1f PT - %s",i,grade.grade,points,grade.details) +if grade.wire and grade.wire<=4 then +text=text..string.format(" %d-wire",grade.wire) +end +if grade.Tgroove and grade.Tgroove<=360 then +text=text..string.format(" Tgroove=%.1f s",grade.Tgroove) +end +end +if grade.finalscore then +p=p+grade.finalscore +n=n+1 +end +m=m+1 +end +end +if n>0 then +text=text..string.format("\nAverage points = %.1f",p/n) +else +text=text..string.format("\nNo data available.") +end +if playerData.client then +MESSAGE:New(text,30,nil,true):ToClient(playerData.client) +end +end +end +end +function AIRBOSS:_DisplayDebriefing(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local text=string.format("Debriefing:") +if#playerData.lastdebrief>0 then +text=text..string.format("\n================================\n") +for _,_data in pairs(playerData.lastdebrief)do +local step=_data.step +local comment=_data.hint +text=text..string.format("* %s:",step) +text=text..string.format("%s\n",comment) +end +else +text=text.." Nothing to show yet." +end +self:MessageToPlayer(playerData,text,nil,"",30,true) +end +end +end +function AIRBOSS:_DisplayQueue(_unitname,qname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +local queue=nil +if qname=="Marshal"then +queue=self.Qmarshal +elseif qname=="Pattern"then +queue=self.Qpattern +elseif qname=="Waiting"then +queue=self.Qwaiting +end +local Nqueue,nqueue=self:_GetQueueInfo(queue,playerData.case) +local text=string.format("%s Queue:",qname) +if#queue==0 then +text=text.." empty" +else +local N=0 +if qname=="Marshal"then +for i,_flight in pairs(queue)do +local flight=_flight +local charlie=self:_GetCharlieTime(flight) +local Charlie=UTILS.SecondsToClock(charlie) +local stack=flight.flag +local angels=self:_GetAngels(self:_GetMarshalAltitude(stack,flight.case)) +local _,nunit,nsec=self:_GetFlightUnits(flight,true) +local nick=self:_GetACNickname(flight.actype) +N=N+nunit +text=text..string.format("\n[Stack %d] %s (%s*%d+%d): Case %d, Angels %d, Charlie %s",stack,flight.onboard,nick,nunit,nsec,flight.case,angels,tostring(Charlie)) +end +elseif qname=="Pattern"or qname=="Waiting"then +for i,_flight in pairs(queue)do +local flight=_flight +local _,nunit,nsec=self:_GetFlightUnits(flight,true) +local nick=self:_GetACNickname(flight.actype) +local ptime=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) +N=N+nunit +text=text..string.format("\n[%d] %s (%s*%d+%d): Case %d, T=%s",i,flight.onboard,nick,nunit,nsec,flight.case,ptime) +end +end +text=text..string.format("\nTotal AC: %d (airborne %d)",N,nqueue) +end +self:MessageToPlayer(playerData,text,nil,"",nil,true) +end +end +end +function AIRBOSS:_DisplayCarrierInfo(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +local coord=self:GetCoordinate() +local carrierheading=self.carrier:GetHeading() +local carrierspeed=UTILS.MpsToKnots(self.carrier:GetVelocityMPS()) +local tacan="unknown" +local icls="unknown" +if self.TACANon and self.TACANchannel~=nil then +tacan=string.format("%d%s (%s)",self.TACANchannel,self.TACANmode,self.TACANmorse) +end +if self.ICLSon and self.ICLSchannel~=nil then +icls=string.format("%d (%s)",self.ICLSchannel,self.ICLSmorse) +end +local wind=UTILS.MpsToKnots(select(1,self:GetWindOnDeck())) +local Nmarshal,nmarshal=self:_GetQueueInfo(self.Qmarshal,playerData.case) +local Npattern,npattern=self:_GetQueueInfo(self.Qpattern) +local Nspinning,nspinning=self:_GetQueueInfo(self.Qspinning) +local Nwaiting,nwaiting=self:_GetQueueInfo(self.Qwaiting) +local Ntotal,ntotal=self:_GetQueueInfo(self.flights) +local Tabs=timer.getAbsTime() +local recoverytext="Recovery time windows (max 5):" +if#self.recoverytimes==0 then +recoverytext=recoverytext.." none." +else +local rw=0 +for _,_recovery in pairs(self.recoverytimes)do +local recovery=_recovery +if Tabs=5 then +break +end +end +end +end +local tankertext=nil +if self.tanker then +tankertext=string.format("Recovery tanker frequency %.3f MHz\n",self.tanker.RadioFreq) +if self.tanker.TACANon then +tankertext=tankertext..string.format("Recovery tanker TACAN %d%s (%s)",self.tanker.TACANchannel,self.tanker.TACANmode,self.tanker.TACANmorse) +else +tankertext=tankertext.."Recovery tanker TACAN n/a" +end +end +local state=self:GetState() +if state=="Idle"then +state="Deck closed" +end +if self.turning then +state=state.." (turning currently)" +end +local text=string.format("%s info:\n",self.alias) +text=text..string.format("================================\n") +text=text..string.format("Carrier state: %s\n",state) +if self.case==1 then +text=text..string.format("Case %d recovery ops\n",self.case) +else +local radial=self:GetRadial(self.case,true,true,false) +text=text..string.format("Case %d recovery ops\nMarshal radial %03d°\n",self.case,radial) +end +text=text..string.format("BRC %03d° - FB %03d°\n",self:GetBRC(),self:GetFinalBearing(true)) +text=text..string.format("Speed %.1f kts - Wind on deck %.1f kts\n",carrierspeed,wind) +text=text..string.format("Tower frequency %.3f MHz\n",self.TowerFreq) +text=text..string.format("Marshal radio %.3f MHz\n",self.MarshalFreq) +text=text..string.format("LSO radio %.3f MHz\n",self.LSOFreq) +text=text..string.format("TACAN Channel %s\n",tacan) +text=text..string.format("ICLS Channel %s\n",icls) +if tankertext then +text=text..tankertext.."\n" +end +text=text..string.format("# A/C total %d (%d)\n",Ntotal,ntotal) +text=text..string.format("# A/C marshal %d (%d)\n",Nmarshal,nmarshal) +text=text..string.format("# A/C pattern %d (%d) - spinning %d (%d)\n",Npattern,npattern,Nspinning,nspinning) +text=text..string.format("# A/C waiting %d (%d)\n",Nwaiting,nwaiting) +text=text..string.format(recoverytext) +self:T2(self.lid..text) +self:MessageToPlayer(playerData,text,nil,"",30,true) +else +self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername)) +end +end +end +function AIRBOSS:_DisplayCarrierWeather(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local text="" +local coord=self:GetCoordinate() +local T=coord:GetTemperature() +local P=coord:GetPressure() +local Wd,Ws=self:GetWind(nil,true) +local Bn,Bd=UTILS.BeaufortScale(Ws) +local WodPA,WodPP=self:GetWindOnDeck() +local WodPA=UTILS.MpsToKnots(WodPA) +local WodPP=UTILS.MpsToKnots(WodPP) +local WD=string.format('%03d°',Wd) +local Ts=string.format("%d°C",T) +local tT=string.format("%d°C",T) +local tW=string.format("%.1f knots",UTILS.MpsToKnots(Ws)) +local tP=string.format("%.2f inHg",UTILS.hPa2inHg(P)) +text=text..string.format("Weather Report at Carrier %s:\n",self.alias) +text=text..string.format("================================\n") +text=text..string.format("Temperature %s\n",tT) +text=text..string.format("Wind from %s at %s (%s)\n",WD,tW,Bd) +text=text..string.format("Wind on deck || %.1f kts, == %.1f kts\n",WodPA,WodPP) +text=text..string.format("QFE %.1f hPa = %s",P,tP) +if self.staticweather then +local clouds,visibility,fog,dust=self:_GetStaticWeather() +text=text..string.format("\nVisibility %.1f NM",UTILS.MetersToNM(visibility)) +text=text..string.format("\nCloud base %d ft",UTILS.MetersToFeet(clouds.base)) +text=text..string.format("\nCloud thickness %d ft",UTILS.MetersToFeet(clouds.thickness)) +text=text..string.format("\nCloud density %d",clouds.density) +text=text..string.format("\nPrecipitation %d",clouds.iprecptns) +if fog then +text=text..string.format("\nFog thickness %d ft",UTILS.MetersToFeet(fog.thickness)) +text=text..string.format("\nFog visibility %d ft",UTILS.MetersToFeet(fog.visibility)) +else +text=text..string.format("\nNo fog") +end +if dust then +text=text..string.format("\nDust density %d",dust) +else +text=text..string.format("\nNo dust") +end +end +self:T2(self.lid..text) +self:MessageToPlayer(self.players[playername],text,nil,"",30,true) +else +self:E(self.lid..string.format("ERROR! Could not find player unit in CarrierWeather! Unit name = %s",_unitname)) +end +end +function AIRBOSS:_SetDifficulty(_unitname,difficulty) +self:T2({difficulty=difficulty,unitname=_unitname}) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.difficulty=difficulty +local text=string.format("roger, your skill level is now: %s.",difficulty) +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +else +self:E(self.lid..string.format("ERROR: Could not get player data for player %s.",playername)) +end +if playerData.difficulty==AIRBOSS.Difficulty.HARD then +playerData.showhints=false +else +playerData.showhints=true +end +end +end +function AIRBOSS:_SetHintsOnOff(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.showhints=not playerData.showhints +local text="" +if playerData.showhints==true then +text=string.format("roger, hints are now ON.") +else +text=string.format("affirm, hints are now OFF.") +end +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +end +end +end +function AIRBOSS:_DisplayAttitude(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.attitudemonitor=not playerData.attitudemonitor +end +end +end +function AIRBOSS:_SubtitlesOnOff(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +playerData.subtitles=not playerData.subtitles +local text="" +if playerData.subtitles==true then +text=string.format("roger, subtitiles are now ON.") +elseif playerData.subtitles==false then +text=string.format("affirm, subtitiles are now OFF.") +end +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +end +end +end +function AIRBOSS:_TrapsheetOnOff(_unitname) +self:F2(_unitname) +local unit,playername=self:_GetPlayerUnitAndName(_unitname) +if unit and playername then +local playerData=self.players[playername] +if playerData then +local text="" +if self.trapsheet then +playerData.trapon=not playerData.trapon +if playerData.trapon==true then +text=string.format("roger, your trapsheets are now SAVED.") +else +text=string.format("affirm, your trapsheets are NOT SAVED.") +end +else +text="negative, trap sheet data recorder is broken on this carrier." +end +self:MessageToPlayer(playerData,text,nil,playerData.name,5) +end +end +end +function AIRBOSS:_DisplayPlayerStatus(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local steptext=playerData.step +if playerData.step==AIRBOSS.PatternStep.HOLDING then +if playerData.holding==nil then +steptext="Transit to Marshal" +elseif playerData.holding==false then +steptext="Marshal (outside zone)" +elseif playerData.holding==true then +steptext="Marshal Stack Holding" +end +end +local stack=playerData.flag +local stacktext=nil +if stack>0 then +local stackalt=self:_GetMarshalAltitude(stack) +local angels=self:_GetAngels(stackalt) +stacktext=string.format("Marshal Stack %d, Angels %d\n",stack,angels) +if playerData.step==AIRBOSS.PatternStep.HOLDING and playerData.case>1 then +local radial=self:GetRadial(playerData.case,true,true,true) +stacktext=stacktext..string.format("Select TACAN %03d°, %d DME\n",radial,angels+15) +end +end +local fuel=playerData.unit:GetFuel()*100 +local fuelstate=self:_GetFuelState(playerData.unit) +local _,nunitsGround=self:_GetFlightUnits(playerData,true) +local _,nunitsAirborne=self:_GetFlightUnits(playerData,false) +local text=string.format("Status of player %s (%s)\n",playerData.name,playerData.callsign) +text=text..string.format("================================\n") +text=text..string.format("Step: %s\n",steptext) +if stacktext then +text=text..stacktext +end +text=text..string.format("Recovery Case: %d\n",playerData.case) +text=text..string.format("Skill Level: %s\n",playerData.difficulty) +text=text..string.format("Modex: %s (%s)\n",playerData.onboard,self:_GetACNickname(playerData.actype)) +text=text..string.format("Fuel State: %.1f lbs/1000 (%.1f %%)\n",fuelstate/1000,fuel) +text=text..string.format("# units: %d (%d airborne)\n",nunitsGround,nunitsAirborne) +text=text..string.format("Section Lead: %s (%d/%d)",tostring(playerData.seclead),#playerData.section+1,self.NmaxSection+1) +for _,_sec in pairs(playerData.section)do +local sec=_sec +text=text..string.format("\n- %s",sec.name) +end +if playerData.step==AIRBOSS.PatternStep.INITIAL then +local zoneinitial=self:GetCoordinate():Translate(UTILS.NMToMeters(3.5),self:GetRadial(2,false,false,false)) +local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneinitial) +local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneinitial)) +local brc=self:GetBRC() +text=text..string.format("\nTo Initial: Fly heading %03d° for %.1f NM and turn to BRC %03d°",flyhdg,flydist,brc) +elseif playerData.step==AIRBOSS.PatternStep.PLATFORM then +local zoneplatform=self:_GetZonePlatform(playerData.case):GetCoordinate() +local flyhdg=playerData.unit:GetCoordinate():HeadingTo(zoneplatform) +local flydist=UTILS.MetersToNM(playerData.unit:GetCoordinate():Get2DDistance(zoneplatform)) +local hdg=self:GetRadial(playerData.case,true,true,true) +text=text..string.format("\nTo Platform: Fly heading %03d° for %.1f NM and turn to %03d°",flyhdg,flydist,hdg) +end +self:MessageToPlayer(playerData,text,nil,"",30,true) +else +self:E(self.lid..string.format("ERROR: playerData=nil. Unit name=%s, player name=%s",_unitName,_playername)) +end +else +self:E(self.lid..string.format("ERROR: could not find player for unit %s",_unitName)) +end +end +function AIRBOSS:_MarkMarshalZone(_unitName,flare) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local stack=playerData.flag +local case=playerData.case +local text="" +if stack>0 then +local zoneHolding=self:_GetZoneHolding(case,stack) +local zoneThree=self:_GetZoneCommence(case,stack) +local patternalt=self:_GetMarshalAltitude(stack,case) +patternalt=5 +text="roger, marking" +if flare then +text=text..string.format("\n* Marshal zone stack %d with WHITE flares.",stack) +zoneHolding:FlareZone(FLARECOLOR.White,45,nil,patternalt) +text=text.."\n* Commence zone with RED flares." +zoneThree:FlareZone(FLARECOLOR.Red,45,nil,patternalt) +else +text=text..string.format("\n* Marshal zone stack %d with WHITE smoke.",stack) +zoneHolding:SmokeZone(SMOKECOLOR.White,45,patternalt) +text=text.."\n* Commence zone with RED smoke." +zoneThree:SmokeZone(SMOKECOLOR.Red,45,patternalt) +end +else +text="negative, you are currently not in a Marshal stack. No zones will be marked!" +end +self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name) +end +end +end +function AIRBOSS:_MarkCaseZones(_unitName,flare) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +local case=playerData.case +local text=string.format("affirm, marking CASE %d zones",case) +if flare then +if case==1 or case==2 then +text=text.."\n* initial with GREEN flares" +self:_GetZoneInitial(case):FlareZone(FLARECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* approach corridor with GREEN flares" +self:_GetZoneCorridor(case):FlareZone(FLARECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* platform with RED flares" +self:_GetZonePlatform(case):FlareZone(FLARECOLOR.Red,45) +end +if case==3 then +text=text.."\n* dirty up with YELLOW flares" +self:_GetZoneDirtyUp(case):FlareZone(FLARECOLOR.Yellow,45) +end +if case==2 or case==3 then +if math.abs(self.holdingoffset)>0 then +self:_GetZoneArcIn(case):FlareZone(FLARECOLOR.White,45) +text=text.."\n* arc turn in with WHITE flares" +self:_GetZoneArcOut(case):FlareZone(FLARECOLOR.White,45) +text=text.."\n* arc trun out with WHITE flares" +end +end +if case==3 then +text=text.."\n* bullseye with GREEN flares" +self:_GetZoneBullseye(case):FlareZone(FLARECOLOR.Green,45) +end +if self.carriertype==AIRBOSS.CarrierType.TARAWA then +text=text.."\n* abeam landing stop with RED flares" +local ALSPT=self:_GetZoneAbeamLandingSpot() +ALSPT:FlareZone(FLARECOLOR.Red,5,nil,UTILS.FeetToMeters(110)) +text=text.."\n* primary landing spot with GREEN flares" +local LSPT=self:_GetZoneLandingSpot() +LSPT:FlareZone(FLARECOLOR.Green,5,nil,self.carrierparam.deckheight) +end +else +if case==1 or case==2 then +text=text.."\n* initial with GREEN smoke" +self:_GetZoneInitial(case):SmokeZone(SMOKECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* approach corridor with GREEN smoke" +self:_GetZoneCorridor(case):SmokeZone(SMOKECOLOR.Green,45) +end +if case==2 or case==3 then +text=text.."\n* platform with RED smoke" +self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Red,45) +end +if case==2 or case==3 then +if math.abs(self.holdingoffset)>0 then +self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue,45) +text=text.."\n* arc turn in with BLUE smoke" +self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue,45) +text=text.."\n* arc trun out with BLUE smoke" +end +end +if case==3 then +text=text.."\n* dirty up with ORANGE smoke" +self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange,45) +end +if case==3 then +text=text.."\n* bullseye with GREEN smoke" +self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.Green,45) +end +end +self:MessageToPlayer(playerData,text,"MARSHAL",playerData.name) +end +end +end +function AIRBOSS:_LSORadioCheck(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +self:RadioTransmission(self.LSORadio,self.LSOCall.RADIOCHECK,nil,nil,nil,true) +end +end +end +function AIRBOSS:_MarshalRadioCheck(_unitName) +self:F(_unitName) +local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) +if _unit and _playername then +local playerData=self.players[_playername] +if playerData then +self:RadioTransmission(self.MarshalRadio,self.MarshalCall.RADIOCHECK,nil,nil,nil,true) +end +end +end +function AIRBOSS:_SaveTrapSheet(playerData,grade) +if playerData.trapsheet==nil or#playerData.trapsheet==0 or not io then +return +end +local function _savefile(filename,data) +local f=io.open(filename,"wb") +if f then +f:write(data) +f:close() +else +self:E(self.lid..string.format("ERROR: could not save trap sheet to file %s.\nFile may contain invalid characters.",tostring(filename))) +end +end +local path=self.trappath +if lfs then +path=path or lfs.writedir() +end +local filename=nil +for i=1,9999 do +if self.trapprefix then +filename=string.format("%s_%s-%04d.csv",self.trapprefix,playerData.actype,i) +else +local name=UTILS.ReplaceIllegalCharacters(playerData.name,"_") +filename=string.format("AIRBOSS-%s_Trapsheet-%s_%s-%04d.csv",self.alias,name,playerData.actype,i) +end +if path~=nil then +filename=path.."\\"..filename +end +local _exists=UTILS.FileExists(filename) +if not _exists then +break +end +end +local text=string.format("Saving player %s trapsheet to file %s",playerData.name,filename) +self:I(self.lid..text) +local data="#Time,Rho,X,Z,Alt,AoA,GSE,LUE,Vtot,Vy,Gamma,Pitch,Roll,Yaw,Step,Grade,Points,Details\n" +local g0=playerData.trapsheet[1] +local T0=g0.Time +for i=1,#playerData.trapsheet do +local groove=playerData.trapsheet[i] +local t=groove.Time-T0 +local a=UTILS.MetersToNM(groove.Rho or 0) +local b=-groove.X or 0 +local c=groove.Z or 0 +local d=UTILS.MetersToFeet(groove.Alt or 0) +local e=groove.AoA or 0 +local f=groove.GSE or 0 +local g=-groove.LUE or 0 +local h=UTILS.MpsToKnots(groove.Vel or 0) +local i=(groove.Vy or 0)*196.85 +local j=groove.Gamma or 0 +local k=groove.Pitch or 0 +local l=groove.Roll or 0 +local m=groove.Yaw or 0 +local n=self:_GS(groove.Step,-1)or"n/a" +local o=groove.Grade or"n/a" +local p=groove.GradePoints or 0 +local q=groove.GradeDetail or"n/a" +data=data..string.format("%.2f,%.3f,%.1f,%.1f,%.1f,%.2f,%.2f,%.2f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%s,%s,%.1f,%s\n",t,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q) +end +_savefile(filename,data) +end +function AIRBOSS:onbeforeSave(From,Event,To,path,filename) +if not io then +self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +return true +end +function AIRBOSS:onafterSave(From,Event,To,path,filename) +local function _savefile(filename,data) +local f=assert(io.open(filename,"wb")) +f:write(data) +f:close() +end +if lfs then +path=path or lfs.writedir() +end +filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n" +local n=0 +for playername,grades in pairs(self.playerscores)do +for i,_grade in pairs(grades)do +local grade=_grade +local wire="n/a" +if grade.wire and grade.wire<=4 then +wire=tostring(grade.wire) +end +local Tgroove="n/a" +if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then +Tgroove=tostring(UTILS.Round(grade.Tgroove,1)) +end +local finalscore="n/a" +if grade.finalscore then +finalscore=tostring(UTILS.Round(grade.finalscore,1)) +end +scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", +playername,i,finalscore,grade.points,grade.grade,grade.details,wire,Tgroove,grade.case, +grade.wind,grade.modex,grade.airframe,grade.carriertype,grade.carriername,grade.theatre,grade.mitime,grade.midate,grade.osdate) +n=n+1 +end +end +local text=string.format("Saving %d player LSO grades to file %s",n,filename) +self:I(self.lid..text) +_savefile(filename,scores) +end +function AIRBOSS:onbeforeLoad(From,Event,To,path,filename) +local function _fileexists(name) +local f=io.open(name,"r") +if f~=nil then +io.close(f) +return true +else +return false +end +end +if not io then +self:E(self.lid.."WARNING: io not desanitized. Can't load player grades.") +return false +end +if path==nil and not lfs then +self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") +end +if lfs then +path=path or lfs.writedir() +end +filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local exists=_fileexists(filename) +if exists then +return true +else +self:E(self.lid..string.format("WARNING: Player LSO grades file %s does not exist.",filename)) +return false +end +end +function AIRBOSS:onafterLoad(From,Event,To,path,filename) +local function _loadfile(filename) +local f=assert(io.open(filename,"rb")) +local data=f:read("*all") +f:close() +return data +end +if lfs then +path=path or lfs.writedir() +end +filename=filename or string.format("AIRBOSS-%s_LSOgrades.csv",self.alias) +if path~=nil then +filename=path.."\\"..filename +end +local text=string.format("Loading player LSO grades from file %s",filename) +MESSAGE:New(text,10):ToAllIf(self.Debug) +self:I(self.lid..text) +local data=_loadfile(filename) +local playergrades=UTILS.Split(data,"\n") +table.remove(playergrades,1) +self.playerscores={} +local n=0 +for _,gradeline in pairs(playergrades)do +local gradedata=UTILS.Split(gradeline,",") +self:T2(gradedata) +local grade={} +local playername=gradedata[1] +if gradedata[3]~=nil and gradedata[3]~="n/a"then +grade.finalscore=tonumber(gradedata[3]) +end +grade.points=tonumber(gradedata[4]) +grade.grade=tostring(gradedata[5]) +grade.details=tostring(gradedata[6]) +if gradedata[7]~=nil and gradedata[7]~="n/a"then +grade.wire=tonumber(gradedata[7]) +end +if gradedata[8]~=nil and gradedata[8]~="n/a"then +grade.Tgroove=tonumber(gradedata[8]) +end +grade.case=tonumber(gradedata[9]) +grade.wind=gradedata[10]or"n/a" +grade.modex=gradedata[11]or"n/a" +grade.airframe=gradedata[12]or"n/a" +grade.carriertype=gradedata[13]or"n/a" +grade.carriername=gradedata[14]or"n/a" +grade.theatre=gradedata[15]or"n/a" +grade.mitime=gradedata[16]or"n/a" +grade.midate=gradedata[17]or"n/a" +grade.osdate=gradedata[18]or"n/a" +self.playerscores[playername]=self.playerscores[playername]or{} +table.insert(self.playerscores[playername],grade) +n=n+1 +self:T2({playername,self.playerscores[playername]}) +end +local text=string.format("Loaded %d player LSO grades from file %s",n,filename) +self:I(self.lid..text) +end +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, +} +_RECOVERYTANKERID=0 +RECOVERYTANKER.version="1.0.9" +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: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) +self.TACANchannel=channel or 1 +self.TACANmode="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) +Spawn:InitRadioCommsOnOff(true) +Spawn:InitRadioFrequency(self.RadioFreq) +Spawn:InitRadioModulation(self.RadioModu) +Spawn:InitModex(self.modex) +if self.takeoff==SPAWN.Takeoff.Air then +local hdg=self.carrier:GetHeading() +local dist=-self.distStern+UTILS.NMToMeters(4) +local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg+190):SetAltitude(self.altitude) +Spawn:InitHeading(hdg+10) +self.tanker=Spawn:SpawnFromCoordinate(Carrier) +else +if self.uncontrolledac then +self.tanker=GROUP:FindByName(self.tankergroupname) +if self.tanker:IsAlive()then +self.tanker:StartUncontrolled() +else +self:E(string.format("ERROR: No uncontrolled (alive) tanker group with name %s could be found!",self.tankergroupname)) +return +end +else +self.tanker=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,self.terminaltype) +end +end +self:ScheduleOnce(1,self._InitRoute,self,-self.distStern+UTILS.NMToMeters(3)) +if self.TACANon then +self:_ActivateTACAN(2) +end +if self.callsignname then +self.tanker:CommandSetCallsign(self.callsignname,self.callsignnumber,2) +end +if self.eplrs then +self.tanker:CommandEPLRS(true,3) +end +self.orientation=self.carrier:GetOrientationX() +self.orientlast=self.carrier:GetOrientationX() +self.position=self.carrier:GetCoordinate() +self:__Status(10) +end +function RECOVERYTANKER:onafterStatus(From,Event,To) +local time=timer.getTime() +if self.tanker and self.tanker:IsAlive()then +local fuel=self.tanker:GetFuel()*100 +local life=self.tanker:GetUnit(1):GetLife() +local life0=self.tanker:GetUnit(1):GetLife0() +local lifeR=self.tanker:GetUnit(1):GetLifeRelative() +local text=string.format("Recovery tanker %s: state=%s fuel=%.1f, life=%.1f/%.1f=%d",self.tanker:GetName(),self:GetState(),fuel,life,life0,lifeR*100) +self:T(self.lid..text) +MESSAGE:New(text,10):ToAllIf(self.Debug) +if self:IsRunning()then +if fuel100 then +return +end +local text=string.format("Recovery tanker %s started refueling unit %s",self.tanker:GetName(),receiver:GetName()) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self:RefuelStart(receiver) +end +end +function RECOVERYTANKER:_RefuelingStop(EventData) +if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive()then +local receiver=EventData.IniUnit +local dist=receiver:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate()) +if dist>100 then +return +end +local text=string.format("Recovery tanker %s stopped refueling unit %s",self.tanker:GetName(),receiver:GetName()) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self:RefuelStop(receiver) +end +end +function RECOVERYTANKER:_OnEventCrashOrDead(EventData) +self:F2({eventdata=EventData}) +if EventData and EventData.IniUnit then +local unit=EventData.IniUnit +local unitname=tostring(EventData.IniUnitName) +if EventData.IniGroupName==self.tanker:GetName()then +self:E(self.lid..string.format("Recovery tanker %s crashed!",unitname)) +self:Stop() +if self.respawn then +self:__Start(5) +end +end +end +end +function RECOVERYTANKER:_InitPatternTaskFunction() +local carriername=self.carrier:GetName() +local DCSScript={} +DCSScript[#DCSScript+1]=string.format('local mycarrier = UNIT:FindByName(\"%s\") ',carriername) +DCSScript[#DCSScript+1]=string.format('local mytanker = mycarrier:GetState(mycarrier, \"RECOVERYTANKER_%d\") ',self.uid) +DCSScript[#DCSScript+1]=string.format('mytanker:PatternUpdate()') +local DCSTask=CONTROLLABLE.TaskWrappedAction(self,CONTROLLABLE.CommandDoScript(self,table.concat(DCSScript))) +return DCSTask +end +function RECOVERYTANKER:_InitRoute(dist,delay) +dist=dist or UTILS.NMToMeters(8) +delay=delay or 1 +self:T(self.lid..string.format("Initializing route of recovery tanker %s.",self.tanker:GetName())) +local Carrier=self.carrier:GetCoordinate() +local hdg=self.carrier:GetHeading() +local p=Carrier:Translate(dist,hdg+190):SetAltitude(self.altitude) +local speed=self.tanker:GetSpeedMax()*0.8 +if self.Debug then +p:MarkToAll(string.format("Enter Pattern WP: alt=%d ft, speed=%d kts",UTILS.MetersToFeet(self.altitude),speed*0.539957)) +end +local task=self:_InitPatternTaskFunction() +local wp={} +if self.takeoff==SPAWN.Takeoff.Air then +wp[#wp+1]=self.tanker:GetCoordinate():SetAltitude(self.altitude):WaypointAirTurningPoint(nil,speed,{},"Spawn Position") +else +wp[#wp+1]=Carrier:WaypointAirTakeOffParking() +end +wp[#wp+1]=p:WaypointAirTurningPoint(nil,speed,{task},"Enter Pattern") +self.tanker:Route(wp,delay) +self:__Run(1) +self.Tupdate=nil +end +function RECOVERYTANKER:_CheckPatternUpdate(dt) +local pos=self.carrier:GetCoordinate() +local vNew=self.carrier:GetOrientationX() +local vOld=self.orientation +local vLast=self.orientlast +vNew.y=0;vOld.y=0;vLast.y=0 +local deltaHeading=math.deg(math.acos(UTILS.VecDot(vNew,vOld)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vOld))) +local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) +self.orientlast=vNew +local turning=deltaLast>=1 +if turning then +self:T2(self.lid..string.format("Carrier is turning. Delta Heading = %.1f",deltaLast)) +end +local Hchange=false +if math.abs(deltaHeading)>=self.Hupdate then +self:T(self.lid..string.format("Carrier heading changed by %d degrees. Turning=%s.",deltaHeading,tostring(turning))) +Hchange=true +end +local dist=pos:Get2DDistance(self.position) +local Dchange=false +if dist>self.Dupdate then +self:T(self.lid..string.format("Carrier position changed by %.1f NM. Turning=%s.",UTILS.MetersToNM(dist),tostring(turning))) +Dchange=true +end +local update=false +if self:IsRunning()and dt>self.dTupdate and not turning then +if Hchange or Dchange then +local text=string.format("Updating tanker %s pattern due to carrier position=%s or heading=%s change.",self.tanker:GetName(),tostring(Dchange),tostring(Hchange)) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self.orientation=vNew +self.position=pos +update=true +end +end +return update +end +function RECOVERYTANKER:_ActivateTACAN(delay) +if delay and delay>0 then +self:ScheduleOnce(delay,RECOVERYTANKER._ActivateTACAN,self) +else +local unit=self.tanker:GetUnit(1) +if unit and unit:IsAlive()then +local text=string.format("Activating TACAN beacon: channel=%d mode=%s, morse=%s.",self.TACANchannel,self.TACANmode,self.TACANmorse) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +self.beacon=BEACON:New(unit) +self.beacon:ActivateTACAN(self.TACANchannel,self.TACANmode,self.TACANmorse,true) +else +self:E(self.lid.."ERROR: Recovery tanker is not alive!") +end +end +end +function RECOVERYTANKER:_Pattern() +local hdg=self.carrier:GetHeading() +local alt=self.altitude +local Carrier=self.carrier:GetCoordinate() +local width=UTILS.NMToMeters(8) +local p={} +p[1]=self.tanker:GetCoordinate() +p[2]=Carrier:SetAltitude(alt) +p[3]=p[2]:Translate(self.distBow,hdg) +p[4]=p[3]:Translate(width/math.sqrt(2),hdg-45) +p[5]=p[3]:Translate(width,hdg-90) +p[6]=p[5]:Translate(self.distStern-self.distBow,hdg) +p[7]=p[2]:Translate(self.distStern,hdg) +local wp={} +for i=1,#p do +local coord=p[i] +coord:MarkToAll(string.format("Waypoint %d",i)) +table.insert(wp,coord:WaypointAirTurningPoint(nil,UTILS.MpsToKmph(self.speed))) +end +return wp +end +RESCUEHELO={ +ClassName="RESCUEHELO", +Debug=false, +lid=nil, +carrier=nil, +carriertype=nil, +helogroupname=nil, +helo=nil, +airbase=nil, +takeoff=nil, +followset=nil, +formation=nil, +lowfuel=nil, +altitude=nil, +offsetX=nil, +offsetZ=nil, +rescuezone=nil, +respawn=nil, +respawninair=nil, +uncontrolledac=nil, +rescueon=nil, +rescueduration=nil, +rescuespeed=nil, +rescuestopboat=nil, +HeloFuel0=nil, +rtb=nil, +carrierstop=nil, +alias=nil, +uid=0, +modex=nil, +dtFollow=nil, +} +_RESCUEHELOID=0 +RESCUEHELO.version="1.1.0" +function RESCUEHELO:New(carrierunit,helogroupname) +local self=BASE:Inherit(self,FSM:New()) +if type(carrierunit)=="string"then +self.carrier=UNIT:FindByName(carrierunit) +else +self.carrier=carrierunit +end +self.carriertype=self.carrier:GetTypeName() +self.helogroupname=helogroupname +_RESCUEHELOID=_RESCUEHELOID+1 +self.uid=_RESCUEHELOID +self.carrier:SetState(self.carrier,string.format("RESCUEHELO_%d",self.uid),self) +self.alias=string.format("%s_%s_%02d",self.carrier:GetName(),self.helogroupname,_RESCUEHELOID) +self.lid=string.format("RESCUEHELO %s | ",self.alias) +self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) +self:SetTakeoffHot() +self:SetLowFuelThreshold() +self:SetAltitude() +self:SetOffsetX() +self:SetOffsetZ() +self:SetRespawnOn() +self:SetRescueOn() +self:SetRescueZone() +self:SetRescueHoverSpeed() +self:SetRescueDuration() +self:SetFollowTimeInterval() +self:SetRescueStopBoatOff() +self.rtb=false +self.carrierstop=false +if false then +self.Debug=true +BASE:TraceOnOff(true) +BASE:TraceClass(self.ClassName) +BASE:TraceLevel(1) +end +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","Running") +self:AddTransition("Running","Rescue","Rescuing") +self:AddTransition("Running","RTB","Returning") +self:AddTransition("Rescuing","RTB","Returning") +self:AddTransition("Returning","Returned","Returned") +self:AddTransition("Running","Run","Running") +self:AddTransition("Returned","Run","Running") +self:AddTransition("*","Status","*") +self:AddTransition("*","Stop","Stopped") +return self +end +function RESCUEHELO:SetLowFuelThreshold(threshold) +self.lowfuel=threshold or 5 +return self +end +function RESCUEHELO:SetHomeBase(airbase) +if type(airbase)=="string"then +self.airbase=AIRBASE:FindByName(airbase) +else +self.airbase=airbase +end +if not self.airbase then +self:E(self.lid.."ERROR: Airbase is nil!") +end +return self +end +function RESCUEHELO:SetRescueZone(radius) +radius=UTILS.NMToMeters(radius or 15) +self.rescuezone=ZONE_UNIT:New("Rescue Zone",self.carrier,radius) +return self +end +function RESCUEHELO:SetRescueHoverSpeed(speed) +self.rescuespeed=UTILS.KnotsToMps(speed or 5) +return self +end +function RESCUEHELO:SetRescueDuration(duration) +self.rescueduration=(duration or 5)*60 +return self +end +function RESCUEHELO:SetRescueOn() +self.rescueon=true +return self +end +function RESCUEHELO:SetRescueOff() +self.rescueon=false +return self +end +function RESCUEHELO:SetRescueStopBoatOn() +self.rescuestopboat=true +return self +end +function RESCUEHELO:SetRescueStopBoatOff() +self.rescuestopboat=false +return self +end +function RESCUEHELO:SetTakeoff(takeofftype) +self.takeoff=takeofftype or SPAWN.Takeoff.Hot +return self +end +function RESCUEHELO:SetTakeoffHot() +self:SetTakeoff(SPAWN.Takeoff.Hot) +return self +end +function RESCUEHELO:SetTakeoffCold() +self:SetTakeoff(SPAWN.Takeoff.Cold) +return self +end +function RESCUEHELO:SetTakeoffAir() +self:SetTakeoff(SPAWN.Takeoff.Air) +return self +end +function RESCUEHELO:SetAltitude(alt) +self.altitude=alt or 70 +return self +end +function RESCUEHELO:SetOffsetX(distance) +self.offsetX=distance or 200 +return self +end +function RESCUEHELO:SetOffsetZ(distance) +self.offsetZ=distance or 240 +return self +end +function RESCUEHELO:SetRespawnOn() +self.respawn=true +return self +end +function RESCUEHELO:SetRespawnOff() +self.respawn=false +return self +end +function RESCUEHELO:SetRespawnOnOff(switch) +if switch==nil or switch==true then +self.respawn=true +else +self.respawn=false +end +return self +end +function RESCUEHELO:SetRespawnInAir() +self.respawninair=true +return self +end +function RESCUEHELO:SetModex(modex) +self.modex=modex +return self +end +function RESCUEHELO:SetFollowTimeInterval(dt) +self.dtFollow=dt or 1.0 +return self +end +function RESCUEHELO:SetUseUncontrolledAircraft() +self.uncontrolledac=true +return self +end +function RESCUEHELO:SetDebugModeON() +self.Debug=true +return self +end +function RESCUEHELO:SetDebugModeOFF() +self.Debug=false +return self +end +function RESCUEHELO:IsReturning() +return self:is("Returning") +end +function RESCUEHELO:IsRunning() +return self:is("Running") +end +function RESCUEHELO:IsRescuing() +return self:is("Rescuing") +end +function RESCUEHELO:IsStopped() +return self:is("Stopped") +end +function RESCUEHELO:GetAlias() +return self.alias +end +function RESCUEHELO:GetUnitName() +local unit=self.helo:GetUnit(1) +if unit then +return unit:GetName() +end +return nil +end +function RESCUEHELO:OnEventLand(EventData) +local group=EventData.IniGroup +if group and group:IsAlive()then +local groupname=group:GetName() +if groupname==self.helo:GetName()then +local airbase=nil +local airbasename="unknown" +if EventData.Place then +airbase=EventData.Place +airbasename=airbase:GetName() +end +local text=string.format("Rescue helo group %s landed at airbase %s.",groupname,airbasename) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +if self:IsRescuing()then +self:T(self.lid..string.format("Rescue helo %s returned from rescue operation.",groupname)) +end +if self.takeoff==SPAWN.Takeoff.Air or self.respawninair then +if not self:IsRescuing()then +self:E(self.lid..string.format("WARNING: Rescue helo %s landed. This should not happen for Takeoff=Air or respawninair=true and no rescue operation in progress.",groupname)) +end +end +self:__Returned(3,airbase) +end +end +end +function RESCUEHELO:_OnEventCrashOrEject(EventData) +self:F2({eventdata=EventData}) +if EventData and EventData.IniUnit then +local unit=EventData.IniUnit +local unitname=tostring(EventData.IniUnitName) +if EventData.IniGroupName~=self.helo:GetName()then +local text=string.format("Unit %s crashed or ejected.",unitname) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:I(self.lid..text) +local coord=unit:GetCoordinate() +if coord and self.rescuezone:IsCoordinateInZone(coord)then +if self.Debug then +coord:MarkToCoalition(self.lid..string.format("Crash site of unit %s.",unitname),self.helo:GetCoalition()) +end +local rightcoalition=EventData.IniGroup:GetCoalition()==self.helo:GetCoalition() +if self:IsRunning()and self.rescueon and rightcoalition then +self:Rescue(coord) +end +end +else +self:E(self.lid..string.format("Rescue helo %s crashed!",unitname)) +self:Stop() +if self.respawn then +self:__Start(5) +end +end +end +end +function RESCUEHELO:onafterStart(From,Event,To) +local text=string.format("Starting Rescue Helo Formation v%s for carrier unit %s of type %s.",RESCUEHELO.version,self.carrier:GetName(),self.carriertype) +self:I(self.lid..text) +self:HandleEvent(EVENTS.Land) +self:HandleEvent(EVENTS.Crash,self._OnEventCrashOrEject) +self:HandleEvent(EVENTS.Ejection,self._OnEventCrashOrEject) +local delay=120 +local Spawn=SPAWN:NewWithAlias(self.helogroupname,self.alias) +Spawn:InitModex(self.modex) +if self.takeoff==SPAWN.Takeoff.Air then +local hdg=self.carrier:GetHeading() +local dist=UTILS.NMToMeters(0.2) +local Carrier=self.carrier:GetCoordinate():Translate(dist,hdg):SetAltitude(math.max(100,self.altitude)) +Spawn:InitHeading(hdg) +self.helo=Spawn:SpawnFromCoordinate(Carrier) +delay=1 +else +if self.uncontrolledac then +self.helo=GROUP:FindByName(self.helogroupname) +if self.helo and self.helo:IsAlive()then +self.helo:StartUncontrolled() +delay=60 +else +self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!",self.helogroupname)) +return +end +else +self.helo=Spawn:SpawnAtAirbase(self.airbase,self.takeoff,nil,AIRBASE.TerminalType.HelicopterUsable) +if self.takeoff==SPAWN.Takeoff.Runway then +delay=5 +elseif self.takeoff==SPAWN.Takeoff.Hot then +delay=30 +elseif self.takeoff==SPAWN.Takeoff.Cold then +delay=60 +end +end +end +self.followset=SET_GROUP:New() +self.followset:AddGroup(self.helo) +self.HeloFuel0=self.helo:GetFuel() +self.formation=AI_FORMATION:New(self.carrier,self.followset,"Helo Formation with Carrier","Follow Carrier at given parameters.") +self.formation:FormationCenterWing(-self.offsetX,50,math.abs(self.altitude),50,self.offsetZ,50) +self.formation:SetFollowTimeInterval(self.dtFollow) +self.formation:SetFlightModeFormation(self.helo) +self.formation:__Start(delay) +self:__Status(1) +end +function RESCUEHELO:onafterStatus(From,Event,To) +local time=timer.getTime() +if self.helo and self.helo:IsAlive()then +local fuel=self.helo:GetFuel()*100 +local fuelrel=fuel/self.HeloFuel0 +local life=self.helo:GetUnit(1):GetLife() +local life0=self.helo:GetUnit(1):GetLife0() +local lifeR=self.helo:GetUnit(1):GetLifeRelative() +local text=string.format("Rescue Helo %s: state=%s fuel=%.1f, rel.fuel=%.1f, life=%.1f/%.1f=%d",self.helo:GetName(),self:GetState(),fuel,fuelrel,life,life0,lifeR*100) +MESSAGE:New(text,10,"DEBUG"):ToAllIf(self.Debug) +self:T(self.lid..text) +if self:IsRunning()then +if fuelUTILS.FeetToMeters(1500)then +dust=nil +end +local visibilitymin=visibility +if fog then +if fog.visibility10 then +reportedviz=10 +end +VISIBILITY=string.format("%d",reportedviz) +else +local reportedviz=UTILS.Round(UTILS.MetersToSM(visibilitymin)) +if reportedviz>10 then +reportedviz=10 +end +VISIBILITY=string.format("%d",reportedviz) +end +local cloudbase=clouds.base +local cloudceil=clouds.base+clouds.thickness +local clouddens=clouds.density +local cloudspreset=clouds.preset or"Nothing" +local precepitation=0 +if cloudspreset:find("Preset10")then +clouddens=4 +elseif cloudspreset:find("Preset11")then +clouddens=4 +elseif cloudspreset:find("Preset12")then +clouddens=4 +elseif cloudspreset:find("Preset13")then +clouddens=7 +elseif cloudspreset:find("Preset14")then +clouddens=7 +elseif cloudspreset:find("Preset15")then +clouddens=7 +elseif cloudspreset:find("Preset16")then +clouddens=7 +elseif cloudspreset:find("Preset17")then +clouddens=7 +elseif cloudspreset:find("Preset18")then +clouddens=7 +elseif cloudspreset:find("Preset19")then +clouddens=7 +elseif cloudspreset:find("Preset20")then +clouddens=7 +elseif cloudspreset:find("Preset21")then +clouddens=9 +elseif cloudspreset:find("Preset22")then +clouddens=9 +elseif cloudspreset:find("Preset23")then +clouddens=9 +elseif cloudspreset:find("Preset24")then +clouddens=9 +elseif cloudspreset:find("Preset25")then +clouddens=9 +elseif cloudspreset:find("Preset26")then +clouddens=9 +elseif cloudspreset:find("Preset27")then +clouddens=9 +elseif cloudspreset:find("Preset1")then +clouddens=1 +elseif cloudspreset:find("Preset2")then +clouddens=1 +elseif cloudspreset:find("Preset3")then +clouddens=4 +elseif cloudspreset:find("Preset4")then +clouddens=4 +elseif cloudspreset:find("Preset5")then +clouddens=4 +elseif cloudspreset:find("Preset6")then +clouddens=4 +elseif cloudspreset:find("Preset7")then +clouddens=4 +elseif cloudspreset:find("Preset8")then +clouddens=4 +elseif cloudspreset:find("Preset9")then +clouddens=4 +elseif cloudspreset:find("RainyPreset")then +clouddens=9 +if temperature>5 then +precepitation=1 +else +precepitation=3 +end +end +local CLOUDBASE=string.format("%d",UTILS.MetersToFeet(cloudbase)) +local CLOUDCEIL=string.format("%d",UTILS.MetersToFeet(cloudceil)) +if self.metric then +CLOUDBASE=string.format("%d",cloudbase) +CLOUDCEIL=string.format("%d",cloudceil) +end +local CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudbase)) +local CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(UTILS.MetersToFeet(cloudceil)) +if self.metric then +CLOUDBASE1000,CLOUDBASE0100=self:_GetThousandsAndHundreds(cloudbase) +CLOUDCEIL1000,CLOUDCEIL0100=self:_GetThousandsAndHundreds(cloudceil) +end +local CloudCover={} +CloudCover=ATIS.Sound.CloudsNotAvailable +local CLOUDSsub="Cloud coverage information not available" +if static then +if clouddens>=9 then +CloudCover=ATIS.Sound.CloudsOvercast +CLOUDSsub="Overcast" +elseif clouddens>=7 then +CloudCover=ATIS.Sound.CloudsBroken +CLOUDSsub="Broken clouds" +elseif clouddens>=4 then +CloudCover=ATIS.Sound.CloudsScattered +CLOUDSsub="Scattered clouds" +elseif clouddens>=1 then +CloudCover=ATIS.Sound.CloudsFew +CLOUDSsub="Few clouds" +else +CLOUDBASE=nil +CLOUDCEIL=nil +CloudCover=ATIS.Sound.CloudsNo +CLOUDSsub="No clouds" +end +end +local subtitle="" +subtitle=string.format("%s",self.airbasename) +if 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 then +subtitle=subtitle.." Airport" +end +self.radioqueue:NewTransmission(string.format("%s/%s.ogg",self.theatre,self.airbasename),3.0,self.soundpath,nil,nil,subtitle,self.subduration) +local alltext=subtitle +subtitle=string.format("Information %s",NATO) +local _INFORMATION=subtitle +self:Transmission(ATIS.Sound.Information,0.5,subtitle) +self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg",NATO),0.75,self.soundpath) +alltext=alltext..";\n"..subtitle +subtitle=string.format("%s Zulu",ZULU) +self.radioqueue:Number2Transmission(ZULU,nil,0.5) +self:Transmission(ATIS.Sound.Zulu,0.2,subtitle) +alltext=alltext..";\n"..subtitle +if not self.zulutimeonly then +subtitle=string.format("Sunrise at %s local time",SUNRISE) +self:Transmission(ATIS.Sound.SunriseAt,0.5,subtitle) +self.radioqueue:Number2Transmission(SUNRISE,nil,0.2) +self:Transmission(ATIS.Sound.TimeLocal,0.2) +alltext=alltext..";\n"..subtitle +subtitle=string.format("Sunset at %s local time",SUNSET) +self:Transmission(ATIS.Sound.SunsetAt,0.5,subtitle) +self.radioqueue:Number2Transmission(SUNSET,nil,0.5) +self:Transmission(ATIS.Sound.TimeLocal,0.2) +alltext=alltext..";\n"..subtitle +end +if self.metric then +subtitle=string.format("Wind from %s at %s m/s",WINDFROM,WINDSPEED) +else +subtitle=string.format("Wind from %s at %s knots",WINDFROM,WINDSPEED) +end +if turbulence>0 then +subtitle=subtitle..", gusting" +end +local _WIND=subtitle +self:Transmission(ATIS.Sound.WindFrom,1.0,subtitle) +self.radioqueue:Number2Transmission(WINDFROM) +self:Transmission(ATIS.Sound.At,0.2) +self.radioqueue:Number2Transmission(WINDSPEED) +if self.metric then +self:Transmission(ATIS.Sound.MetersPerSecond,0.2) +else +self:Transmission(ATIS.Sound.Knots,0.2) +end +if turbulence>0 then +self:Transmission(ATIS.Sound.Gusting,0.2) +end +alltext=alltext..";\n"..subtitle +if self.metric then +subtitle=string.format("Visibility %s km",VISIBILITY) +else +subtitle=string.format("Visibility %s SM",VISIBILITY) +end +self:Transmission(ATIS.Sound.Visibilty,1.0,subtitle) +self.radioqueue:Number2Transmission(VISIBILITY) +if self.metric then +self:Transmission(ATIS.Sound.Kilometers,0.2) +else +self:Transmission(ATIS.Sound.StatuteMiles,0.2) +end +alltext=alltext..";\n"..subtitle +local wp=false +local wpsub="" +if precepitation==1 then +wp=true +wpsub=wpsub.." rain" +elseif precepitation==2 then +if wp then +wpsub=wpsub.."," +end +wpsub=wpsub.." thunderstorm" +wp=true +elseif precepitation==3 then +wpsub=wpsub.." snow" +wp=true +elseif precepitation==4 then +wpsub=wpsub.." snowstorm" +wp=true +end +if fog then +if wp then +wpsub=wpsub.."," +end +wpsub=wpsub.." fog" +wp=true +end +if dust then +if wp then +wpsub=wpsub.."," +end +wpsub=wpsub.." dust" +wp=true +end +if wp then +subtitle=string.format("Weather phenomena:%s",wpsub) +self:Transmission(ATIS.Sound.WeatherPhenomena,1.0,subtitle) +if precepitation==1 then +self:Transmission(ATIS.Sound.Rain,0.5) +elseif precepitation==2 then +self:Transmission(ATIS.Sound.ThunderStorm,0.5) +elseif precepitation==3 then +self:Transmission(ATIS.Sound.Snow,0.5) +elseif precepitation==4 then +self:Transmission(ATIS.Sound.SnowStorm,0.5) +end +if fog then +self:Transmission(ATIS.Sound.Fog,0.5) +end +if dust then +self:Transmission(ATIS.Sound.Dust,0.5) +end +alltext=alltext..";\n"..subtitle +end +self:Transmission(CloudCover,1.0,CLOUDSsub) +if CLOUDBASE and static then +if self.metric then +subtitle=string.format("Cloudbase %s, ceiling %s meters",CLOUDBASE,CLOUDCEIL) +else +subtitle=string.format("Cloudbase %s, ceiling %s ft",CLOUDBASE,CLOUDCEIL) +end +self:Transmission(ATIS.Sound.CloudBase,1.0,subtitle) +if tonumber(CLOUDBASE1000)>0 then +self.radioqueue:Number2Transmission(CLOUDBASE1000) +self:Transmission(ATIS.Sound.Thousand,0.1) +end +if tonumber(CLOUDBASE0100)>0 then +self.radioqueue:Number2Transmission(CLOUDBASE0100) +self:Transmission(ATIS.Sound.Hundred,0.1) +end +self:Transmission(ATIS.Sound.CloudCeiling,0.5) +if tonumber(CLOUDCEIL1000)>0 then +self.radioqueue:Number2Transmission(CLOUDCEIL1000) +self:Transmission(ATIS.Sound.Thousand,0.1) +end +if tonumber(CLOUDCEIL0100)>0 then +self.radioqueue:Number2Transmission(CLOUDCEIL0100) +self:Transmission(ATIS.Sound.Hundred,0.1) +end +if self.metric then +self:Transmission(ATIS.Sound.Meters,0.1) +else +self:Transmission(ATIS.Sound.Feet,0.1) +end +end +alltext=alltext..";\n"..subtitle +if self.TDegF then +if temperature<0 then +subtitle=string.format("Temperature -%s °F",TEMPERATURE) +else +subtitle=string.format("Temperature %s °F",TEMPERATURE) +end +else +if temperature<0 then +subtitle=string.format("Temperature -%s °C",TEMPERATURE) +else +subtitle=string.format("Temperature %s °C",TEMPERATURE) +end +end +local _TEMPERATURE=subtitle +self:Transmission(ATIS.Sound.Temperature,1.0,subtitle) +if temperature<0 then +self:Transmission(ATIS.Sound.Minus,0.2) +end +self.radioqueue:Number2Transmission(TEMPERATURE) +if self.TDegF then +self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2) +else +self:Transmission(ATIS.Sound.DegreesCelsius,0.2) +end +alltext=alltext..";\n"..subtitle +if self.TDegF then +if dewpoint<0 then +subtitle=string.format("Dew point -%s °F",DEWPOINT) +else +subtitle=string.format("Dew point %s °F",DEWPOINT) +end +else +if dewpoint<0 then +subtitle=string.format("Dew point -%s °C",DEWPOINT) +else +subtitle=string.format("Dew point %s °C",DEWPOINT) +end +end +local _DEWPOINT=subtitle +self:Transmission(ATIS.Sound.DewPoint,1.0,subtitle) +if dewpoint<0 then +self:Transmission(ATIS.Sound.Minus,0.2) +end +self.radioqueue:Number2Transmission(DEWPOINT) +if self.TDegF then +self:Transmission(ATIS.Sound.DegreesFahrenheit,0.2) +else +self:Transmission(ATIS.Sound.DegreesCelsius,0.2) +end +alltext=alltext..";\n"..subtitle +if self.PmmHg then +if self.qnhonly then +subtitle=string.format("Altimeter %s.%s mmHg",QNH[1],QNH[2]) +else +subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg",QNH[1],QNH[2],QFE[1],QFE[2]) +end +else +if self.metric then +if self.qnhonly then +subtitle=string.format("Altimeter %s.%s hPa",QNH[1],QNH[2]) +else +subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa",QNH[1],QNH[2],QFE[1],QFE[2]) +end +else +if self.qnhonly then +subtitle=string.format("Altimeter %s.%s inHg",QNH[1],QNH[2]) +else +subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s inHg",QNH[1],QNH[2],QFE[1],QFE[2]) +end +end +end +local _ALTIMETER=subtitle +self:Transmission(ATIS.Sound.Altimeter,1.0,subtitle) +if not self.qnhonly then +self:Transmission(ATIS.Sound.QNH,0.5) +end +self.radioqueue:Number2Transmission(QNH[1]) +if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then +self:Transmission(ATIS.Sound.Decimal,0.2) +end +self.radioqueue:Number2Transmission(QNH[2]) +if not self.qnhonly then +self:Transmission(ATIS.Sound.QFE,0.75) +self.radioqueue:Number2Transmission(QFE[1]) +if ATIS.ICAOPhraseology[UTILS.GetDCSMap()]then +self:Transmission(ATIS.Sound.Decimal,0.2) +end +self.radioqueue:Number2Transmission(QFE[2]) +end +if self.PmmHg then +self:Transmission(ATIS.Sound.MillimetersOfMercury,0.1) +else +if self.metric then +self:Transmission(ATIS.Sound.HectoPascal,0.1) +else +self:Transmission(ATIS.Sound.InchesOfMercury,0.1) +end +end +alltext=alltext..";\n"..subtitle +local subtitle=string.format("Active runway %s",runway) +if rwyLeft==true then +subtitle=subtitle.." Left" +elseif rwyLeft==false then +subtitle=subtitle.." Right" +end +local _RUNACT=subtitle +self:Transmission(ATIS.Sound.ActiveRunway,1.0,subtitle) +self.radioqueue:Number2Transmission(runway) +if rwyLeft==true then +self:Transmission(ATIS.Sound.Left,0.2) +elseif rwyLeft==false then +self:Transmission(ATIS.Sound.Right,0.2) +end +alltext=alltext..";\n"..subtitle +if self.rwylength then +local runact=self.airbase:GetActiveRunway(self.runwaym2t) +local length=runact.length +if not self.metric then +length=UTILS.MetersToFeet(length) +end +local L1000,L0100=self:_GetThousandsAndHundreds(length) +local subtitle=string.format("Runway length %d",length) +if self.metric then +subtitle=subtitle.." meters" +else +subtitle=subtitle.." feet" +end +self:Transmission(ATIS.Sound.RunwayLength,1.0,subtitle) +if tonumber(L1000)>0 then +self.radioqueue:Number2Transmission(L1000) +self:Transmission(ATIS.Sound.Thousand,0.1) +end +if tonumber(L0100)>0 then +self.radioqueue:Number2Transmission(L0100) +self:Transmission(ATIS.Sound.Hundred,0.1) +end +if self.metric then +self:Transmission(ATIS.Sound.Meters,0.1) +else +self:Transmission(ATIS.Sound.Feet,0.1) +end +alltext=alltext..";\n"..subtitle +end +if self.elevation then +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("Elevation %d",elevation) +if self.metric then +subtitle=subtitle.." meters" +else +subtitle=subtitle.." feet" +end +self:Transmission(ATIS.Sound.Elevation,1.0,subtitle) +if tonumber(L1000)>0 then +self.radioqueue:Number2Transmission(L1000) +self:Transmission(ATIS.Sound.Thousand,0.1) +end +if tonumber(L0100)>0 then +self.radioqueue:Number2Transmission(L0100) +self:Transmission(ATIS.Sound.Hundred,0.1) +end +if self.metric then +self:Transmission(ATIS.Sound.Meters,0.1) +else +self:Transmission(ATIS.Sound.Feet,0.1) +end +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 +subtitle=string.format("Tower frequency %s",freqs) +self:Transmission(ATIS.Sound.TowerFrequency,1.0,subtitle) +for _,freq in pairs(self.towerfrequency)do +local f=string.format("%.3f",freq) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(ATIS.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(ATIS.Sound.MegaHertz,0.2) +end +alltext=alltext..";\n"..subtitle +end +local ils=self:GetNavPoint(self.ils,runway,rwyLeft) +if ils then +subtitle=string.format("ILS frequency %.2f MHz",ils.frequency) +self:Transmission(ATIS.Sound.ILSFrequency,1.0,subtitle) +local f=string.format("%.2f",ils.frequency) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(ATIS.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(ATIS.Sound.MegaHertz,0.2) +alltext=alltext..";\n"..subtitle +end +local ndb=self:GetNavPoint(self.ndbouter,runway,rwyLeft) +if ndb then +subtitle=string.format("Outer NDB frequency %.2f MHz",ndb.frequency) +self:Transmission(ATIS.Sound.OuterNDBFrequency,1.0,subtitle) +local f=string.format("%.2f",ndb.frequency) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(ATIS.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(ATIS.Sound.MegaHertz,0.2) +alltext=alltext..";\n"..subtitle +end +local ndb=self:GetNavPoint(self.ndbinner,runway,rwyLeft) +if ndb then +subtitle=string.format("Inner NDB frequency %.2f MHz",ndb.frequency) +self:Transmission(ATIS.Sound.InnerNDBFrequency,1.0,subtitle) +local f=string.format("%.2f",ndb.frequency) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(ATIS.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(ATIS.Sound.MegaHertz,0.2) +alltext=alltext..";\n"..subtitle +end +if self.vor then +subtitle=string.format("VOR frequency %.2f MHz",self.vor) +self:Transmission(ATIS.Sound.VORFrequency,1.0,subtitle) +local f=string.format("%.2f",self.vor) +f=UTILS.Split(f,".") +self.radioqueue:Number2Transmission(f[1],nil,0.5) +if tonumber(f[2])>0 then +self:Transmission(ATIS.Sound.Decimal,0.2) +self.radioqueue:Number2Transmission(f[2]) +end +self:Transmission(ATIS.Sound.MegaHertz,0.2) +alltext=alltext..";\n"..subtitle +end +if self.tacan then +subtitle=string.format("TACAN channel %dX",self.tacan) +self:Transmission(ATIS.Sound.TACANChannel,1.0,subtitle) +self.radioqueue:Number2Transmission(tostring(self.tacan),nil,0.2) +self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg",0.75,self.soundpath,nil,0.2) +alltext=alltext..";\n"..subtitle +end +if self.rsbn then +subtitle=string.format("RSBN channel %d",self.rsbn) +self:Transmission(ATIS.Sound.RSBNChannel,1.0,subtitle) +self.radioqueue:Number2Transmission(tostring(self.rsbn),nil,0.2) +alltext=alltext..";\n"..subtitle +end +local ndb=self:GetNavPoint(self.prmg,runway,rwyLeft) +if ndb then +subtitle=string.format("PRMG channel %d",ndb.frequency) +self:Transmission(ATIS.Sound.PRMGChannel,1.0,subtitle) +self.radioqueue:Number2Transmission(tostring(ndb.frequency),nil,0.5) +alltext=alltext..";\n"..subtitle +end +subtitle=string.format("Advise on initial contact, you have information %s",NATO) +self:Transmission(ATIS.Sound.AdviceOnInitial,0.5,subtitle) +self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg",NATO),0.75,self.soundpath) +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(self.lid..string.format("Report:\n%s",Text)) +end +function ATIS:UpdateMarker(information,runact,wind,altimeter,temperature) +if self.markerid then +self.airbase:GetCoordinate():RemoveMark(self.markerid) +end +local text=string.format("ATIS on %.3f %s, %s:\n",self.frequency,UTILS.GetModulationName(self.modulation),tostring(information)) +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() +local coord=self.airbase:GetCoordinate() +local height=coord:GetLandHeight() +local windFrom,windSpeed=coord:GetWind(height+10) +local runact=self.airbase:GetActiveRunway(self.runwaym2t) +local runway=self:GetMagneticRunway(windFrom)or runact.idx +local rwyLeft=nil +if self.activerunway then +local runwayno=self:GetRunwayWithoutLR(self.activerunway) +if runwayno~=""then +runway=runwayno +end +rwyLeft=self:GetRunwayLR(self.activerunway) +end +return runway,rwyLeft +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 diffself.Tstop or false 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: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() +local Ntargets=self:CountMissionTargets() +local Ntargets0=self:GetTargetInitialNumber() +local Ngroups=self:CountOpsGroups() +if self:IsNotOver()then +if self:CheckGroupsDone()then +self:Done() +elseif(self.Tstop and Tnow>self.Tstop+10)or(Ntargets0>0 and Ntargets==0)then +self:Cancel() +end +end +local fsmstate=self:GetState() +if fsmstate~=self.status then +self:E(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 airwing=self.airwing and self.airwing.alias or"N/A" +local commander=self.wingcommander and tostring(self.wingcommander.coalition)or"N/A" +self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s",self.status,targetname,Cstart,Cstop,#self.assets,Ngroups,Ntargets,airwing,commander)) +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 +local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false +if self:IsOver()and ready2evaluate then +self:Evaluate() +else +self:__Status(-30) +end +if self.markerOn then +self:UpdateMarker() +end +end +function AUFTRAG:Evaluate() +local failed=false +local targetdamage=self:GetTargetDamage() +local owndamage=self.Ncasualties/self.Nelements*100 +local Ntargets=self:CountMissionTargets() +local Ntargets0=self:GetTargetInitialNumber() +local Life=self:GetTargetLife() +local Life0=self:GetTargetInitialLife() +if Ntargets0>0 then +if self.type==AUFTRAG.Type.TROOPTRANSPORT or self.type==AUFTRAG.Type.ESCORT then +if Ntargets0 then +failed=true +end +end +else +if self.Nelements==self.Ncasualties then +failed=true +end +end +local successCondition=self:EvalConditionsAny(self.conditionSuccess) +local failureCondition=self:EvalConditionsAny(self.conditionFailure) +if failureCondition then +failed=true +elseif successCondition then +failed=false +end +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) +if failed then +self:Failed() +else +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) +self:T(self.lid..string.format("Setting flight %s to status %s",opsgroup and opsgroup.groupname or"nil",tostring(status))) +if self:GetGroupStatus(opsgroup)==AUFTRAG.GroupStatus.CANCELLED and status==AUFTRAG.GroupStatus.DONE then +else +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.status=status +else +self:E(self.lid.."WARNING: Could not SET flight data for flight group. Setting status to DONE") +end +end +self:T2(self.lid..string.format("Setting flight %s status to %s. IsNotOver=%s CheckGroupsDone=%s",opsgroup.groupname,self:GetGroupStatus(opsgroup),tostring(self:IsNotOver()),tostring(self:CheckGroupsDone()))) +if self:IsNotOver()and self:CheckGroupsDone()then +self:T3(self.lid.."All flights done ==> mission DONE!") +self:Done() +else +self:T3(self.lid.."Mission NOT DONE yet!") +end +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:E(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:SetGroupWaypointCoordinate(opsgroup,coordinate) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.waypointcoordinate=coordinate +end +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 waypoint index %d",waypointindex)) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +groupdata.waypointindex=waypointindex +end +end +function AUFTRAG:GetGroupWaypointIndex(opsgroup) +local groupdata=self:GetGroupData(opsgroup) +if groupdata then +return groupdata.waypointindex +end +end +function AUFTRAG:CheckGroupsDone() +if self:IsPlanned()or self:IsQueued()or self:IsRequested()then +return false +end +if self:IsStarted()and self:CountOpsGroups()==0 then +return true +end +for groupname,data in pairs(self.groupdata)do +local groupdata=data +if groupdata then +if groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED then +else +return false +end +end +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:I(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.airwing=Airwing +self:T(self.lid..string.format("New mission status=%s at airwing %s",self.status,tostring(Airwing.alias))) +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:T(self.lid..string.format("New mission status=%s",self.status)) +end +function AUFTRAG:onafterExecuting(From,Event,To) +self.status=AUFTRAG.Status.EXECUTING +self:T(self.lid..string.format("New mission status=%s",self.status)) +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() +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 +end +function AUFTRAG:onafterAssetDead(From,Event,To,Asset) +local N=self:CountOpsGroups() +self:I(self.lid..string.format("Asset %s dead! Number of ops groups remaining %d",tostring(Asset.spawngroupname),N)) +if N==0 then +if self:IsNotOver()then +self:Cancel() +else +end +end +self:DelAsset(Asset) +end +function AUFTRAG:onafterCancel(From,Event,To) +self:I(self.lid..string.format("CANCELLING mission in status %s. Will wait for groups to report mission DONE before evaluation",self.status)) +self.Tover=timer.getAbsTime() +self.Nrepeat=self.repeated +self.NrepeatFailure=self.repeatedFailure +self.NrepeatSuccess=self.repeatedSuccess +self.dTevaluate=0 +if self.wingcommander then +self:T(self.lid..string.format("Wingcommander will cancel the mission. Will wait for mission DONE before evaluation!")) +self.wingcommander:CancelMission(self) +elseif self.airwing then +self:T(self.lid..string.format("Airwing %s will cancel the mission. Will wait for mission DONE before evaluation!",self.airwing.alias)) +self.airwing:MissionCancel(self) +else +self:T(self.lid..string.format("No airwing or wingcommander. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) +for _,_groupdata in pairs(self.groupdata)do +local groupdata=_groupdata +groupdata.opsgroup:MissionCancel(self) +end +end +if self.status==AUFTRAG.Status.PLANNED then +self:T(self.lid..string.format("Cancelled mission was in planned stage. Call it done!")) +self:Done() +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)) +local repeatme=self.repeatedSuccess Repeat mission!",self.repeated+1,N)) +self:Repeat() +else +self:I(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)) +local repeatme=self.repeatedFailure Repeat mission!",self.repeated+1,N)) +self:Repeat() +else +self:I(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!",self.repeated+1)) +self:Stop() +end +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.repeated=self.repeated+1 +if self.chief then +elseif self.wingcommander then +if self.airwing then +self.airwing:RemoveMission(self) +end +elseif self.airwing then +self:Queued(self.airwing) +else +self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, WINGCOMMANDER or AIRWING! Stopping AUFTRAG") +self:Stop() +end +self.assets={} +for _,_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:__Status(-30) +end +function AUFTRAG:onafterStop(From,Event,To) +self:I(self.lid..string.format("STOPPED mission in status=%s. Removing missions from queues. Stopping CallScheduler!",self.status)) +if self.wingcommander then +self.wingcommander:RemoveMission(self) +end +if self.airwing then +self.airwing:RemoveMission(self) +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:IsInstanceOf("TARGET")then +self.engageTarget=Object +else +self.engageTarget=TARGET:New(Object) +end +else +end +return self +end +function AUFTRAG:CountMissionTargets() +if self.engageTarget then +return self.engageTarget:CountTargets() +else +return 0 +end +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() +return self:GetTargetData():GetObject() +end +function AUFTRAG:GetTargetType() +return self:GetTargetData().Type +end +function AUFTRAG:GetTargetVec2() +local coord=self:GetTargetCoordinate() +if coord then +return coord:GetVec2() +end +return nil +end +function AUFTRAG:GetTargetCoordinate() +if self.transportPickup then +return self.transportPickup +elseif self.engageTarget then +return self.engageTarget:GetCoordinate() +else +self:E(self.lid.."ERROR: Cannot get target coordinate!") +end +return nil +end +function AUFTRAG:GetTargetName() +if self.engageTarget then +return self.engageTarget:GetName() +end +return"N/A" +end +function AUFTRAG:GetTargetDistance(FromCoord) +local TargetCoord=self:GetTargetCoordinate() +if TargetCoord and FromCoord then +return TargetCoord:Get2DDistance(FromCoord) +else +self:E(self.lid.."ERROR: TargetCoord or FromCoord does not exist in AUFTRAG:GetTargetDistance() function! Returning 0") +end +return 0 +end +function AUFTRAG:AddAsset(Asset) +self.assets=self.assets or{} +table.insert(self.assets,Asset) +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:GetMissionTypesText(MissionTypes) +local text="" +for _,missiontype in pairs(MissionTypes)do +text=text..string.format("%s, ",missiontype) +end +return text +end +function AUFTRAG:SetMissionWaypointCoord(Coordinate) +self.missionWaypointCoord=Coordinate +end +function AUFTRAG:GetMissionWaypointCoord(group) +if self.missionWaypointCoord then +local coord=self.missionWaypointCoord +if self.missionAltitude then +coord.y=self.missionAltitude +end +return coord +end +local waypointcoord=group:GetCoordinate():GetIntermediateCoordinate(self:GetTargetCoordinate(),self.missionFraction) +local alt=waypointcoord.y +waypointcoord=ZONE_RADIUS:New("Temp",waypointcoord:GetVec2(),1000):GetRandomCoordinate():SetAltitude(alt,false) +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: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("\nFlights %d/%d",self:CountOpsGroups(),self.nassets) +if not self.marker then +local targetcoord=self:GetTargetCoordinate() +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 +else +if self.marker:GetText()~=text then +self.marker:UpdateText(text) +end +end +return self +end +function AUFTRAG:GetDCSMissionTask(TaskControllable) +local DCStasks={} +if self.type==AUFTRAG.Type.ANTISHIP then +self:_GetDCSAttackTask(self.engageTarget,DCStasks) +elseif self.type==AUFTRAG.Type.AWACS then +local DCStask=CONTROLLABLE.EnRouteTaskAWACS(nil) +table.insert(self.enrouteTasks,DCStask) +elseif self.type==AUFTRAG.Type.BAI then +self:_GetDCSAttackTask(self.engageTarget,DCStasks) +elseif self.type==AUFTRAG.Type.BOMBING then +local DCStask=CONTROLLABLE.TaskBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,Divebomb) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.BOMBRUNWAY then +local DCStask=CONTROLLABLE.TaskBombingRunway(nil,self.engageTarget:GetObject(),self.engageWeaponType,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAsGroup) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.BOMBCARPET then +local DCStask=CONTROLLABLE.TaskCarpetBombing(nil,self:GetTargetVec2(),self.engageAsGroup,self.engageWeaponExpend,self.engageQuantity,self.engageDirection,self.engageAltitude,self.engageWeaponType,self.engageCarpetLength) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.CAP then +local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority) +table.insert(self.enrouteTasks,DCStask) +elseif self.type==AUFTRAG.Type.CAS then +local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil,self.engageZone:GetVec2(),self.engageZone:GetRadius(),self.engageTargetTypes,Priority) +table.insert(self.enrouteTasks,DCStask) +elseif self.type==AUFTRAG.Type.ESCORT then +local DCStask=CONTROLLABLE.TaskEscort(nil,self.engageTarget:GetObject(),self.escortVec3,LastWaypointIndex,self.engageMaxDistance,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.FERRY then +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 +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 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.RESCUEHELO then +local DCStask={} +DCStask.id="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 +local DCStask=CONTROLLABLE.TaskFireAtPoint(nil,self:GetTargetVec2(),self.artyRadius,self.artyShots,self.engageWeaponType) +table.insert(DCStasks,DCStask) +elseif self.type==AUFTRAG.Type.PATROLZONE then +local DCStask={} +DCStask.id="PatrolZone" +local param={} +param.zone=self:GetObjective() +param.altitude=self.missionAltitude +param.speed=self.missionSpeed +DCStask.params=param +table.insert(DCStasks,DCStask) +else +self:E(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 then +local Coordinate=self:GetTargetCoordinate() +local DCStask=CONTROLLABLE.TaskOrbit(nil,Coordinate,self.orbitAltitude,self.orbitSpeed,self.orbitRaceTrack) +table.insert(DCStasks,DCStask) +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 +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 +} +TARGET.ObjectType={ +GROUP="Group", +UNIT="Unit", +STATIC="Static", +SCENERY="Scenery", +COORDINATE="Coordinate", +AIRBASE="Airbase", +ZONE="Zone", +} +TARGET.Category={ +AIRCRAFT="Aircraft", +GROUND="Ground", +NAVAL="Naval", +AIRBASE="Airbase", +COORDINATE="Coordinate", +ZONE="Zone", +} +TARGET.ObjectStatus={ +ALIVE="Alive", +DEAD="Dead", +} +_TARGETID=0 +TARGET.version="0.3.1" +function TARGET:New(TargetObject) +local self=BASE:Inherit(self,FSM:New()) +_TARGETID=_TARGETID+1 +self:AddObject(TargetObject) +local Target=self.targets[1] +if not Target then +self:E("ERROR: No valid TARGET!") +return nil +end +self.name=self:GetTargetName(Target) +self.category=self:GetTargetCategory(Target) +self.lid=string.format("TARGET #%03d [%s] | ",_TARGETID,tostring(self.category)) +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","*") +self:AddTransition("*","Destroyed","Dead") +self:AddTransition("*","Dead","Dead") +self:__Start(-1) +return self +end +function TARGET:AddObject(Object) +if Object:IsInstanceOf("SET_GROUP")or Object:IsInstanceOf("SET_UNIT")then +local set=Object +for _,object in pairs(set.Set)do +self:AddObject(object) +end +else +self:_AddObject(Object) +end +end +function TARGET:IsAlive() +return self:Is("Alive") +end +function TARGET:IsDead() +return self:Is("Dead") +end +function TARGET:onafterStart(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) +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=1 then +local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f",fsmstate,self:CountTargets(),self.N0,self:GetLife(),self:GetLife0(),self:GetDamage()) +if damaged then +text=text.." Damaged!" +end +self:I(self.lid..text) +end +if self.verbose>=2 then +local text="Target:" +for i,_target in pairs(self.targets)do +local target=_target +local damage=(1-target.Life/target.Life0)*100 +text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f",i,target.Type,target.Name,target.Status,target.Life,target.Life0,damage) +end +self:I(self.lid..text) +end +if self:IsAlive()then +self:__Status(-30) +end +end +function TARGET:onafterObjectDamaged(From,Event,To,Target) +self:T(self.lid..string.format("Object %s damaged",Target.Name)) +end +function TARGET:onafterObjectDestroyed(From,Event,To,Target) +self:T(self.lid..string.format("Object %s destroyed",Target.Name)) +self.Ndestroyed=self.Ndestroyed+1 +self:ObjectDead(Target) +end +function TARGET:onafterObjectDead(From,Event,To,Target) +self:T(self.lid..string.format("Object %s dead",Target.Name)) +Target.Status=TARGET.ObjectStatus.DEAD +self.Ndead=self.Ndead+1 +local dead=true +for _,_target in pairs(self.targets)do +local target=_target +if target.Status==TARGET.ObjectStatus.ALIVE then +dead=false +end +end +if dead then +if self.Ndestroyed==self.Ntargets0 then +self:Destroyed() +else +self:Dead() +end +end +end +function TARGET:onafterDamaged(From,Event,To) +self:T(self.lid..string.format("TARGET damaged")) +end +function TARGET:onafterDestroyed(From,Event,To) +self:T(self.lid..string.format("TARGET destroyed")) +self:Dead() +end +function TARGET:onafterDead(From,Event,To) +self:T(self.lid..string.format("TARGET dead")) +end +function TARGET:OnEventUnitDeadOrLost(EventData) +local Name=EventData and EventData.IniUnitName or nil +if self:IsElement(Name)and not self:IsCasualty(Name)then +self:T3(self.lid..string.format("EVENT ID=%d: Unit %s dead or lost!",EventData.id,tostring(Name))) +table.insert(self.casualties,Name) +local target=self:GetTargetByName(EventData.IniGroupName) +if not target then +target=self:GetTargetByName(EventData.IniUnitName) +end +if target then +if EventData.id==EVENTS.RemoveUnit then +target.Ndead=target.Ndead+1 +else +target.Ndestroyed=target.Ndestroyed+1 +target.Ndead=target.Ndead+1 +end +if target.Ndead==target.N0 then +if target.Ndestroyed>=target.N0 then +self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed",EventData.id,tostring(target.Name))) +self:ObjectDestroyed(target) +else +self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead",EventData.id,tostring(target.Name))) +self:ObjectDead(target) +end +end +end +end +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=1 +target.Life=1 +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=1 +target.Life=1 +target.N0=target.N0+1 +table.insert(self.elements,target.Name) +elseif Object:IsInstanceOf("AIRBASE")then +local airbase=Object +target.Type=TARGET.ObjectType.AIRBASE +target.Name=airbase:GetName() +target.Coordinate=airbase:GetCoordinate() +target.Life0=1 +target.Life=1 +target.N0=target.N0+1 +table.insert(self.elements,target.Name) +elseif Object:IsInstanceOf("COORDINATE")then +local coord=UTILS.DeepCopy(Object) +target.Type=TARGET.ObjectType.COORDINATE +target.Name=coord:ToStringMGRS() +target.Coordinate=coord +target.Life0=1 +target.Life=1 +elseif Object:IsInstanceOf("ZONE_BASE")then +local zone=Object +Object=zone +target.Type=TARGET.ObjectType.ZONE +target.Name=zone:GetName() +target.Coordinate=zone:GetCoordinate() +target.Life0=1 +target.Life=1 +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) +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 +return 1 +else +return 0 +end +elseif Target.Type==TARGET.ObjectType.SCENERY then +if Target.Status==TARGET.ObjectStatus.ALIVE then +return 1 +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 then +return 1 +else +self:E("ERROR: unknown target object type in GetTargetLife!") +end +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:GetTargetVec3(Target) +if Target.Type==TARGET.ObjectType.GROUP 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.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 +end +self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3") +end +function TARGET:GetTargetCoordinate(Target) +if Target.Type==TARGET.ObjectType.COORDINATE then +return Target.Object +else +local vec3=self:GetTargetVec3(Target) +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() +end +return"Unknown" +end +function TARGET:GetName() +return self.name +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",self.name)) +return nil +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 +else +self:E("ERROR: unknown target category!") +end +return category +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() +for _,_target in pairs(self.targets)do +local target=_target +if target.Status==TARGET.ObjectStatus.ALIVE then +return target +end +end +return nil +end +function TARGET:GetObject() +local target=self:GetObjective() +if target then +return target.Object +end +return nil +end +function TARGET:CountObjectives(Target) +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 +N=N+1 +end +end +elseif Target.Type==TARGET.ObjectType.UNIT then +local target=Target.Object +if target and target:IsAlive()~=nil and target:GetLife()>1 then +N=N+1 +end +elseif Target.Type==TARGET.ObjectType.STATIC then +local target=Target.Object +if target and target:IsAlive()then +N=N+1 +end +elseif Target.Type==TARGET.ObjectType.SCENERY then +if Target.Status==TARGET.ObjectStatus.ALIVE then +N=N+1 +end +elseif Target.Type==TARGET.ObjectType.AIRBASE then +if Target.Status==TARGET.ObjectStatus.ALIVE then +N=N+1 +end +elseif Target.Type==TARGET.ObjectType.COORDINATE then +elseif Target.Type==TARGET.ObjectType.ZONE then +else +self:E(self.lid.."ERROR: Unknown target type! Cannot count targets") +end +return N +end +function TARGET:CountTargets() +local N=0 +for _,_target in pairs(self.targets)do +local Target=_target +N=N+self:CountObjectives(Target) +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 name==Name then +return true +end +end +return false +end +OPSGROUP={ +ClassName="OPSGROUP", +Debug=false, +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, +respawning=nil, +wpcounter=1, +radio={}, +option={}, +optionDefault={}, +tacan={}, +icls={}, +callsign={}, +Ndestroyed=0, +Nkills=0, +weaponData={}, +} +OPSGROUP.ElementStatus={ +INUTERO="inutero", +SPAWNED="spawned", +PARKING="parking", +ENGINEON="engineon", +TAXIING="taxiing", +TAKEOFF="takeoff", +AIRBORNE="airborne", +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.version="0.7.1" +function OPSGROUP:New(Group) +local self=BASE:Inherit(self,FSM:New()) +if type(Group)=="string"then +self.groupname=Group +self.group=GROUP:FindByName(self.groupname) +else +self.group=Group +self.groupname=Group:GetName() +end +self.lid=string.format("OPSGROUP %s | ",tostring(self.groupname)) +if self.group then +if not self:IsExist()then +self:E(self.lid.."ERROR: GROUP does not exist! Returning nil") +return nil +end +end +self.detectedunits=SET_UNIT:New() +self.detectedgroups=SET_GROUP:New() +self.inzones=SET_ZONE:New() +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.taskcurrent=0 +self.taskcounter=0 +self:SetStartState("InUtero") +self:AddTransition("InUtero","Spawned","Spawned") +self:AddTransition("*","Dead","Dead") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","Status","*") +self:AddTransition("*","Destroyed","*") +self:AddTransition("*","Damaged","*") +self:AddTransition("*","UpdateRoute","*") +self:AddTransition("*","Respawn","*") +self:AddTransition("*","PassingWaypoint","*") +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("*","PassingWaypoint","*") +self:AddTransition("*","GotoWaypoint","*") +self:AddTransition("*","OutOfAmmo","*") +self:AddTransition("*","OutOfGuns","*") +self:AddTransition("*","OutOfRockets","*") +self:AddTransition("*","OutOfBombs","*") +self:AddTransition("*","OutOfMissiles","*") +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("*","ElementSpawned","*") +self:AddTransition("*","ElementDestroyed","*") +self:AddTransition("*","ElementDead","*") +self:AddTransition("*","ElementDamaged","*") +return self +end +function OPSGROUP:GetCoalition() +return self.group:GetCoalition() +end +function OPSGROUP:GetLifePoints() +if self.group then +return self.group:GetLife(),self.group:GetLife0() +end +end +function OPSGROUP:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function OPSGROUP:SetDefaultSpeed(Speed) +if Speed then +self.speedCruise=UTILS.KnotsToKmph(Speed) +end +return self +end +function OPSGROUP:GetSpeedCruise() +return UTILS.KmphToKnots(self.speedCruise or self.speedMax*0.7) +end +function OPSGROUP:SetDetection(Switch) +self.detectionOn=Switch +return self +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) +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[weapon.BitType]=weapon +return self +end +function OPSGROUP:GetWeaponData(BitType) +BitType=BitType or ENUMS.WeaponFlag.Auto +if self.weaponData[BitType]then +return self.weaponData[BitType] +else +return self.weaponData[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 threatlevellevelmax then +threat=unit +levelmax=threatlevel +end +end +return threat,levelmax +end +function OPSGROUP:HasLoS(Coordinate,Element,OffsetElement,OffsetCoordinate) +local Vec3=Coordinate:GetVec3() +if OffsetCoordinate then +Vec3=UTILS.VecAdd(Vec3,OffsetCoordinate) +end +local function checklos(element) +local vec3=element.unit:GetVec3() +if OffsetElement then +vec3=UTILS.VecAdd(vec3,OffsetElement) +end +local _los=land.isVisible(vec3,Vec3) +return _los +end +if Element then +local los=checklos(Element) +return los +else +for _,element in pairs(self.elements)do +local los=checklos(element) +if los then +return true +end +end +return false +end +return nil +end +function OPSGROUP:GetGroup() +return self.group +end +function OPSGROUP:GetName() +return self.groupname +end +function OPSGROUP:GetDCSGroup() +local DCSGroup=Group.getByName(self.groupname) +return DCSGroup +end +function OPSGROUP:GetUnit(UnitNumber) +local DCSUnit=self:GetDCSUnit(UnitNumber) +if DCSUnit then +local unit=UNIT:Find(DCSUnit) +return unit +end +return nil +end +function OPSGROUP:GetDCSUnit(UnitNumber) +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +local unit=DCSGroup:getUnit(UnitNumber or 1) +return unit +end +return nil +end +function OPSGROUP:GetDCSUnits() +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +local units=DCSGroup:getUnits() +return units +end +return nil +end +function OPSGROUP:Despawn(Delay,NoEventRemoveUnit) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.Despawn,self,0,NoEventRemoveUnit) +else +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +DCSGroup:destroy() +if not NoEventRemoveUnit then +local units=self:GetDCSUnits() +local EventTime=timer.getTime() +for i=1,#units do +self:CreateEventRemoveUnit(EventTime,units[i]) +end +end +end +end +return self +end +function OPSGROUP:Destroy(Delay) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.Destroy,self) +else +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +self:T(self.lid.."Destroying group") +DCSGroup:destroy() +local units=self:GetDCSUnits() +local EventTime=timer.getTime() +for i=1,#units do +if self.isAircraft then +self:CreateEventUnitLost(EventTime,units[i]) +else +self:CreateEventDead(EventTime,units[i]) +end +end +end +end +return self +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:GetVec2() +local vec3=self:GetVec3() +if vec3 then +local vec2={x=vec3.x,y=vec3.z} +return vec2 +end +return nil +end +function OPSGROUP:GetVec3() +if self:IsExist()then +local unit=self:GetDCSUnit() +if unit then +local vec3=unit:getPoint() +return vec3 +end +end +return nil +end +function OPSGROUP:GetCoordinate(NewObject) +local vec3=self:GetVec3() +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:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") +end +return nil +end +function OPSGROUP:GetVelocity() +if self:IsExist()then +local unit=self:GetDCSUnit(1) +if unit then +local velvec3=unit:getVelocity() +local vel=UTILS.VecNorm(velvec3) +return vel +end +else +self:E(self.lid.."WARNING: Group does not exist. Cannot get velocity!") +end +return nil +end +function OPSGROUP:GetHeading() +if self:IsExist()then +local unit=self:GetDCSUnit() +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:E(self.lid.."WARNING: Group does not exist. Cannot get heading!") +end +return nil +end +function OPSGROUP:GetOrientation() +if self:IsExist()then +local unit=self:GetDCSUnit() +if unit then +local pos=unit:getPosition() +return pos.x,pos.y,pos.z +end +else +self:E(self.lid.."WARNING: Group does not exist. Cannot get orientation!") +end +return nil +end +function OPSGROUP:GetOrientationX() +local X,Y,Z=self:GetOrientation() +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: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:E(self.lid.."WARNING: Activating group that is already activated") +else +self:E(self.lid.."ERROR: Activating group that is does not exist!") +end +end +return self +end +function OPSGROUP:SelfDestruction(Delay,ExplosionPower) +if Delay and Delay>0 then +self:ScheduleOnce(Delay,OPSGROUP.SelfDestruction,self,0,ExplosionPower) +else +for i,_element in pairs(self.elements)do +local element=_element +local unit=element.unit +if unit and unit:IsAlive()then +unit:Explode(ExplosionPower) +end +end +end +end +function OPSGROUP:IsExist() +local DCSGroup=self:GetDCSGroup() +if DCSGroup then +local exists=DCSGroup:isExist() +return exists +end +return nil +end +function OPSGROUP:IsActive() +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() +return self:Is("InUtero") +end +function OPSGROUP:IsSpawned() +return self:Is("Spawned") +end +function OPSGROUP:IsDead() +return self:Is("Dead") +end +function OPSGROUP:IsStopped() +return self:Is("Stopped") +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:IsLasing() +return self.spot.On +end +function OPSGROUP:IsRetreating() +return self:is("Retreating") +end +function OPSGROUP:IsEngaging() +return self:is("Engaging") +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),"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: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.1 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()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 N=#self.waypoints +local wp=self:GetWaypoint(wpindex) +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 index %d, current wp index %d. N %d-->%d",wpindex,self.currentwp,N,n)) +if wpindex>self.currentwp then +if self.currentwp>=n then +self.passedfinalwp=true +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 +end +end +return self +end +function OPSGROUP:SetTask(DCSTask) +if self:IsAlive()then +if self.taskcurrent>0 then +end +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.group: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 +self.group: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:ClearTasks() +if self:IsAlive()then +self.group:ClearTasks() +self:I(self.lid..string.format("CLEARING Tasks")) +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}) +self:__UpdateRoute(-1) +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 +table.insert(self.taskenroute,task) +end +end +function OPSGROUP:GetTasksWaypoint(id) +local tasks={} +self:_SortTaskQueue() +for _,_task in pairs(self.taskqueue)do +local task=_task +if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then +table.insert(tasks,task) +end +end +return tasks +end +function OPSGROUP:CountTasksWaypoint(id) +local n=0 +for _,_task in pairs(self.taskqueue)do +local task=_task +if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then +n=n+1 +end +end +return n +end +function OPSGROUP:_SortTaskQueue() +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio=task.time then +return task +end +end +return nil +end +function OPSGROUP:GetTaskCurrent() +local task=self:GetTaskByID(self.taskcurrent,OPSGROUP.TaskStatus.EXECUTING) +return task +end +function OPSGROUP:GetTaskByID(id,status) +for _,_task in pairs(self.taskqueue)do +local task=_task +if task.id==id then +if status==nil or status==task.status then +return task +end +end +end +return nil +end +function OPSGROUP:onafterTaskExecute(From,Event,To,Task) +local text=string.format("Task %s ID=%d execute",tostring(Task.description),Task.id) +self:T(self.lid..text) +if self.taskcurrent>0 then +self:TaskCancel() +end +self.taskcurrent=Task.id +Task.timestamp=timer.getAbsTime() +Task.status=OPSGROUP.TaskStatus.EXECUTING +if Task.dcstask.id=="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,"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=="PatrolZone"then +local zone=Task.dcstask.params.zone +local Coordinate=zone:GetRandomCoordinate() +local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) +local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude)or nil +if self.isFlightgroup then +FLIGHTGROUP.AddWaypoint(self,Coordinate,Speed,AfterWaypointWithID,Altitude) +elseif self.isNavygroup then +ARMYGROUP.AddWaypoint(self,Coordinate,Speed,AfterWaypointWithID,Formation) +elseif self.isArmygroup then +NAVYGROUP.AddWaypoint(self,Coordinate,Speed,AfterWaypointWithID,Altitude) +end +else +if Task.type==OPSGROUP.TaskType.SCHEDULED then +local DCStasks={} +if Task.dcstask.id=='ComboTask'then +for TaskID,Task in ipairs(Task.dcstask.params.tasks)do +table.insert(DCStasks,Task) +end +else +table.insert(DCStasks,Task.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}) +self:SetTask(TaskFinal) +end +end +local Mission=self:GetMissionByTaskID(self.taskcurrent) +if Mission then +self:MissionExecute(Mission) +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=="Formation"then +Task.formation:Stop() +done=true +elseif Task.dcstask.id=="PatrolZone"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:E(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 +self:T(self.lid.."Task Done ==> Mission Done!") +self:MissionDone(Mission) +else +end +else +if Task.description=="Engage_Target"then +self:Disengage() +end +self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") +self:_CheckGroupDone(1) +end +end +function OPSGROUP:AddMission(Mission) +Mission:AddOpsGroup(self) +Mission:SetGroupStatus(self,AUFTRAG.GroupStatus.SCHEDULED) +Mission:Scheduled() +Mission.Nelements=Mission.Nelements+#self.elements +table.insert(self.missionqueue,Mission) +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,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission.auftragsnummer==Mission.auftragsnummer then +local Task=Mission:GetGroupWaypointTask(self) +if Task then +self:RemoveTask(Task) +end +table.remove(self.missionqueue,i) +return self +end +end +return self +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:_GetNextMission() +local Nmissions=#self.missionqueue +if Nmissions==0 then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio0 then +self:ScheduleOnce(delay,OPSGROUP.RouteToMission,self,mission) +else +if self:IsDead()then +return +end +local uid=self:GetWaypointCurrent().uid +local waypointcoord=mission:GetMissionWaypointCoord(self.group) +for _,task in pairs(mission.enrouteTasks)do +self:AddTaskEnroute(task) +end +local SpeedToMission=UTILS.KmphToKnots(self.speedCruise) +if mission.type==AUFTRAG.Type.TROOPTRANSPORT then +mission.DCStask=mission:GetDCSMissionTask(self.group) +for _,_group in pairs(mission.transportGroupSet.Set)do +local group=_group +if group and group:IsAlive()then +local DCSTask=group:TaskEmbarkToTransport(mission.transportPickup,500) +group:SetTask(DCSTask,5) +end +end +elseif mission.type==AUFTRAG.Type.ARTY then +local weapondata=self:GetWeaponData(mission.engageWeaponType) +if weapondata then +local targetcoord=mission:GetTargetCoordinate() +local heading=self:GetCoordinate():HeadingTo(targetcoord) +local dist=self:GetCoordinate():Get2DDistance(targetcoord) +if dist>weapondata.RangeMax then +local d=(dist-weapondata.RangeMax)*1.1 +waypointcoord=self:GetCoordinate():Translate(d,heading) +self:T(self.lid..string.format("Out of max range = %.1f km for weapon %d",weapondata.RangeMax/1000,mission.engageWeaponType)) +elseif dist0 then +for i,_task in pairs(tasks)do +local task=_task +text=text..string.format("\n[%d] %s",i,task.description) +end +else +text=text.." None" +end +self:T(self.lid..text) +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:onafterGotoWaypoint(From,Event,To,UID) +local n=self:GetWaypointIndex(UID) +if n then +if false then +local tasks=self:GetTasksWaypoint(n) +for _,_task in pairs(tasks)do +local task=_task +task.status=OPSGROUP.TaskStatus.SCHEDULED +end +end +local Speed=self:GetSpeedToWaypoint(n) +self:__UpdateRoute(-1,n,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:E(self.lid.."ERROR: No target provided for LASER!") +return false +end +local element=self:GetElementAlive() +if element then +self.spot.element=element +local offsetY=0 +if self.isGround or self.isNaval 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:I(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:E(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=1,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:E("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:E(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: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:_UpdateStatus(Element,OPSGROUP.ElementStatus.DEAD) +end +function OPSGROUP:onafterElementDead(From,Event,To,Element) +self:T(self.lid..string.format("Element dead %s at t=%.3f",Element.name,timer.getTime())) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.DEAD) +if self.spot.On and self.spot.element.name==Element.name then +self:LaserOff() +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 +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.waypoints=nil +self.groupinitialized=false +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:__Stop(-5) +end +function OPSGROUP:onafterStop(From,Event,To) +self.timerCheckZone:Stop() +self.timerQueueUpdate:Stop() +self.CallScheduler:Clear() +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:E(self.lid..text) +end +self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") +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.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) +if self:IsAlive()and self.isAI then +if delay and delay>0 then +self:ScheduleOnce(delay,self._CheckGroupDone,self) +else +if self:IsEngaging()then +self:UpdateRoute() +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:UpdateRoute(i,speed) +self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots",i,speed)) +else +self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) +self:__FullStop(-1) +end +else +if self.passedfinalwp then +self:__FullStop(-1) +self:T(self.lid..string.format("Passed final WP, adinfinitum=FALSE ==> Full Stop")) +else +if#self.waypoints>0 then +self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) +self:UpdateRoute() +else +self:E(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")then +return +end +local Tnow=timer.getTime() +local ExpectedSpeed=self:GetExpectedSpeed() +local speed=self:GetVelocity() +if speed<0.5 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>=10*60 then +self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec",speed,ExpectedSpeed,holdtime)) +end +end +end +function OPSGROUP:_CheckDamage() +self.life=0 +local damaged=false +for _,_element in pairs(self.elements)do +local element=_element +local life=element.unit:GetLife() +self.life=self.life+life +if life0 then +local ammo=self:GetAmmoTot() +if self:IsRearming()then +if ammo.Total==self.ammo.Total then +self:Rearmed() +end +end +if self.outofAmmo and ammo.Total>0 then +self.outofAmmo=false +end +if ammo.Total==0 and not self.outofAmmo then +self.outofAmmo=true +self:OutOfAmmo() +end +if self.outofGuns and ammo.Guns>0 then +self.outoffGuns=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.outoffRockets=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.outoffBombs=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.outoffMissiles=false +end +if ammo.Missiles==0 and self.ammo.Missiles>0 and not self.outofMissiles then +self.outofMissiles=true +self:OutOfMissiles() +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:_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 +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 id=%d",wpnumber,waypoint.uid)) +self.passedfinalwp=false +if self:IsHolding()then +self:Cruise() +end +end +function OPSGROUP:InitWaypoints() +self.waypoints0=self.group:GetTemplateRoutePoints() +self.waypoints={} +for index,wp in pairs(self.waypoints0)do +local coordinate=COORDINATE:New(wp.x,wp.alt,wp.y) +wp.speed=wp.speed or 0 +local speedknots=UTILS.MpsToKnots(wp.speed) +if index==1 then +self.speedWp=wp.speed +end +self:AddWaypoint(coordinate,speedknots,index-1,nil,false) +end +self:T(self.lid..string.format("Initializing %d waypoints",#self.waypoints)) +if#self.waypoints>0 then +if#self.waypoints==1 then +self.passedfinalwp=true +end +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 Tasks={} +local TaskRoute=self.group:TaskRoute(waypoints) +table.insert(Tasks,TaskRoute) +local TaskCombo=self.group:TaskCombo(Tasks) +if#Tasks>1 then +self:SetTask(TaskCombo) +else +self:SetTask(TaskRoute) +end +else +self:E(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(group,opsgroup,uid) +local waypoint=opsgroup:GetWaypointByID(uid) +if waypoint then +local currentwp=opsgroup.currentwp +opsgroup.currentwp=opsgroup:GetWaypointIndex(uid) +local wpnext=opsgroup:GetWaypointNext() +if wpnext then +if opsgroup.isGround then +opsgroup.formation=wpnext.action +end +opsgroup.speed=wpnext.speed +end +local text=string.format("Group passing waypoint uid=%d",uid) +opsgroup:T(opsgroup.lid..text) +if waypoint.astar then +opsgroup:RemoveWaypointByID(uid) +opsgroup:Cruise() +elseif waypoint.detour then +opsgroup:RemoveWaypointByID(uid) +if opsgroup:IsRearming()then +opsgroup:Rearming() +elseif opsgroup:IsRetreating()then +opsgroup:Retreated() +elseif opsgroup:IsEngaging()then +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") +end +end +else +if opsgroup.ispathfinding then +opsgroup.ispathfinding=false +end +waypoint.npassed=waypoint.npassed+1 +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:T3(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:E(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: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:E(self.lid.."WARNING: Cannot switch ROT! Group is not alive") +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:E("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:E(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: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.isAircraft 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.isAircraft 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:E(self.lid.."ERROR: Cound not set TACAN! Unit is not alive") +end +else +self:E(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: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:E(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.isAircraft 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:E(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.isAircraft 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:E(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.isAircraft then +self.group:SetOption(AI.Option.Air.id.FORMATION,Formation) +elseif self.isGround then +else +self:E(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 %d",self.option.Formation)) +end +return self +end +function OPSGROUP:SetDefaultCallsign(CallsignName,CallsignNumber) +self.callsignDefault={} +self.callsignDefault.NumberSquad=CallsignName +self.callsignDefault.NumberGroup=CallsignNumber or 1 +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) +else +end +return self +end +function OPSGROUP:_UpdatePosition() +if self:IsAlive()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() +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.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.SPAWNED then +if self:_AllSimilarStatus(newstatus)then +self:__Spawned(-0.5) +end +elseif newstatus==OPSGROUP.ElementStatus.PARKING then +if self:_AllSimilarStatus(newstatus)then +self:__Parking(-0.5) +end +elseif newstatus==OPSGROUP.ElementStatus.ENGINEON then +elseif newstatus==OPSGROUP.ElementStatus.TAXIING then +if self:_AllSimilarStatus(newstatus)then +self:__Taxiing(-0.5) +end +elseif newstatus==OPSGROUP.ElementStatus.TAKEOFF then +if self:_AllSimilarStatus(newstatus)then +self:__Takeoff(-0.5,airbase) +end +elseif newstatus==OPSGROUP.ElementStatus.AIRBORNE then +if self:_AllSimilarStatus(newstatus)then +self:__Airborne(-0.5) +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(-1) +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) +for _,_element in pairs(self.elements)do +local element=_element +if element.name==unitname then +return element +end +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)do +local unit=_unit +if unit and unit:IsAlive()~=nil 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 +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 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\n",Nammo,_weaponName) +elseif Category==Weapon.Category.ROCKET then +nrockets=nrockets+Nammo +text=text..string.format("- %d rockets of type %s\n",Nammo,_weaponName) +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 +nmissilesAG=nmissilesAG+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\n",Nammo,self:_MissileCategoryName(MissileCategory),_weaponName) +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 +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:_CoordinateFromObject(Object) +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") +return Object:GetCoordinate() +else +self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") +end +end +return nil +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=true, +outofAGMrtb=true, +squadron=nil, +flightcontrol=nil, +flaghold=nil, +Tholding=nil, +Tparking=nil, +menu=nil, +ishelo=nil, +RTBRecallCount=0, +} +FLIGHTGROUP.Attribute={ +TRANSPORTPLANE="TransportPlane", +AWACS="AWACS", +FIGHTER="Fighter", +BOMBER="Bomber", +TANKER="Tanker", +TRANSPORTHELO="TransportHelo", +ATTACKHELO="AttackHelo", +UAV="UAV", +OTHER="Other", +} +FLIGHTGROUP.version="0.6.1" +function FLIGHTGROUP:New(group) +local fg=_DATABASE:GetFlightGroup(group) +if fg then +fg:I(fg.lid..string.format("WARNING: Flight group already exists in data base!")) +return fg +end +local self=BASE:Inherit(self,OPSGROUP:New(group)) +self.lid=string.format("FLIGHTGROUP %s | ",self.groupname) +self:SetFuelLowThreshold() +self:SetFuelLowRTB() +self:SetFuelCriticalThreshold() +self:SetFuelCriticalRTB() +self:SetDefaultROE() +self:SetDefaultROT() +self:SetDetection() +self.isFlightgroup=true +self.flaghold=USERFLAG:New(string.format("%s_FlagHold",self.groupname)) +self.flaghold:Set(0) +self:AddTransition("*","RTB","Inbound") +self:AddTransition("*","RTZ","Inbound") +self:AddTransition("Inbound","Holding","Holding") +self:AddTransition("*","Refuel","Going4Fuel") +self:AddTransition("Going4Fuel","Refueled","Airborne") +self:AddTransition("*","LandAt","LandingAt") +self:AddTransition("LandingAt","LandedAt","LandedAt") +self:AddTransition("*","Wait","*") +self:AddTransition("*","FuelLow","*") +self:AddTransition("*","FuelCritical","*") +self:AddTransition("*","OutOfMissilesAA","*") +self:AddTransition("*","OutOfMissilesAG","*") +self:AddTransition("*","OutOfMissilesAS","*") +self:AddTransition("Airborne","EngageTarget","Engaging") +self:AddTransition("Engaging","Disengage","Airborne") +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("*","Landing","Landing") +self:AddTransition("*","Landed","Landed") +self:AddTransition("*","Arrived","Arrived") +if false then +BASE:TraceOnOff(true) +BASE:TraceClass(self.ClassName) +BASE:TraceLevel(1) +end +_DATABASE:AddFlightGroup(self) +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:InitWaypoints() +self:_InitGroup() +self:__Status(-1) +self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) +self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(3,10) +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:SetAirwing(airwing) +self:T(self.lid..string.format("Add flight to AIRWING %s",airwing.alias)) +self.airwing=airwing +return self +end +function FLIGHTGROUP:GetAirWing() +return self.airwing +end +function FLIGHTGROUP:SetFlightControl(flightcontrol) +if self.flightcontrol then +if self.flightcontrol.airbasename==flightcontrol.airbasename then +return +else +self.flightcontrol:_RemoveFlight(self) +end +end +self:I(self.lid..string.format("Setting FLIGHTCONTROL to airbase %s",flightcontrol.airbasename)) +self.flightcontrol=flightcontrol +table.insert(flightcontrol.flights,self) +if self.isAI==false then +self:_UpdateMenu(0.5) +end +return self +end +function FLIGHTGROUP:GetFlightControl() +return self.flightcontrol +end +function FLIGHTGROUP:SetHomebase(HomeAirbase) +self.homebase=HomeAirbase +return self +end +function FLIGHTGROUP:SetDestinationbase(DestinationAirbase) +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: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:SetDetection(true) +return self +end +function FLIGHTGROUP:SetEngageDetectedOff() +self.engagedetectedOn=false +return self +end +function FLIGHTGROUP:SetDespawnAfterLanding() +self.despawnAfterLanding=true +return self +end +function FLIGHTGROUP:IsParking() +return self:Is("Parking") +end +function FLIGHTGROUP:IsTaxiing() +return self:Is("Taxiing") +end +function FLIGHTGROUP:IsAirborne() +return self:Is("Airborne") +end +function FLIGHTGROUP:IsWaiting() +return self:Is("Waiting") +end +function FLIGHTGROUP:IsLanding() +return self:Is("Landing") +end +function FLIGHTGROUP:IsLanded() +return self:Is("Landed") +end +function FLIGHTGROUP:IsArrived() +return self:Is("Arrived") +end +function FLIGHTGROUP:IsInbound() +return self:Is("Inbound") +end +function FLIGHTGROUP:IsHolding() +return self:Is("Holding") +end +function FLIGHTGROUP:IsGoing4Fuel() +return self:Is("Going4Fuel") +end +function FLIGHTGROUP:IsLandingAt() +return self:Is("LandingAt") +end +function FLIGHTGROUP:IsLandedAt() +return self:Is("LandedAt") +end +function FLIGHTGROUP:IsFuelLow() +return self.fuellow +end +function FLIGHTGROUP:IsFuelCritical() +return self.fuelcritical +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 +if self:IsAlive()then +self:T(self.lid.."Starting uncontrolled group") +self.group:StartUncontrolled(delay) +self.isUncontrolled=true +else +self:E(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) +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 false")) +return false +elseif self:IsStopped()then +self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) +return false +end +return true +end +function FLIGHTGROUP:onafterStatus(From,Event,To) +local fsmstate=self:GetState() +self:_UpdatePosition() +if self.detectionOn then +self:_CheckDetectedUnits() +end +if self:IsParking()then +for _,_element in pairs(self.elements)do +local element=_element +if element.parking then +local dist=element.unit:GetCoordinate():Get2DDistance(element.parking.Coordinate) +if dist>10 then +if element.status==OPSGROUP.ElementStatus.ENGINEON then +self:ElementTaxiing(element) +end +end +else +end +end +end +if self.verbose>=1 then +local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Curr=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s", +fsmstate,#self.elements,#self.elements,nTaskTot,nTaskSched,nTaskWP,self.taskcurrent,nMissions,self.currentwp or 0,self.waypoints and#self.waypoints or 0, +self.detectedunits:Count(),self.homebase and self.homebase:GetName()or"unknown",self.destbase and self.destbase:GetName()or"unknown") +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 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, guns=%d, rockets=%d, bombs=%d, missiles=%d (AA=%d, AG=%d, AS=%s), parking=%s", +i,name,status,fuel*100,life*100,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 self:IsAlive()then +local ds=self.travelds +local dt=self.dTpositionUpdate +local v=ds/dt +local TmaxFuel=math.huge +for _,_element in pairs(self.elements)do +local element=_element +local fuel=element.unit:GetFuel()or 0 +local dFrel=element.fuelrel-fuel +local dFreldt=dFrel/dt +local Tfuel=fuel/dFreldt +if Tfuel Tfuel=%.1f min",element.name,fuel*100,dFrel*100,dFreldt*100*60,Tfuel/60)) +element.fuelrel=fuel +end +self:I(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 +self:_PrintTaskAndMissionStatus() +if self:IsAlive()and self.group:IsAirborne(true)then +local fuelmin=self:GetFuelMin() +if fuelmin>=self.fuellowthresh then +self.fuellow=false +end +if fuelmin>=self.fuelcriticalthresh then +self.fuelcritical=false +end +if fuelmin spawned",element.name,self.homebase and self.homebase:GetName()or"unknown")) +self:__ElementSpawned(0.0,element) +end +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)) +if self.isAI then +self:ElementEngineOn(element) +else +if element.ai then +self:ElementEngineOn(element) +end +end +end +end +end +end +function FLIGHTGROUP:OnEventTakeOff(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 +self:T3(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 +if element.unit and element.unit:IsAlive()then +local airbase=self:GetClosestAirbase() +local parking=self:GetParkingSpot(element,10,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: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 FLIGHTGROUP:OnEventRemoveUnit(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 +self:T3(self.lid..string.format("EVENT: Element %s removed ==> dead",element.name)) +self:ElementDead(element) +end +end +end +function FLIGHTGROUP:onafterElementSpawned(From,Event,To,Element) +self:T(self.lid..string.format("Element spawned %s",Element.name)) +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.SPAWNED) +if Element.unit:InAir(true)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) +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 Spot then +self:_SetElementParkingAt(Element,Spot) +end +if self:IsTakeoffCold()then +elseif self:IsTakeoffHot()then +self:__ElementEngineOn(0.5,Element) +elseif self:IsTakeoffRunway()then +self:__ElementEngineOn(0.5,Element) +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(2,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")) +if self.despawnAfterLanding then +self:DespawnElement(Element) +else +if self.ishelo then +local Spot=self:GetParkingSpot(Element,10,airbase) +self:_SetElementParkingAt(Element,Spot) +end +self:_UpdateStatus(Element,OPSGROUP.ElementStatus.LANDED,airbase) +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) +self:GetParent(self).onafterElementDead(self,From,Event,To,Element) +if self.flightcontrol and Element.parking then +self.flightcontrol:SetParkingFree(Element.parking) +end +Element.parking=nil +end +function FLIGHTGROUP:onafterSpawned(From,Event,To) +self:T(self.lid..string.format("Flight spawned")) +self:_UpdatePosition() +if self.isAI then +self:SwitchROE(self.option.ROE) +self:SwitchROT(self.option.ROT) +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,true) +self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB,true) +self:GetGroup():SetOption(AI.Option.Air.id.RTB_ON_BINGO,false) +self:__UpdateRoute(-0.5) +else +self:_UpdateMenu() +end +end +function FLIGHTGROUP:onafterParking(From,Event,To) +self:T(self.lid..string.format("Flight is parking")) +local airbase=self:GetClosestAirbase() +local airbasename=airbase:GetName()or"unknown" +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) +if not self.isAI then +self:_UpdateMenu(0.5) +end +end +end +end +function FLIGHTGROUP:onafterTaxiing(From,Event,To) +self:T(self.lid..string.format("Flight is taxiing")) +self.Tparking=nil +local airbase=self:GetClosestAirbase() +if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName()then +if self.isAI then +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAKEOFF) +else +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.TAXIOUT) +self:_UpdateMenu() +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")) +if self.isAI then +self:_CheckGroupDone(1) +else +self:_UpdateMenu() +end +end +function FLIGHTGROUP:onafterLanding(From,Event,To) +self:T(self.lid..string.format("Flight is landing")) +self:_SetElementStatusAll(OPSGROUP.ElementStatus.LANDING) +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 airbase and self.flightcontrol.airbasename==airbase:GetName()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")) +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.airwing then +self:Despawn(5*60) +end +end +function FLIGHTGROUP:onafterDead(From,Event,To) +if self.flightcontrol then +self.flightcontrol:_RemoveFlight(self) +self.flightcontrol=nil +end +if self.Ndestroyed==#self.elements then +if self.squadron then +self.squadron:DelGroup(self.groupname) +end +else +if self.airwing then +self.airwing:AddAsset(self.group,1) +end +end +self:GetParent(self).onafterDead(self,From,Event,To) +end +function FLIGHTGROUP:onbeforeUpdateRoute(From,Event,To,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:E(self.lid.."Update route denied. Group is DEAD!") +allowed=false +else +self:T(self.lid.."Update route denied ==> checking back in 5 sec") +trepeat=-5 +allowed=false +end +if n and n<1 then +self:E(self.lid.."Update route denied because waypoint n<1!") +allowed=false +end +if not self.currentwp then +self:E(self.lid.."Update route denied because self.currentwp=nil!") +allowed=false +end +local N=n or self.currentwp+1 +if not N or N<1 then +self:E(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=="PatrolZone"then +else +local taskname=task and task.description or"No description" +self:E(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 allowed=%s state=%s repeat in %s",tostring(allowed),self:GetState(),tostring(trepeat))) +if trepeat then +self:__UpdateRoute(trepeat,n) +end +return allowed +end +function FLIGHTGROUP:onafterUpdateRoute(From,Event,To,n) +n=n or self.currentwp+1 +self:_UpdateWaypointTasks(n) +local wp={} +local speed=self.group and self.group:GetVelocityKMH()or 100 +local current=self.group:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,speed,true,nil,{},"Current") +table.insert(wp,current) +local Nwp=self.waypoints and#self.waypoints or 0 +for i=n,Nwp do +table.insert(wp,self.waypoints[i]) +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 homebase=%s destination=%s",n,#wp,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:onafterRespawn(From,Event,To,Template) +self:T(self.lid.."Respawning group!") +local template=UTILS.DeepCopy(Template or self.template) +if self.group and self.group:InAir()then +template.lateActivation=false +self.respawning=true +self.group=self.group:Respawn(template) +end +end +function FLIGHTGROUP:onafterOutOfMissilesAA(From,Event,To) +self:I(self.lid.."Group is out of AA Missiles!") +if self.outofAAMrtb then +local airbase=self.destbase or self.homebase +self:__RTB(-5,airbase) +end +end +function FLIGHTGROUP:onafterOutOfMissilesAG(From,Event,To) +self:I(self.lid.."Group is out of AG Missiles!") +if self.outofAGMrtb then +local airbase=self.destbase or self.homebase +self:__RTB(-5,airbase) +end +end +function FLIGHTGROUP:_CheckGroupDone(delay) +if self:IsAlive()and self.isAI then +if delay and delay>0 then +self:ScheduleOnce(delay,FLIGHTGROUP._CheckGroupDone,self) +else +if self.missionpaused then +self:UnpauseMission() +return +end +if self:IsEngaging()then +return +end +local nTasks=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +if self.passedfinalwp then +if self.currentmission==nil and self.taskcurrent==0 then +if nTasks==0 and nMissions==0 then +local destbase=self.destbase or self.homebase +local destzone=self.destzone or self.homezone +if destbase then +self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!") +self:__RTB(-3,destbase) +elseif destzone then +self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTZ!") +self:__RTZ(-3,destzone) +else +self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") +self:__Wait(-1) +end +else +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 +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",self:GetState())) +self:__UpdateRoute(-1) +end +end +end +end +function FLIGHTGROUP:onbeforeRTB(From,Event,To,airbase,SpeedTo,SpeedHold) +if self:IsAlive()then +local allowed=true +local Tsuspend=nil +if airbase==nil then +self:E(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:E(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())) +allowed=false +end +if not self.group:IsAirborne(true)then +self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 20 sec.")) +allowed=false +Tsuspend=-20 +local groupspeed=self.group:GetVelocityMPS() +if groupspeed<=1 then self.RTBRecallCount=self.RTBRecallCount+1 end +if self.RTBRecallCount>6 then +self:Despawn(5) +end +end +if not(self:IsFuelLow()or self:IsFuelCritical())then +local Ntot,Nsched,Nwp=self:CountRemainingTasks() +if self.taskcurrent>0 then +self:I(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:I(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:I(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 +end +if Tsuspend and not allowed then +self:__RTB(Tsuspend,airbase,SpeedTo,SpeedHold) +end +return allowed +else +self:E(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.Tholding=nil +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 +local text=string.format("Canceling mission %s in state=%s",mission.name,mission.status) +self:T(self.lid..text) +self:MissionCancel(mission) +end +end +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) +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.group:GetCoordinate() +local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) +local p1=nil +local wpap=nil +local fc=_DATABASE:GetFlightControl(airbase:GetName()) +if fc then +local HoldingPoint=fc:_GetHoldingpoint(self) +p0=HoldingPoint.pos0 +p1=HoldingPoint.pos1 +if self.Debug then +p0:MarkToAll("Holding point P0") +p1:MarkToAll("Holding point P1") +end +self:SetFlightControl(fc) +self.flightcontrol:SetFlightStatus(self,FLIGHTCONTROL.FlightStatus.INBOUND) +end +local x1=self.ishelo and UTILS.NMToMeters(5.0)or UTILS.NMToMeters(10) +local x2=self.ishelo and UTILS.NMToMeters(2.5)or UTILS.NMToMeters(5) +local alpha=math.rad(3) +local h1=x1*math.tan(alpha) +local h2=x2*math.tan(alpha) +local runway=airbase:GetActiveRunway() +self.flaghold:Set(0) +local holdtime=5*60 +if fc or self.airboss then +holdtime=nil +end +local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding",self) +local TaskOrbit=self.group:TaskOrbit(p0,nil,UTILS.KnotsToMps(SpeedHold),p1) +local TaskLand=self.group:TaskCondition(nil,self.flaghold.UserFlagName,1,nil,holdtime) +local TaskHold=self.group:TaskControlled(TaskOrbit,TaskLand) +local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand",self) +local wp={} +wp[#wp+1]=c0:WaypointAir(nil,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{},"Current Pos") +wp[#wp+1]=p0:WaypointAir(nil,COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,UTILS.KnotsToKmph(SpeedTo),true,nil,{TaskArrived,TaskHold,TaskKlar},"Holding Point") +if airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME then +local papp=airbase:GetCoordinate():Translate(x1,runway.heading-180):SetAltitude(h1) +wp[#wp+1]=papp:WaypointAirTurningPoint(nil,UTILS.KnotsToKmph(SpeedLand),{},"Final Approach") +local pland=airbase:GetCoordinate():Translate(x2,runway.heading-180):SetAltitude(h2) +wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing") +elseif airbase:GetAirbaseCategory()==Airbase.Category.SHIP then +local pland=airbase:GetCoordinate() +wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand),airbase,{},"Landing") +end +if self.isAI then +local routeto=false +if fc or world.event.S_EVENT_KILL then +routeto=true +end +if routeto then +self:Route(wp,1) +else +local Template=self.group:GetTemplate() +Template.route.points=wp +self:Respawn(Template) +end +end +end +function FLIGHTGROUP:onbeforeWait(From,Event,To,Coord,Altitude,Speed) +local allowed=true +local Tsuspend=nil +local Ntot,Nsched,Nwp=self:CountRemainingTasks() +if self.taskcurrent>0 then +self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 10 sec.")) +Tsuspend=-10 +allowed=false +end +if Nsched>0 then +self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.",Nsched)) +Tsuspend=-10 +allowed=false +end +if Nwp>0 then +self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.",Nwp)) +Tsuspend=-10 +allowed=false +end +if Tsuspend and not allowed then +self:__Wait(Tsuspend,Coord,Altitude,Speed) +end +return allowed +end +function FLIGHTGROUP:onafterWait(From,Event,To,Coord,Altitude,Speed) +Coord=Coord or self.group:GetCoordinate() +Altitude=Altitude or(self.ishelo and 1000 or 10000) +Speed=Speed or(self.ishelo and 80 or 250) +local text=string.format("Flight group set to wait/orbit at altitude %d m and speed %.1f km/h",Altitude,Speed) +self:T(self.lid..text) +local TaskOrbit=self.group:TaskOrbit(Coord,UTILS.FeetToMeters(Altitude),UTILS.KnotsToMps(Speed)) +self:SetTask(TaskOrbit) +end +function FLIGHTGROUP:onafterRefuel(From,Event,To,Coordinate) +local text=string.format("Flight group set to refuel at the nearest tanker") +self:I(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.group:GetCoordinate() +Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5),self.group:GetHeading(),true) +local wp0=coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true) +local wp9=Coordinate:WaypointAir("BARO",COORDINATE.WaypointType.TurningPoint,COORDINATE.WaypointAction.TurningPoint,Speed,true,nil,DCSTasks,"Refuel") +self:Route({wp0,wp9},1) +end +function FLIGHTGROUP:onafterRefueled(From,Event,To) +local text=string.format("Flight group finished refuelling") +self:I(self.lid..text) +self:_CheckGroupDone(1) +end +function FLIGHTGROUP:onafterHolding(From,Event,To) +self.flaghold:Set(0) +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 not self.isAI then +self:_UpdateMenu() +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:E("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) +Duration=Duration or 600 +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 text=string.format("Low fuel for flight group %s",self.groupname) +self:I(self.lid..text) +self.fuellow=true +local airbase=self.destbase or self.homebase +if self.airwing then +local tanker=self.airwing:GetTankerForFlight(self) +if tanker then +self:I(self.lid..string.format("Send to refuel at tanker %s",tanker.flightgroup:GetName())) +local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker.flightgroup:GetCoordinate(),0.75) +self:Refuel(coordinate) +else +if airbase and self.fuellowrtb then +self:RTB(airbase) +end +end +else +if self.fuellowrefuel and self.refueltype then +local tanker=self:FindNearestTanker(50) +if tanker then +self:I(self.lid..string.format("Send to refuel at tanker %s",tanker:GetName())) +local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker:GetCoordinate(),0.75) +self:Refuel(coordinate) +return +end +end +if airbase and self.fuellowrtb then +self:RTB(airbase) +end +end +end +function FLIGHTGROUP:onafterFuelCritical(From,Event,To) +local text=string.format("Critical fuel for flight group %s",self.groupname) +self:I(self.lid..text) +self.fuelcritical=true +local airbase=self.destbase or self.homebase +if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel()then +self:RTB(airbase) +end +end +function FLIGHTGROUP:onafterStop(From,Event,To) +if self:IsAlive()then +if self.flightcontrol then +for _,_element in pairs(self.elements)do +local element=_element +self:_SetElementParkingFree(element) +end +end +end +self:UnHandleEvent(EVENTS.Birth) +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:UnHandleEvent(EVENTS.RemoveUnit) +self:GetParent(self).onafterStop(self,From,Event,To) +_DATABASE.FLIGHTGROUPS[self.groupname]=nil +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._FinishedRefuelling(group,flightgroup) +flightgroup:T2(flightgroup.lid..string.format("Group finished refueling")) +flightgroup:__Refueled(-1) +end +function FLIGHTGROUP:_InitGroup() +if self.groupinitialized then +self:E(self.lid.."WARNING: Group was already initialized!") +return +end +local group=self.group +self.template=group:GetTemplate() +self.isAircraft=true +self.isNaval=false +self.isGround=false +self.ishelo=group:IsHelicopter() +self.isUncontrolled=self.template.uncontrolled +self.isLateActivated=self.template.lateActivation +self.speedMax=group:GetSpeedMax() +local speedCruiseLimit=self.ishelo and UTILS.KnotsToKmph(80)or UTILS.KnotsToKmph(350) +self.speedCruise=math.min(self.speedMax*0.7,speedCruiseLimit) +self.ammo=self:GetAmmoTot() +self.radio.Freq=tonumber(self.template.frequency) +self.radio.Modu=tonumber(self.template.modulation) +self.radio.On=self.template.communication +local callsign=self.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=callsign[1] +self.callsign.NumberGroup=callsign[2] +self.callsign.NumberElement=callsign[3] +self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) +if self.ishelo then +self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 +else +self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group +end +self:SetDefaultTACAN(nil,nil,nil,nil,true) +self.tacan=UTILS.DeepCopy(self.tacanDefault) +self.isAI=not self:_IsHuman(group) +if not self.isAI then +self.menu=self.menu or{} +self.menu.atc=self.menu.atc or{} +self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group,"ATC") +end +for _,unit in pairs(self.group:GetUnits())do +local element=self:AddElementByName(unit:GetName()) +end +local unit=self.group:GetUnit(1) +if unit then +self.rangemax=unit:GetRange() +self.descriptors=unit:GetDesc() +self.actype=unit:GetTypeName() +self.ceiling=self.descriptors.Hmax +self.tankertype=select(2,unit:IsTanker()) +self.refueltype=select(2,unit:IsRefuelable()) +if self.verbose>=1 then +local text=string.format("Initialized Flight Group %s:\n",self.groupname) +text=text..string.format("Unit type = %s\n",self.actype) +text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) +text=text..string.format("Range max = %.1f km\n",self.rangemax/1000) +text=text..string.format("Ceiling = %.1f feet\n",UTILS.MetersToFeet(self.ceiling)) +text=text..string.format("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("Helicopter = %s\n",tostring(self.group:IsHelicopter())) +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())) +self:I(self.lid..text) +end +self.groupinitialized=true +end +return self +end +function FLIGHTGROUP:AddElementByName(unitname) +local unit=UNIT:FindByName(unitname) +if unit then +local element={} +element.name=unitname +element.unit=unit +element.status=OPSGROUP.ElementStatus.INUTERO +element.group=unit:GetGroup() +local unittemplate=element.unit:GetTemplate() +element.modex=unittemplate.onboard_num +element.skill=unittemplate.skill +element.payload=unittemplate.payload +element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil +element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 +element.fuelmass=element.fuelmass0 +element.fuelrel=element.unit:GetFuel() +element.category=element.unit:GetUnitCategory() +element.categoryname=element.unit:GetCategoryName() +element.callsign=element.unit:GetCallsign() +element.size=element.unit:GetObjectSize() +if element.skill=="Client"or element.skill=="Player"then +element.ai=false +element.client=CLIENT:FindByName(unitname) +else +element.ai=true +end +local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", +element.name,element.status,element.skill,element.modex,element.fuelmass,element.fuelrel*100,element.category,element.categoryname,element.callsign,tostring(element.ai)) +self:T(self.lid..text) +table.insert(self.elements,element) +if unit:IsAlive()then +self:ElementSpawned(element) +end +return element +end +return nil +end +function FLIGHTGROUP:GetHomebaseFromWaypoints() +local wp=self:GetWaypoint(1) +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 %s Destination",#self.waypoints,self.homebase and self.homebase:GetName()or"unknown",self.destbase and self.destbase:GetName()or"uknown")) +if#self.waypoints>0 then +if#self.waypoints==1 then +self.passedfinalwp=true +end +end +return self +end +function FLIGHTGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Altitude,Updateroute) +local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) +if wpnumber>self.currentwp then +self.passedfinalwp=false +end +Speed=Speed or 350 +local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO,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(-1) +end +return waypoint +end +function FLIGHTGROUP:_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 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)) +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:GetParkingSpotsTable() +local spot=nil +local dist=nil +local distmin=math.huge +for _,_parking in pairs(parking)do +local parking=_parking +dist=coord:Get2DDistance(parking.Coordinate) +if distsafedist) +return safe +end +local function _clients() +local clients=_DATABASE.CLIENTS +local coords={} +for clientname,client in pairs(clients)do +local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) +local units=template.units +for i,unit in pairs(units)do +local coord=COORDINATE:New(unit.x,unit.alt,unit.y) +coords[unit.name]=coord +end +end +return coords +end +local airbasecategory=airbase:GetAirbaseCategory() +local parkingdata=airbase:GetParkingSpotsTable() +local obstacles={} +for _,_parkingspot in pairs(parkingdata)do +local parkingspot=_parkingspot +local _,_,_,_units,_statics,_sceneries=parkingspot.Coordinate:ScanObjects(scanradius,scanunits,scanstatics,scanscenery) +for _,_unit in pairs(_units)do +local unit=_unit +local _coord=unit:GetCoordinate() +local _size=self:_GetObjectSize(unit:GetDCSObject()) +local _name=unit:GetName() +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="unit"}) +end +local clientcoords=_clients() +for clientname,_coord in pairs(clientcoords)do +table.insert(obstacles,{coord=_coord,size=15,name=clientname,type="client"}) +end +for _,static in pairs(_statics)do +local _vec3=static:getPoint() +local _coord=COORDINATE:NewFromVec3(_vec3) +local _name=static:getName() +local _size=self:_GetObjectSize(static) +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="static"}) +end +for _,scenery in pairs(_sceneries)do +local _vec3=scenery:getPoint() +local _coord=COORDINATE:NewFromVec3(_vec3) +local _name=scenery:getTypeName() +local _size=self:_GetObjectSize(scenery) +table.insert(obstacles,{coord=_coord,size=_size,name=_name,type="scenery"}) +end +end +local parking={} +local terminaltype=self:_GetTerminal(self.attribute,airbase:GetAirbaseCategory()) +for i,_element in pairs(self.elements)do +local element=_element +local gotit=false +for _,_parkingspot in pairs(parkingdata)do +local parkingspot=_parkingspot +if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype)then +local free=true +local problem=nil +if verysafe and parkingspot.TOAC then +free=false +self:T2(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).",parkingspot.TerminalID)) +end +for _,obstacle in pairs(obstacles)do +local dist=parkingspot.Coordinate:Get2DDistance(obstacle.coord) +local safe=_overlap(element.size,obstacle.size,dist) +if not safe then +free=false +problem=obstacle +problem.dist=dist +break +end +end +if self.flightcontrol and self.flightcontrol.airbasename==airbase:GetName()then +local problem=self.flightcontrol:IsParkingReserved(parkingspot)or self.flightcontrol:IsParkingOccupied(parkingspot) +if problem then +free=false +end +end +if free then +table.insert(parking,parkingspot) +self:T2(self.lid..string.format("Parking spot %d is free for element %s!",parkingspot.TerminalID,element.name)) +table.insert(obstacles,{coord=parkingspot.Coordinate,size=element.size,name=element.name,type="element"}) +gotit=true +break +else +self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!",parkingspot.TerminalID)) +end +end +end +if not gotit then +self:E(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 then +_terminal=AIRBASE.TerminalType.FighterAircraft +elseif _attribute==FLIGHTGROUP.Attribute.AIR_BOMBER or _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTPLANE or _attribute==FLIGHTGROUP.Attribute.AIR_TANKER or _attribute==FLIGHTGROUP.Attribute.AIR_AWACS then +_terminal=AIRBASE.TerminalType.OpenBig +elseif _attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO then +_terminal=AIRBASE.TerminalType.HelicopterUsable +else +end +if _category==Airbase.Category.SHIP then +if not(_attribute==FLIGHTGROUP.Attribute.AIR_TRANSPORTHELO or _attribute==FLIGHTGROUP.Attribute.AIR_ATTACKHELO)then +_terminal=AIRBASE.TerminalType.OpenMedOrBig +end +end +return _terminal +end +function FLIGHTGROUP:_UpdateMenu(delay) +if delay and delay>0 then +self:I(self.lid..string.format("FF updating menu in %.1f sec",delay)) +self:ScheduleOnce(delay,FLIGHTGROUP._UpdateMenu,self) +else +self:I(self.lid.."FF updating menu NOW") +local position=self.group:GetCoordinate() +local fc={} +for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS)do +local airbase=AIRBASE:FindByName(airbasename) +local coord=airbase:GetCoordinate() +local dist=coord:Get2DDistance(position) +local fcitem={airbasename=airbasename,dist=dist} +table.insert(fc,fcitem) +end +local function _sort(a,b) +return a.distTstop then +self:E(string.format("ERROR:Into wind stop time %s lies before start time %s. Input rejected!",UTILS.SecondsToClock(Tstart),UTILS.SecondsToClock(Tstop))) +return self +end +if Tstop<=Tnow then +self:E(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.",UTILS.SecondsToClock(Tstop),UTILS.SecondsToClock(Tnow))) +return self +end +self.intowindcounter=self.intowindcounter+1 +local recovery={} +recovery.Tstart=Tstart +recovery.Tstop=Tstop +recovery.Open=false +recovery.Over=false +recovery.Speed=speed or 20 +recovery.Uturn=uturn and uturn or false +recovery.Offset=offset or 0 +recovery.Id=self.intowindcounter +return recovery +end +function NAVYGROUP:AddTurnIntoWind(starttime,stoptime,speed,uturn,offset) +local recovery=self:_CreateTurnIntoWind(starttime,stoptime,speed,uturn,offset) +table.insert(self.Qintowind,recovery) +return recovery +end +function NAVYGROUP:RemoveTurnIntoWind(IntoWindData) +if self.intowind and self.intowind.Id==IntoWindData.Id then +self:TurnIntoWindStop() +return +end +for i,_tiw in pairs(self.Qintowind)do +local tiw=_tiw +if tiw.Id==IntoWindData.Id then +table.remove(self.Qintowind,i) +break +end +end +return self +end +function NAVYGROUP:IsHolding() +return self:Is("Holding") +end +function NAVYGROUP:IsCruising() +return self:Is("Cruising") +end +function NAVYGROUP:IsOnDetour() +return self:Is("OnDetour") +end +function NAVYGROUP:IsDiving() +return self:Is("Diving") +end +function NAVYGROUP:IsTurning() +return self.turning +end +function NAVYGROUP:IsSteamingIntoWind() +if self.intowind then +return true +else +return false +end +end +function NAVYGROUP:onbeforeStatus(From,Event,To) +if self:IsDead()then +self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) +return false +elseif self:IsStopped()then +self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) +return false +end +return true +end +function NAVYGROUP:onafterStatus(From,Event,To) +local fsmstate=self:GetState() +if self:IsAlive()then +if self.detectionOn then +self:_CheckDetectedUnits() +end +self:_UpdatePosition() +self:_CheckTurning() +local freepath=UTILS.NMToMeters(10) +if not self:IsTurning()then +freepath=self:_CheckFreePath(freepath,100) +if freepath<5000 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:_CheckStuck() +if self.verbose>=1 then +local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local intowind=self:IsSteamingIntoWind()and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(),true)or"N/A" +local turning=tostring(self:IsTurning()) +local alt=self.position.y +local speed=UTILS.MpsToKnots(self.velocity) +local speedExpected=UTILS.MpsToKnots(self:GetExpectedSpeed()) +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 wpDist=UTILS.MetersToNM(self:GetDistanceToWaypoint()or 0) +local wpETA=UTILS.SecondsToClock(self:GetTimeToWaypoint()or 0,true) +local roe=self:GetROE()or 0 +local als=self:GetAlarmstate()or 0 +local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] (of %d) Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", +fsmstate,roe,als,nTaskTot,nMissions,wpidxCurr,wpuidCurr,wpidxNext,wpuidNext,#self.waypoints or 0,wpDist,wpETA,speed,speedExpected,alt,self.heading,turning,freepath,intowind) +self:I(self.lid..text) +if false then +local text="Waypoints:" +for i,wp in pairs(self.waypoints)do +local waypoint=wp +text=text..string.format("\n%d. UID=%d",i,waypoint.uid) +if i==self.currentwp then +text=text.." current!" +end +end +env.info(text) +end +end +else +local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive())) +self:T(self.lid..text) +end +if self.verbose>=2 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 +self:_PrintTaskAndMissionStatus() +self:__Status(-30) +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!")) +self:_UpdatePosition() +if self.isAI then +self:SwitchROE(self.option.ROE) +self:SwitchAlarmstate(self.option.Alarm) +self:_SwitchTACAN() +self:_SwitchICLS() +if self.radioDefault then +self:SwitchRadio() +else +self:SetDefaultRadio(self.radio.Freq,self.radio.Modu,false) +end +end +if#self.waypoints>1 then +self:Cruise() +else +self:FullStop() +end +end +function NAVYGROUP:onafterUpdateRoute(From,Event,To,n,Speed,Depth) +n=n or self:GetWaypointIndexNext() +self:_UpdateWaypointTasks(n) +local waypoints={} +local wp=UTILS.DeepCopy(self.waypoints[n]) +if Speed then +wp.speed=UTILS.KnotsToMps(Speed) +else +if self.adinfinitum and 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 +end +self.speedWp=wp.speed +table.insert(waypoints,wp) +local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp),wp.alt) +table.insert(waypoints,1,current) +if not self.passedfinalwp then +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),wp.alt)) +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) +IntoWind.Heading=self:GetHeadingIntoWind(IntoWind.Offset) +IntoWind.Open=true +IntoWind.Coordinate=self:GetCoordinate() +self.intowind=IntoWind +local _,vwind=self:GetWind() +vwind=UTILS.MpsToKnots(vwind) +local speed=math.max(IntoWind.Speed-vwind,2) +self:T(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f Vwind=%.1f Vtot=%.1f knots, Tstart=%d Tstop=%d",IntoWind.Heading,speed,vwind,speed+vwind,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 self.Debug 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!") +self:Detour(self.intowind.Coordinate,self:GetSpeedCruise(),0,true) +else +local indx=self:GetWaypointIndexNext() +local speed=self:GetWaypointSpeed(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,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.depth=nil +self:__UpdateRoute(-1,nil,Speed) +end +function NAVYGROUP:onafterDive(From,Event,To,Depth,Speed) +Depth=Depth or 50 +self:T(self.lid..string.format("Diving to %d meters",Depth)) +self.depth=Depth +self:__UpdateRoute(-1,nil,Speed) +end +function NAVYGROUP:onafterSurface(From,Event,To,Speed) +self.depth=0 +self:__UpdateRoute(-1,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:onafterStop(From,Event,To) +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.Dead) +self:UnHandleEvent(EVENTS.RemoveUnit) +self:GetParent(self).onafterStop(self,From,Event,To) +end +function NAVYGROUP: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.respawning then +local function reset() +self.respawning=nil +end +self:ScheduleOnce(1,reset) +else +local element=self:GetElementByName(unitname) +self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned",element.name)) +self:ElementSpawned(element) +end +end +end +function NAVYGROUP:OnEventDead(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +self:T(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 then +self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed",element.name)) +self:ElementDestroyed(element) +end +end +end +function NAVYGROUP:OnEventRemoveUnit(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 +self:T(self.lid..string.format("EVENT: Element %s removed ==> dead",element.name)) +self:ElementDead(element) +end +end +end +function NAVYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Depth,Updateroute) +if not Coordinate:IsInstanceOf("COORDINATE")then +if Coordinate:IsInstanceOf("POSITIONABLE")or Coordinate:IsInstanceOf("ZONE_BASE")then +self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") +Coordinate=Coordinate:GetCoordinate() +else +self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") +return nil +end +end +local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) +if wpnumber>self.currentwp then +self.passedfinalwp=false +end +Speed=Speed or self:GetSpeedCruise() +local wp=Coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed),Depth) +local waypoint=self:_CreateWaypoint(wp) +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:_CheckGroupDone(1) +end +return waypoint +end +function NAVYGROUP:_InitGroup() +if self.groupinitialized then +self:E(self.lid.."WARNING: Group was already initialized!") +return +end +self.template=self.group:GetTemplate() +self.isAircraft=false +self.isNaval=true +self.isGround=false +self.isAI=true +self.isLateActivated=self.template.lateActivation +self.isUncontrolled=false +self.speedMax=self.group:GetSpeedMax() +self.speedCruise=self.speedMax*0.7 +self.ammo=self:GetAmmoTot() +self.radio.On=true +self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 +self.radio.Modu=tonumber(self.template.units[1].modulation) +self.optionDefault.Formation="Off Road" +self.option.Formation=self.optionDefault.Formation +self:SetDefaultTACAN(nil,nil,nil,nil,true) +self.tacan=UTILS.DeepCopy(self.tacanDefault) +self:SetDefaultICLS(nil,nil,nil,true) +self.icls=UTILS.DeepCopy(self.iclsDefault) +local units=self.group:GetUnits() +for _,_unit in pairs(units)do +local unit=_unit +local unittemplate=unit:GetTemplate() +local element={} +element.name=unit:GetName() +element.unit=unit +element.status=OPSGROUP.ElementStatus.INUTERO +element.typename=unit:GetTypeName() +element.skill=unittemplate.skill or"Unknown" +element.ai=true +element.category=element.unit:GetUnitCategory() +element.categoryname=element.unit:GetCategoryName() +element.size,element.length,element.height,element.width=unit:GetObjectSize() +element.ammo0=self:GetAmmoUnit(unit,false) +if self.verbose>=2 then +local text=string.format("Adding element %s: status=%s, skill=%s, category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)", +element.name,element.status,element.skill,element.categoryname,element.category,element.size,element.length,element.height,element.width) +self:I(self.lid..text) +end +table.insert(self.elements,element) +self.descriptors=self.descriptors or unit:GetDesc() +self.actype=self.actype or unit:GetTypeName() +if unit:IsAlive()then +self:ElementSpawned(element) +end +end +if self.verbose>=1 then +local text=string.format("Initialized Navy Group %s:\n",self.groupname) +text=text..string.format("Unit type = %s\n",self.actype) +text=text..string.format("Speed max = %.1f Knots\n",UTILS.KmphToKnots(self.speedMax)) +text=text..string.format("Speed cruise = %.1f Knots\n",UTILS.KmphToKnots(self.speedCruise)) +text=text..string.format("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.groupinitialized=true +return self +end +function NAVYGROUP:_CheckFreePath(DistanceMax,dx) +local distance=DistanceMax or 5000 +local dx=dx or 100 +if self:IsTurning()then +return distance +end +local offsetY=0.1 +if UTILS.GetDCSMap()==DCSMAP.Caucasus then +offsetY=5.01 +end +local vec3=self:GetVec3() +vec3.y=offsetY +local heading=self:GetHeading() +local function LoS(dist) +local checkvec3=UTILS.VecTranslate(vec3,dist,heading) +local los=land.isVisible(vec3,checkvec3) +return los +end +if LoS(DistanceMax)then +return DistanceMax +end +local function check() +local xmin=0 +local xmax=DistanceMax +local Nmax=100 +local eps=100 +local N=1 +while N<=Nmax do +local d=xmax-xmin +local x=xmin+d/2 +local los=LoS(x) +self:T2(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 +return check() +end +function NAVYGROUP:_CheckTurning() +local unit=self.group:GetUnit(1) +if unit and unit:IsAlive()then +local vNew=self.orientX +local vLast=self.orientXLast +vNew.y=0;vLast.y=0 +local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) +local turning=math.abs(deltaLast)>=2 +if self.turning and not turning then +self:TurningStopped() +elseif turning and not self.turning then +self:TurningStarted() +end +self.turning=turning +end +end +function NAVYGROUP:_CheckTurnsIntoWind() +local time=timer.getAbsTime() +if self.intowind then +if time>=self.intowind.Tstop then +self:TurnIntoWindOver(self.intowind) +end +else +local IntoWind=self:GetTurnIntoWindNext() +if IntoWind then +self:TurnIntoWind(IntoWind) +end +end +end +function NAVYGROUP:GetTurnIntoWindNext() +if#self.Qintowind>0 then +local time=timer.getAbsTime() +table.sort(self.Qintowind,function(a,b)return a.Tstart=recovery.Tstart and time0 +else +return false +end +end +return findpath() +end +ARMYGROUP={ +ClassName="ARMYGROUP", +formationPerma=nil, +engage={}, +} +ARMYGROUP.version="0.4.0" +function ARMYGROUP:New(Group) +local self=BASE:Inherit(self,OPSGROUP:New(Group)) +self.lid=string.format("ARMYGROUP %s | ",self.groupname) +self.isArmygroup=true +self:SetDefaultROE() +self:SetDefaultAlarmstate() +self:SetDetection() +self:SetPatrolAdInfinitum(false) +self:SetRetreatZones() +self:AddTransition("*","FullStop","Holding") +self:AddTransition("*","Cruise","Cruising") +self:AddTransition("*","Detour","OnDetour") +self:AddTransition("OnDetour","DetourReached","Cruising") +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("*","Rearm","Rearm") +self:AddTransition("Rearm","Rearming","Rearming") +self:AddTransition("Rearming","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:__Status(-1) +self.timerQueueUpdate=TIMER:New(self._QueueUpdate,self):Start(2,5) +self.timerCheckZone=TIMER:New(self._CheckInZones,self):Start(2,30) +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() +return self:GetCoordinate():GetClosestPointToRoad() +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: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: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: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.outofAmmo or self:IsEngaging()or self:is("Retreated")or self:IsDead()or self:IsStopped()or self:IsInUtero()then +combatready=false +end +return combatready +end +function ARMYGROUP:onbeforeStatus(From,Event,To) +if self:IsDead()then +self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) +return false +elseif self:IsStopped()then +self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) +return false +end +return true +end +function ARMYGROUP:onafterStatus(From,Event,To) +local fsmstate=self:GetState() +if self:IsAlive()then +if self.detectionOn then +self:_CheckDetectedUnits() +end +self:_CheckAmmoStatus() +self:_UpdatePosition() +self:_CheckStuck() +self:_CheckDamage() +if self:IsEngaging()then +self:_UpdateEngageTarget() +end +if self.verbose>=1 then +local nTaskTot,nTaskSched,nTaskWP=self:CountRemainingTasks() +local nMissions=self:CountRemainingMissison() +local roe=self:GetROE() +local alarm=self:GetAlarmstate() +local speed=UTILS.MpsToKnots(self.velocity) +local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) +local formation=self.option.Formation or"unknown" +local ammo=self:GetAmmoTot() +local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d", +fsmstate,roe,alarm,nTaskTot,nMissions,self.currentwp,#self.waypoints,self:GetWaypointIndexNext(),tostring(self.passedfinalwp),self.life or 0,speed,speedEx,self.heading,ammo.Total) +self:I(self.lid..text) +end +else +local text=string.format("State %s: Alive=%s",fsmstate,tostring(self:IsAlive())) +self:T2(self.lid..text) +end +self:_PrintTaskAndMissionStatus() +self:__Status(-30) +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!")) +self:_UpdatePosition() +if self.isAI then +self:SwitchROE(self.option.ROE) +self:SwitchAlarmstate(self.option.Alarm) +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 +self.option.Formation=self.optionDefault.Formation +end +end +if#self.waypoints>1 then +self:Cruise(nil,self.option.Formation or self.optionDefault.Formation) +else +self:FullStop() +end +end +function ARMYGROUP:onafterUpdateRoute(From,Event,To,n,Speed,Formation) +local text=string.format("Update route n=%s, Speed=%s, Formation=%s",tostring(n),tostring(Speed),tostring(Formation)) +self:T(self.lid..text) +n=n or self:GetWaypointIndexNext(self.adinfinitum) +self:_UpdateWaypointTasks(n) +local waypoints={} +local wp=UTILS.DeepCopy(self.waypoints[n]) +local onroad=wp.action==ENUMS.Formation.Vehicle.OnRoad +if Speed then +wp.speed=UTILS.KnotsToMps(Speed) +else +if self.adinfinitum and 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 +self.option.Formation=wp.action +self.speedWp=wp.speed +if onroad then +wp.action=ENUMS.Formation.Vehicle.OffRoad +local wproad=wp.roadcoord:WaypointGround(wp.speed,ENUMS.Formation.Vehicle.OnRoad) +table.insert(waypoints,wproad) +end +table.insert(waypoints,wp) +local formation=ENUMS.Formation.Vehicle.OffRoad +if wp.action~=ENUMS.Formation.Vehicle.OnRoad then +formation=wp.action +end +local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speedWp),formation) +table.insert(waypoints,1,current) +if onroad then +local current=self:GetClosestRoad():WaypointGround(UTILS.MpsToKmph(self.speedWp),ENUMS.Formation.Vehicle.OnRoad) +table.insert(waypoints,2,current) +end +if false then +for i,_wp in pairs(waypoints)do +local wp=_wp +local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s",i,wp.uid and wp.uid or 0,wp.type,wp.speed,wp.alt,wp.action) +self:T(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:E(self.lid..string.format("WARNING: Passed final WP ==> Full Stop!")) +self:FullStop() +end +end +function ARMYGROUP:onafterGotoWaypoint(From,Event,To,UID,Speed,Formation) +local n=self:GetWaypointIndex(UID) +if n then +if false then +local tasks=self:GetTasksWaypoint(n) +for _,_task in pairs(tasks)do +local task=_task +task.status=OPSGROUP.TaskStatus.SCHEDULED +end +end +Speed=Speed or self:GetSpeedToWaypoint(n) +self:UpdateRoute(n,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:GetWaypointCurrent().uid +local wp=self:AddWaypoint(Coordinate,Speed,uid,Formation,true) +if ResumeRoute then +wp.detour=1 +else +wp.detour=0 +end +end +function ARMYGROUP:onafterRearm(From,Event,To,Coordinate,Formation) +local uid=self:GetWaypointCurrent().uid +local wp=self:AddWaypoint(Coordinate,nil,uid,Formation,true) +wp.detour=0 +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 dist100 then +self.engage.Coordinate:UpdateFromVec3(vec3) +local uid=self:GetWaypointCurrent().uid +self:RemoveWaypointByID(self.engage.Waypoint.uid) +self.engage.Waypoint=self:AddWaypoint(self.engage.Coordinate,nil,uid,Formation,true) +self.engage.Waypoint.detour=0 +end +else +self:Disengage() +end +end +function ARMYGROUP:onafterDisengage(From,Event,To) +self:_CheckGroupDone(1) +end +function ARMYGROUP:onafterRearmed(From,Event,To) +self:_CheckGroupDone(1) +end +function ARMYGROUP:onafterDetourReached(From,Event,To) +self:I(self.lid.."Group reached detour coordinate.") +end +function ARMYGROUP:onafterFullStop(From,Event,To) +local pos=self:GetCoordinate() +local wp=pos:WaypointGround(0) +self:Route({wp}) +end +function ARMYGROUP:onafterCruise(From,Event,To,Speed,Formation) +self:__UpdateRoute(-1,nil,Speed,Formation) +end +function ARMYGROUP:onafterStop(From,Event,To) +self:UnHandleEvent(EVENTS.Birth) +self:UnHandleEvent(EVENTS.Dead) +self:UnHandleEvent(EVENTS.RemoveUnit) +self:GetParent(self).onafterStop(self,From,Event,To) +end +function ARMYGROUP: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.respawning then +local function reset() +self.respawning=nil +end +self:ScheduleOnce(1,reset) +else +local element=self:GetElementByName(unitname) +self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned",element.name)) +self:ElementSpawned(element) +end +end +end +function ARMYGROUP:OnEventDead(EventData) +if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then +self:T(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 then +self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed",element.name)) +self:ElementDestroyed(element) +end +end +end +function ARMYGROUP:OnEventRemoveUnit(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 +self:T(self.lid..string.format("EVENT: Element %s removed ==> dead",element.name)) +self:ElementDead(element) +end +end +end +function ARMYGROUP:OnEventHit(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 +end +end +function ARMYGROUP:AddWaypoint(Coordinate,Speed,AfterWaypointWithID,Formation,Updateroute) +local coordinate=self:_CoordinateFromObject(Coordinate) +local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) +if wpnumber>self.currentwp then +self.passedfinalwp=false +end +Speed=Speed or self:GetSpeedCruise() +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:_CheckGroupDone(1) +end +return waypoint +end +function ARMYGROUP:_InitGroup() +if self.groupinitialized then +self:E(self.lid.."WARNING: Group was already initialized!") +return +end +self.template=self.group:GetTemplate() +self.isAircraft=false +self.isNaval=false +self.isGround=true +self.isAI=true +self.isLateActivated=self.template.lateActivation +self.isUncontrolled=false +self.speedMax=self.group:GetSpeedMax() +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.optionDefault.Formation=self:GetWaypoint(1).action +self:SetDefaultTACAN(nil,nil,nil,nil,true) +self.tacan=UTILS.DeepCopy(self.tacanDefault) +local units=self.group:GetUnits() +for _,_unit in pairs(units)do +local unit=_unit +local unittemplate=unit:GetTemplate() +local element={} +element.name=unit:GetName() +element.unit=unit +element.status=OPSGROUP.ElementStatus.INUTERO +element.typename=unit:GetTypeName() +element.skill=unittemplate.skill or"Unknown" +element.ai=true +element.category=element.unit:GetUnitCategory() +element.categoryname=element.unit:GetCategoryName() +element.size,element.length,element.height,element.width=unit:GetObjectSize() +element.ammo0=self:GetAmmoUnit(unit,false) +element.life0=unit:GetLife0() +element.life=element.life0 +if self.verbose>=2 then +local text=string.format("Adding element %s: status=%s, skill=%s, life=%.3f category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)", +element.name,element.status,element.skill,element.life,element.categoryname,element.category,element.size,element.length,element.height,element.width) +self:I(self.lid..text) +end +table.insert(self.elements,element) +self.descriptors=self.descriptors or unit:GetDesc() +self.actype=self.actype or unit:GetTypeName() +if unit:IsAlive()then +self:ElementSpawned(element) +end +end +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("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.groupinitialized=true +return self +end +function ARMYGROUP:SwitchFormation(Formation,Permanently,NoRouteUpdate) +if self:IsAlive()or self:IsInUtero()then +Formation=Formation or self.optionDefault.Formation +if Permanently then +self.formationPerma=Formation +else +self.formationPerma=nil +end +self.option.Formation=Formation +if self:IsInUtero()then +self:T(self.lid..string.format("Will switch formation to %s (permanently=%s) when group is spawned",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)",self.option.Formation,tostring(Permanently))) +end +end +return self +end +SQUADRON={ +ClassName="SQUADRON", +verbose=0, +lid=nil, +name=nil, +templatename=nil, +aircrafttype=nil, +assets={}, +missiontypes={}, +repairtime=0, +maintenancetime=0, +livery=nil, +skill=nil, +modex=nil, +modexcounter=0, +callsignName=nil, +callsigncounter=11, +airwing=nil, +Ngroups=nil, +engageRange=nil, +tankerSystem=nil, +refuelSystem=nil, +tacanChannel={}, +} +SQUADRON.version="0.5.0" +function SQUADRON:New(TemplateGroupName,Ngroups,SquadronName) +local self=BASE:Inherit(self,FSM:New()) +self.templatename=TemplateGroupName +self.name=tostring(SquadronName or TemplateGroupName) +self.lid=string.format("SQUADRON %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.Ngroups=Ngroups or 3 +self:SetMissionRange() +self:SetSkill(AI.Skill.GOOD) +self:AddMissionCapability(AUFTRAG.Type.ORBIT) +self.attribute=self.templategroup:GetAttribute() +self.aircrafttype=self.templategroup:GetTypeName() +self.refuelSystem=select(2,self.templategroup:GetUnit(1):IsRefuelable()) +self.tankerSystem=select(2,self.templategroup:GetUnit(1):IsTanker()) +self:SetStartState("Stopped") +self:AddTransition("Stopped","Start","OnDuty") +self:AddTransition("*","Status","*") +self:AddTransition("OnDuty","Pause","Paused") +self:AddTransition("Paused","Unpause","OnDuty") +self:AddTransition("*","Stop","Stopped") +if false then +BASE:TraceOnOff(true) +BASE:TraceClass(self.ClassName) +BASE:TraceLevel(1) +end +return self +end +function SQUADRON:SetLivery(LiveryName) +self.livery=LiveryName +return self +end +function SQUADRON:SetSkill(Skill) +self.skill=Skill +return self +end +function SQUADRON:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +end +function SQUADRON:SetTurnoverTime(MaintenanceTime,RepairTime) +self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 +self.repairtime=RepairTime and RepairTime*60 or 0 +return self +end +function SQUADRON:SetRadio(Frequency,Modulation) +self.radioFreq=Frequency or 251 +self.radioModu=Modulation or radio.modulation.AM +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:AddMissionCapability(MissionTypes,Performance) +if MissionTypes and type(MissionTypes)~="table"then +MissionTypes={MissionTypes} +end +self.missiontypes=self.missiontypes or{} +for _,missiontype in pairs(MissionTypes)do +if self:CheckMissionCapability(missiontype,self.missiontypes)then +self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice.") +else +local capability={} +capability.MissionType=missiontype +capability.Performance=Performance or 50 +table.insert(self.missiontypes,capability) +end +end +self:T2(self.missiontypes) +return self +end +function SQUADRON:GetMissionTypes() +local missiontypes={} +for _,Capability in pairs(self.missiontypes)do +local capability=Capability +table.insert(missiontypes,capability.MissionType) +end +return missiontypes +end +function SQUADRON:GetMissionCapabilities() +return self.missiontypes +end +function SQUADRON: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 SQUADRON:SetMissionRange(Range) +self.engageRange=UTILS.NMToMeters(Range or 100) +return self +end +function SQUADRON:SetCallsign(Callsign,Index) +self.callsignName=Callsign +self.callsignIndex=Index +return self +end +function SQUADRON:SetModex(Modex,Prefix,Suffix) +self.modex=Modex +self.modexPrefix=Prefix +self.modexSuffix=Suffix +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.airwing=Airwing +return self +end +function SQUADRON:AddAsset(Asset) +self:T(self.lid..string.format("Adding asset %s of type %s",Asset.spawngroupname,Asset.unittype)) +Asset.squadname=self.name +table.insert(self.assets,Asset) +return self +end +function SQUADRON: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 SQUADRON: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 SQUADRON:GetName() +return self.name +end +function SQUADRON:GetRadio() +return self.radioFreq,self.radioModu +end +function SQUADRON: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 SQUADRON: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 SQUADRON: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 SQUADRON:FetchTacan() +for channel,free in pairs(self.tacanChannel)do +if free then +self:T(self.lid..string.format("Checking out Tacan channel %d",channel)) +self.tacanChannel[channel]=false +return channel +end +end +return nil +end +function SQUADRON:ReturnTacan(channel) +self:T(self.lid..string.format("Returning Tacan channel %d",channel)) +self.tacanChannel[channel]=true +end +function SQUADRON:IsOnDuty() +return self:Is("OnDuty") +end +function SQUADRON:IsStopped() +return self:Is("Stopped") +end +function SQUADRON:IsPaused() +return self:Is("Paused") +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:CountAssetsInStock() +local NassetsQP=0;local NassetsP=0;local NassetsQ=0 +if self.airwing then +NassetsQP,NassetsP,NassetsQ=self.airwing: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 +function SQUADRON:_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.airwing and self.airwing: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() +local fuelmin=asset.flightgroup:GetFuelMin() +local fuellow=asset.flightgroup:IsFuelLow() +local fuelcri=asset.flightgroup:IsFuelCritical() +text=text..string.format("%s Fuel=%d",status,fuelmin) +if fuelcri then +text=text.." (Critical!)" +elseif fuellow then +text=text.." (Low)" +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 +local payload=asset.payload and table.concat(self.airwing:GetPayloadMissionTypes(asset.payload),", ")or"None" +text=text..", Payload={"..payload.."}" +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:I(self.lid..text) +end +end +function SQUADRON:onafterStop(From,Event,To) +self:I(self.lid.."STOPPING Squadron!") +for i=#self.assets,1,-1 do +local asset=self.assets[i] +self:DelAsset(asset) +end +self.CallScheduler:Clear() +end +function SQUADRON:CanMission(Mission) +local cando=true +if not self:IsOnDuty()then +self:T(self.lid..string.format("Squad 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 self:CheckMissionType(Mission.type,self:GetMissionTypes())then +self:T(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)",Mission.type,Mission.name,Mission:GetTargetName())) +return false +end +if Mission.type==AUFTRAG.Type.TANKER then +if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then +else +self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available",tostring(Mission.refuelSystem),tostring(self.tankerSystem))) +return false +end +end +local TargetDistance=Mission:GetTargetDistance(self.airwing:GetCoordinate()) +local engagerange=Mission.engageRange and math.max(self.engageRange,Mission.engageRange)or self.engageRange +if TargetDistance>engagerange then +self:I(self.lid..string.format("INFO: Squad 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 SQUADRON:CountAssetsInStock() +local N=0 +for _,_asset in pairs(self.assets)do +local asset=_asset +if asset.spawned then +else +N=N+1 +end +end +return N +end +function SQUADRON:RecruitAssets(Mission,Npayloads) +Npayloads=Npayloads or self.airwing:CountPayloadsInStock(Mission.type,self.aircrafttype,Mission.payloads) +local assets={} +for _,_asset in pairs(self.assets)do +local asset=_asset +if self.airwing:IsAssetOnMission(asset)then +if self.airwing:IsAssetOnMission(asset,AUFTRAG.Type.GCICAP)and Mission.type==AUFTRAG.Type.INTERCEPT then +self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") +table.insert(assets,asset) +end +else +if asset.spawned then +local flightgroup=asset.flightgroup +if self:CheckMissionCapability(Mission.type,asset.payload.capabilities)and flightgroup and flightgroup:IsAlive()then +local combatready=true +if Mission.type==AUFTRAG.Type.INTERCEPT then +combatready=flightgroup:CanAirToAir() +else +local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET or Mission.type==AUFTRAG.Type.SEAD or Mission.type==AUFTRAG.Type.ANTISHIP +combatready=flightgroup:CanAirToGround(excludeguns) +end +if flightgroup:IsFuelLow()then +combatready=false +end +if flightgroup:IsHolding()or flightgroup:IsLanding()or flightgroup:IsLanded()or flightgroup:IsArrived()or flightgroup:IsDead()or flightgroup:IsStopped()then +combatready=false +end +if combatready then +self:I(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)and(not asset.requested)then +table.insert(assets,asset) +Npayloads=Npayloads-1 +end +end +end +end +return assets +end +function SQUADRON: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 SQUADRON: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 SQUADRON: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 SQUADRON:CheckMissionCapability(MissionType,Capabilities) +for _,cap in pairs(Capabilities)do +local capability=cap +if capability.MissionType==MissionType then +return true +end +end +return false +end +AIRWING={ +ClassName="AIRWING", +verbose=0, +lid=nil, +menu=nil, +squadrons={}, +missionqueue={}, +payloads={}, +payloadcounter=0, +pointsCAP={}, +pointsTANKER={}, +pointsAWACS={}, +wingcommander=nil, +markpoints=false, +} +AIRWING.version="0.5.1" +function AIRWING:New(warehousename,airwingname) +local self=BASE:Inherit(self,WAREHOUSE: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:AddTransition("*","MissionRequest","*") +self:AddTransition("*","MissionCancel","*") +self:AddTransition("*","SquadAssetReturned","*") +self:AddTransition("*","FlightOnMission","*") +self.nflightsCAP=0 +self.nflightsAWACS=0 +self.nflightsTANKERboom=0 +self.nflightsTANKERprobe=0 +self.nflightsRecoveryTanker=0 +self.nflightsRescueHelo=0 +self.markpoints=false +return self +end +function AIRWING:AddSquadron(Squadron) +table.insert(self.squadrons,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 +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() +payload.unlimited=Npayloads<0 +if payload.unlimited then +payload.navail=1 +else +payload.navail=Npayloads or 99 +end +payload.capabilities={} +for _,missiontype in pairs(MissionTypes)do +local capability={} +capability.MissionType=missiontype +capability.Performance=Performance +table.insert(payload.capabilities,capability) +end +if not self:CheckMissionType(AUFTRAG.Type.ORBIT,MissionTypes)then +local capability={} +capability.MissionType=AUFTRAG.Type.ORBIT +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: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)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=self: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 X and aircraft type=Y:")) +for _,_payload in ipairs(self.payloads)do +local payload=_payload +if payload.aircrafttype==UnitType and self:CheckMissionCapability(MissionType,payload.capabilities)then +local performace=self:GetPayloadPeformance(payload,MissionType) +self:I(self.lid..string.format("FF %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.."Warning could not find a payload for airframe X mission type Y!") +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) +for _,_squadron in pairs(self.squadrons)do +local squadron=_squadron +if squadron.name==SquadronName then +return squadron +end +end +return nil +end +function AIRWING:SetVerbosity(VerbosityLevel) +self.verbose=VerbosityLevel or 0 +return self +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:AddMission(Mission) +Mission:Queued(self) +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 AIRWING:RemoveMission(Mission) +for i,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission.auftragsnummer==Mission.auftragsnummer then +table.remove(self.missionqueue,i) +break +end +end +return self +end +function AIRWING:SetNumberCAP(n) +self.nflightsCAP=n or 1 +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: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) +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 +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:AddPatrolPointTANKER(Coordinate,Altitude,Speed,Heading,LegLength) +local patrolpoint=self:NewPatrolPoint("Tanker",Coordinate,Altitude,Speed,Heading,LegLength) +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:onafterStart(From,Event,To) +self:GetParent(self).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() +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.squadrons,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) +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>=3 then +local text="Squadrons:" +for i,_squadron in pairs(self.squadrons)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:CountAssetsInStock(),#squadron.assets,callsign,modex,skill) +end +self:I(self.lid..text) +end +self:_CheckMissions() +local mission=self:_GetNextMission() +if mission then +self:MissionRequest(mission) +end +end +function AIRWING:_GetPatrolData(PatrolPoints) +local function sort(a,b) +return a.noccupied0 then +table.sort(PatrolPoints,sort) +return PatrolPoints[1] +else +return self:NewPatrolPoint() +end +end +function AIRWING:CheckCAP() +local Ncap=self:CountMissionsInQueue({AUFTRAG.Type.GCICAP,AUFTRAG.Type.INTERCEPT}) +for i=1,self.nflightsCAP-Ncap do +local patrol=self:_GetPatrolData(self.pointsCAP) +local altitude=patrol.altitude+1000*patrol.noccupied +local missionCAP=AUFTRAG:NewGCICAP(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg) +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: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 then +if mission.refuelSystem==0 then +Nboom=Nboom+1 +elseif mission.refuelSystem==1 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,1) +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,0) +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=self:CountMissionsInQueue({AUFTRAG.Type.AWACS}) +for i=1,self.nflightsAWACS-N do +local patrol=self:_GetPatrolData(self.pointsAWACS) +local altitude=patrol.altitude+1000*patrol.noccupied +local mission=AUFTRAG:NewAWACS(patrol.coord,altitude,patrol.speed,patrol.heading,patrol.leg) +mission.patroldata=patrol +patrol.noccupied=patrol.noccupied+1 +if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol)end +self:AddMission(mission) +end +return self +end +function AIRWING:CheckRescuhelo() +local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) +local name=self.airbase:GetName() +local carrier=UNIT:FindByName(name) +for i=1,self.nflightsRescueHelo-N do +local mission=AUFTRAG:NewRESCUEHELO(carrier) +self:AddMission(mission) +end +return self +end +function AIRWING:GetTankerForFlight(flightgroup) +local tankers=self:GetAssetsOnMission(AUFTRAG.Type.TANKER) +if#tankers>0 then +local tankeropt={} +for _,_tanker in pairs(tankers)do +local tanker=_tanker +if flightgroup.refueltype and flightgroup.refueltype==tanker.flightgroup.tankertype then +local tankercoord=tanker.flightgroup.group:GetCoordinate() +local assetcoord=flightgroup.group:GetCoordinate() +local dist=assetcoord:Get2DDistance(tankercoord) +if dist>5 then +table.insert(tankeropt,{tanker=tanker,dist=dist}) +end +end +end +table.sort(tankeropt,function(a,b)return a.dist0 then +return tankeropt[1].tanker +else +return nil +end +end +return nil +end +function AIRWING:_CheckMissions() +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and mission:IsReadyToCancel()then +mission:Cancel() +end +end +end +function AIRWING:_GetNextMission() +local Nmissions=#self.missionqueue +if Nmissions==0 then +return nil +end +local function _sort(a,b) +local taskA=a +local taskB=b +return(taskA.prio0 then +self:E(self.lid..string.format("ERROR: mission %s of type %s has already assets attached!",mission.name,mission.type)) +end +mission.assets={} +for i=1,mission.nassets do +local asset=assets[i] +if not asset.payload then +self:E(self.lid.."ERROR: No payload for asset! This should not happen!") +end +mission:AddAsset(asset) +end +for i=mission.nassets+1,#assets do +local asset=assets[i] +for _,uid in pairs(gotpayload)do +if uid==asset.uid then +self:ReturnPayloadFromAsset(asset) +break +end +end +end +return mission +end +end +end +return nil +end +function AIRWING:CalculateAssetMissionScore(asset,Mission,includePayload) +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 +local squad=self:GetSquadronOfAsset(asset) +local missionperformance=squad:GetMissionPeformance(Mission.type) +score=score+missionperformance +if includePayload and asset.payload then +score=score+self:GetPayloadPeformance(asset.payload,Mission.type) +end +if Mission.type==AUFTRAG.Type.INTERCEPT then +if asset.spawned then +self:T(self.lid.."Adding 25 to asset because it is spawned") +score=score+25 +end +end +return score +end +function AIRWING:_OptimizeAssetSelection(assets,Mission,includePayload) +local TargetVec2=Mission:GetTargetVec2() +local dStock=UTILS.VecDist2D(TargetVec2,self:GetVec2()) +local distmin=math.huge +local distmax=0 +for _,_asset in pairs(assets)do +local asset=_asset +if asset.spawned then +local group=GROUP:FindByName(asset.spawngroupname) +asset.dist=UTILS.VecDist2D(group:GetVec2(),TargetVec2) +else +asset.dist=dStock +end +if asset.distdistmax then +distmax=asset.dist +end +end +for _,_asset in pairs(assets)do +local asset=_asset +asset.score=self:CalculateAssetMissionScore(asset,Mission,includePayload) +end +local function optimize(a,b) +local assetA=a +local assetB=b +return(assetA.score>assetB.score)or(assetA.score==assetB.score and assetA.dist0 then +for i,_asset in pairs(Assetlist)do +local asset=_asset +asset.requested=true +if Mission.missionTask then +asset.missionTask=Mission.missionTask +end +end +self:AddRequest(self,WAREHOUSE.Descriptor.ASSETLIST,Assetlist,#Assetlist,nil,nil,Mission.prio,tostring(Mission.auftragsnummer)) +Mission.requestID=self.queueid +end +end +function AIRWING:onafterMissionCancel(From,Event,To,Mission) +self:I(self.lid..string.format("Cancel mission %s",Mission.name)) +local Ngroups=Mission:CountOpsGroups() +if Mission:IsPlanned()or Mission:IsQueued()or Mission:IsRequested()or Ngroups==0 then +Mission:Done() +else +for _,_asset in pairs(Mission.assets)do +local asset=_asset +local flightgroup=asset.flightgroup +if flightgroup then +flightgroup:MissionCancel(Mission) +end +asset.requested=nil +end +end +if Mission.requestID then +self:_DeleteQueueItemByID(Mission.requestID,self.queue) +end +end +function AIRWING:onafterNewAsset(From,Event,To,asset,assignment) +self:GetParent(self).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:T3(self.lid..text) +local squad=self:GetSquadron(asset.assignment) +if squad then +if asset.assignment==assignment then +local nunits=#asset.template.units +local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s",squad.name,assignment,asset.unittype,asset.attribute,nunits,tostring(squad.ngrouping)) +self:T(self.lid..text) +if squad.ngrouping then +local template=asset.template +local N=math.max(#template.units,squad.ngrouping) +for i=1,N do +local unit=template.units[i] +if i>nunits then +table.insert(template.units,UTILS.DeepCopy(template.units[1])) +end +if squad.ngroupingnunits then +unit=nil +end +end +asset.nunits=squad.ngrouping +end +squad:GetCallsign(asset) +squad:GetModex(asset) +asset.spawngroupname=string.format("%s_AID-%d",squad.name,asset.uid) +squad:AddAsset(asset) +else +self:SquadAssetReturned(squad,asset) +end +end +end +function AIRWING:onafterSquadAssetReturned(From,Event,To,Squadron,Asset) +self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"",Asset.spawngroupname,Squadron.name,tostring(Asset.assignment))) +if Asset.flightgroup and not Asset.flightgroup:IsStopped()then +Asset.flightgroup:Stop() +end +self:ReturnPayloadFromAsset(Asset) +if Asset.tacan then +Squadron:ReturnTacan(Asset.tacan) +end +Asset.Treturned=timer.getAbsTime() +end +function AIRWING:onafterAssetSpawned(From,Event,To,group,asset,request) +self:GetParent(self).onafterAssetSpawned(self,From,Event,To,group,asset,request) +local flightgroup=self:_CreateFlightGroup(asset) +asset.flightgroup=flightgroup +asset.requested=nil +asset.Treturned=nil +local squadron=self:GetSquadronOfAsset(asset) +local Tacan=squadron:FetchTacan() +if Tacan then +asset.tacan=Tacan +end +local radioFreq,radioModu=squadron:GetRadio() +if radioFreq then +flightgroup:SwitchRadio(radioFreq,radioModu) +end +if squadron.fuellow then +flightgroup:SetFuelLowThreshold(squadron.fuellow) +end +if squadron.fuellowRefuel then +flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) +end +local mission=self:GetMissionByID(request.assignment) +if mission then +if Tacan then +mission:SetTACAN(Tacan,Morse,UnitName,Band) +end +asset.flightgroup:AddMission(mission) +self:FlightOnMission(flightgroup,mission) +else +if Tacan then +flightgroup:SwitchTACAN(Tacan,Morse,UnitName,Band) +end +end +if self.wingcommander and self.wingcommander.chief then +self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) +end +end +function AIRWING:onafterAssetDead(From,Event,To,asset,request) +self:GetParent(self).onafterAssetDead(self,From,Event,To,asset,request) +if self.wingcommander and self.wingcommander.chief then +self.wingcommander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) +end +end +function AIRWING:onafterDestroyed(From,Event,To) +self:I(self.lid.."Airwing warehouse destroyed!") +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +mission:Cancel() +end +for _,_squadron in pairs(self.squadrons)do +local squadron=_squadron +squadron:Stop() +end +self:GetParent(self).onafterDestroyed(self,From,Event,To) +end +function AIRWING:onafterRequest(From,Event,To,Request) +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 +self:GetParent(self).onafterRequest(self,From,Event,To,Request) +end +function AIRWING:onafterSelfRequest(From,Event,To,groupset,request) +self:GetParent(self).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 AIRWING:_CreateFlightGroup(asset) +local flightgroup=FLIGHTGROUP:New(asset.spawngroupname) +flightgroup:SetAirwing(self) +flightgroup.squadron=self:GetSquadronOfAsset(asset) +flightgroup.homebase=self.airbase +return flightgroup +end +function AIRWING: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 self:CheckMissionType(mission.type,MissionTypes)then +return true +end +end +end +end +return false +end +function AIRWING:GetAssetCurrentMission(asset) +if asset.flightgroup then +return asset.flightgroup:GetMissionCurrent() +end +return nil +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=self: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:CountMissionsInQueue(MissionTypes) +MissionTypes=MissionTypes or AUFTRAG.Type +local N=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission:IsNotOver()and self:CheckMissionType(mission.type,MissionTypes)then +N=N+1 +end +end +return N +end +function AIRWING:CountAssets() +local N=0 +for _,_squad in pairs(self.squadrons)do +local squad=_squad +N=N+#squad.assets +end +return N +end +function AIRWING:CountAssetsOnMission(MissionTypes,Squadron) +local Nq=0 +local Np=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if self:CheckMissionType(mission.type,MissionTypes or AUFTRAG.Type)then +for _,_asset in pairs(mission.assets or{})do +local asset=_asset +if Squadron==nil or Squadron.name==asset.squadname then +local request,isqueued=self:GetRequestByID(mission.requestID) +if isqueued then +Nq=Nq+1 +else +Np=Np+1 +end +end +end +end +end +return Np+Nq,Np,Nq +end +function AIRWING:GetAssetsOnMission(MissionTypes) +local assets={} +local Np=0 +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if self: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 +function AIRWING:GetAircraftTypes(onlyactive,squadrons) +local unittypes={} +for _,_squadron in pairs(squadrons or self.squadrons)do +local squadron=_squadron +if(not onlyactive)or squadron:IsOnDuty()then +local gotit=false +for _,unittype in pairs(unittypes)do +if squadron.aircrafttype==unittype then +gotit=true +break +end +end +if not gotit then +table.insert(unittypes,squadron.aircrafttype) +end +end +end +return unittypes +end +function AIRWING:CanMission(Mission) +local Can=true +local Assets={} +local squadrons=Mission.squadrons or self.squadrons +local unittypes=self:GetAircraftTypes(true,squadrons) +local Npayloads=self:CountPayloadsInStock(Mission.type,unittypes,Mission.payloads) +if Npayloads#Assets then +self:T(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d",#Assets,Mission.nassets)) +Can=false +end +return Can,Assets +end +function AIRWING:RecruitAssets(Mission) +end +function AIRWING: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 AIRWING:CheckMissionCapability(MissionType,Capabilities) +for _,cap in pairs(Capabilities)do +local capability=cap +if capability.MissionType==MissionType then +return true +end +end +return false +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 +function AIRWING: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 AIRWING:GetMissionFromRequestID(RequestID) +for _,_mission in pairs(self.missionqueue)do +local mission=_mission +if mission.requestID and mission.requestID==RequestID then +return mission +end +end +return nil +end +function AIRWING:GetMissionFromRequest(Request) +return self:GetMissionFromRequestID(Request.uid) +end +INTEL={ +ClassName="INTEL", +verbose=0, +lid=nil, +alias=nil, +filterCategory={}, +detectionset=nil, +Contacts={}, +ContactsLost={}, +ContactsUnknown={}, +Clusters={}, +clustercounter=1, +clusterradius=15, +} +INTEL.version="0.2.1" +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="SPECTRE" +if self.coalition then +if self.coalition==coalition.side.RED then +self.alias="KGB" +elseif self.coalition==coalition.side.BLUE then +self.alias="CIA" +end +end +end +self.lid=string.format("INTEL %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("*","Detect","*") +self:AddTransition("*","NewContact","*") +self:AddTransition("*","LostContact","*") +self:AddTransition("*","NewCluster","*") +self:AddTransition("*","LostCluster","*") +self:SetForgetTime() +self:SetAcceptZones() +self:SetRejectZones() +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:SetForgetTime(TimeInterval) +self.dTforget=TimeInterval or 120 +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: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:SetClusterAnalysis(Switch,Markers) +self.clusteranalysis=Switch +self.clustermarkers=Markers +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) +local radius=radius or 15 +self.clusterradius=radius +return self +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)) +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.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"unkown") +end +end +self:I(self.lid..text) +end +self:__Status(-60) +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) +end +end +end +local remove={} +for unitname,_unit in pairs(DetectedUnits)do +local unit=_unit +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 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 then +table.insert(remove,unitname) +end +end +if#self.filterCategory>0 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 RecceGroups={} +for unitname,_unit in pairs(DetectedUnits)do +local unit=_unit +local group=unit:GetGroup() +if group then +local groupname=group:GetName() +DetectedGroups[groupname]=group +RecceGroups[groupname]=RecceDetecting[unitname] +end +end +self:CreateDetectedItems(DetectedGroups,RecceGroups) +if self.clusteranalysis then +self:PaintPicture() +end +end +function INTEL:CreateDetectedItems(DetectedGroups,RecceDetecting) +self:F({RecceDetecting=RecceDetecting}) +local Tnow=timer.getAbsTime() +for groupname,_group in pairs(DetectedGroups)do +local group=_group +local detecteditem=self:GetContactByName(groupname) +if detecteditem then +detecteditem.Tdetected=Tnow +detecteditem.position=group:GetCoordinate() +detecteditem.velocity=group:GetVelocityVec3() +detecteditem.speed=group:GetVelocityMPS() +else +local item={} +item.groupname=groupname +item.group=group +item.Tdetected=Tnow +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=RecceDetecting[groupname] +self:T(string.format("%s group detect by %s/%s",groupname,RecceDetecting[groupname]or"unknonw",item.recce or"unknown")) +self:AddContact(item) +self:NewContact(item) +end +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 +end +function INTEL:GetDetectedUnits(Unit,DetectedUnits,RecceDetecting,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local detectedtargets=Unit:GetDetectedTargets(DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) +local reccename=Unit:GetName() +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() +DetectedUnits[unitname]=unit +RecceDetecting[unitname]=reccename +self:T(string.format("Unit %s detect by %s",unitname,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) +end +function INTEL:onafterLostContact(From,Event,To,Contact) +self:F(self.lid..string.format("LOST contact %s",Contact.groupname)) +table.insert(self.ContactsLost,Contact) +end +function INTEL:onafterNewCluster(From,Event,To,Contact,Cluster) +self:F(self.lid..string.format("NEW cluster %d size %d with contact %s",Cluster.index,Cluster.size,Contact.groupname)) +end +function INTEL:onafterLostCluster(From,Event,To,Cluster,Mission) +local text=self.lid..string.format("LOST cluster %d",Cluster.index) +if Mission then +local mission=Mission +text=text..string.format(" mission name=%s type=%s target=%s",mission.name,mission.type,mission:GetTargetName()or"unkown") +end +self:T(text) +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:AddContact(Contact) +table.insert(self.Contacts,Contact) +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 +end +function INTEL:_CheckContactLost(Contact) +if Contact.group==nil or not Contact.group:IsAlive()then +return true +end +local dT=timer.getAbsTime()-Contact.Tdetected +local dTforget=self.dTforget +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() +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 +if(_cluster.size>0)and(self:ClusterCountUnits(_cluster)>0)then +table.insert(ClusterSet,_cluster) +else +local mission=_cluster.mission or nil +local marker=_cluster.marker +if marker then +marker:Remove() +end +self:LostCluster(_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 isincluster=self:CheckContactInClusters(contact) +local currentcluster=self:GetClusterOfContact(contact) +if currentcluster then +local isconnected=self:IsContactConnectedToCluster(contact,currentcluster) +if(not isconnected)and(currentcluster.size>1)then +local cluster=self:IsContactPartOfAnyClusters(contact) +if cluster then +self:AddContactToCluster(contact,cluster) +else +local newcluster=self:CreateCluster(contact.position) +self:AddContactToCluster(contact,newcluster) +self:NewCluster(contact,newcluster) +end +end +else +local cluster=self:IsContactPartOfAnyClusters(contact) +if cluster then +self:AddContactToCluster(contact,cluster) +else +local newcluster=self:CreateCluster(contact.position) +self:AddContactToCluster(contact,newcluster) +self:NewCluster(contact,newcluster) +end +end +end +if self.clustermarkers then +for _,_cluster in pairs(self.Clusters)do +local cluster=_cluster +local coordinate=self:GetClusterCoordinate(cluster) +self:UpdateClusterMarker(cluster) +end +end +end +function INTEL:CreateCluster(coordinate) +local cluster={} +cluster.index=self.clustercounter +cluster.coordinate=coordinate +cluster.threatlevelSum=0 +cluster.threatlevelMax=0 +cluster.size=0 +cluster.Contacts={} +table.insert(self.Clusters,cluster) +self.clustercounter=self.clustercounter+1 +return cluster +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 +end +end +function INTEL:RemoveContactFromCluster(contact,cluster) +if contact and cluster then +for i,_contact in pairs(cluster.Contacts)do +local Contact=_contact +if Contact.groupname==contact.groupname then +cluster.threatlevelSum=cluster.threatlevelSum-contact.threatlevel +cluster.size=cluster.size-1 +table.remove(cluster.Contacts,i) +return +end +end +end +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: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) +for _,_contact in pairs(cluster.Contacts)do +local Contact=_contact +if Contact.groupname~=contact.groupname then +local dist=Contact.position:DistanceFromPointVec2(contact.position) +local radius=self.clusterradius or 15 +if dist1000 then +return true +else +return false +end +end +function INTEL:_UpdateClusterPositions() +for _,_cluster in pairs(self.Clusters)do +local coord=self:GetClusterCoordinate(_cluster) +_cluster.coordinate=coord +self:T(self.lid..string.format("Cluster size: %s",_cluster.size)) +end +end +function INTEL:ClusterCountUnits(Cluster) +local unitcount=0 +for _,_group in pairs(Cluster.Contacts)do +unitcount=unitcount+_group.group:CountAliveUnits() +end +return unitcount +end +function INTEL:UpdateClusterMarker(cluster) +local unitcount=self:ClusterCountUnits(cluster) +local text=string.format("Cluster #%d. Size %d, Units %d, TLsum=%d",cluster.index,cluster.size,unitcount,cluster.threatlevelSum) +if not cluster.marker then +if self.coalition==coalition.side.RED then +cluster.marker=MARKER:New(cluster.coordinate,text):ToRed() +elseif self.coalition==coalition.side.BLUE then +cluster.marker=MARKER:New(cluster.coordinate,text):ToBlue() +else +cluster.marker=MARKER:New(cluster.coordinate,text):ToNeutral() +end +else +local refresh=false +if cluster.marker.text~=text then +cluster.marker.text=text +refresh=true +end +if cluster.marker.coordinate~=cluster.coordinate then +cluster.marker.coordinate=cluster.coordinate +refresh=true +end +if refresh then +cluster.marker:Refresh() +end +end +return self +end +AI_BALANCER={ +ClassName="AI_BALANCER", +PatrolZones={}, +AIGroups={}, +Earliest=5, +Latest=60, +} +function AI_BALANCER:New(SetClient,SpawnAI) +local self=BASE:Inherit(self,FSM_SET:New(SET_GROUP:New())) +self:SetStartState("None") +self:AddTransition("*","Monitor","Monitoring") +self:AddTransition("*","Spawn","Spawning") +self:AddTransition("Spawning","Spawned","Spawned") +self:AddTransition("*","Destroy","Destroying") +self:AddTransition("*","Return","Returning") +self.SetClient=SetClient +self.SetClient:FilterOnce() +self.SpawnAI=SpawnAI +self.SpawnQueue={} +self.ToNearestAirbase=false +self.ToHomeAirbase=false +self:__Monitor(1) +return self +end +function AI_BALANCER:InitSpawnInterval(Earliest,Latest) +self.Earliest=Earliest +self.Latest=Latest +return self +end +function AI_BALANCER:ReturnToNearestAirbases(ReturnThresholdRange,ReturnAirbaseSet) +self.ToNearestAirbase=true +self.ReturnThresholdRange=ReturnThresholdRange +self.ReturnAirbaseSet=ReturnAirbaseSet +end +function AI_BALANCER:ReturnToHomeAirbase(ReturnThresholdRange) +self.ToHomeAirbase=true +self.ReturnThresholdRange=ReturnThresholdRange +end +function AI_BALANCER:onenterSpawning(SetGroup,From,Event,To,ClientName) +local AIGroup=self.SpawnAI:Spawn() +if AIGroup then +AIGroup:T({"Spawning new AIGroup",ClientName=ClientName}) +SetGroup:Remove(ClientName) +SetGroup:Add(ClientName,AIGroup) +self.SpawnQueue[ClientName]=nil +self:Spawned(AIGroup) +end +end +function AI_BALANCER:onenterDestroying(SetGroup,From,Event,To,ClientName,AIGroup) +AIGroup:Destroy() +SetGroup:Flush(self) +SetGroup:Remove(ClientName) +SetGroup:Flush(self) +end +function AI_BALANCER:onenterReturning(SetGroup,From,Event,To,AIGroup) +local AIGroupTemplate=AIGroup:GetTemplate() +if self.ToHomeAirbase==true then +local WayPointCount=#AIGroupTemplate.route.points +local SwitchWayPointCommand=AIGroup:CommandSwitchWayPoint(1,WayPointCount,1) +AIGroup:SetCommand(SwitchWayPointCommand) +AIGroup:MessageToRed("Returning to home base ...",30) +else +local PointVec2=POINT_VEC2:New(AIGroup:GetVec2().x,AIGroup:GetVec2().y) +local ClosestAirbase=self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2(PointVec2) +self:T(ClosestAirbase.AirbaseName) +AIGroup:RouteRTB(ClosestAirbase) +end +end +function AI_BALANCER:onenterMonitoring(SetGroup) +self:T2({self.SetClient:Count()}) +self.SetClient:ForEachClient( +function(Client) +self:T3(Client.ClientName) +local AIGroup=self.Set:Get(Client.UnitName) +if AIGroup then self:T({AIGroup=AIGroup:GetName(),IsAlive=AIGroup:IsAlive()})end +if Client:IsAlive()==true then +if AIGroup and AIGroup:IsAlive()==true then +if self.ToNearestAirbase==false and self.ToHomeAirbase==false then +self:Destroy(Client.UnitName,AIGroup) +else +local PlayerInRange={Value=false} +local RangeZone=ZONE_RADIUS:New('RangeZone',AIGroup:GetVec2(),self.ReturnThresholdRange) +self:T2(RangeZone) +_DATABASE:ForEachPlayerUnit( +function(RangeTestUnit,RangeZone,AIGroup,PlayerInRange) +self:T2({PlayerInRange,RangeTestUnit.UnitName,RangeZone.ZoneName}) +if RangeTestUnit:IsInZone(RangeZone)==true then +self:T2("in zone") +if RangeTestUnit:GetCoalition()~=AIGroup:GetCoalition()then +self:T2("in range") +PlayerInRange.Value=true +end +end +end, +function(RangeZone,AIGroup,PlayerInRange) +if PlayerInRange.Value==false then +self:Return(AIGroup) +end +end +,RangeZone,AIGroup,PlayerInRange +) +end +self.Set:Remove(Client.UnitName) +end +else +if not AIGroup or not AIGroup:IsAlive()==true then +self:T("Client "..Client.UnitName.." not alive.") +self:T({Queue=self.SpawnQueue[Client.UnitName]}) +if not self.SpawnQueue[Client.UnitName]then +self:__Spawn(math.random(self.Earliest,self.Latest),Client.UnitName) +self.SpawnQueue[Client.UnitName]=true +self:T("New AI Spawned for Client "..Client.UnitName) +end +end +end +return true +end +) +self:__Monitor(10) +end +AI_AIR={ +ClassName="AI_AIR", +} +AI_AIR.TaskDelay=0.5 +function AI_AIR:New(AIGroup) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +self:SetControllable(AIGroup) +self:SetStartState("Stopped") +self:AddTransition("*","Queue","Queued") +self:AddTransition("*","Start","Started") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("*","Status","*") +self:AddTransition("*","RTB","*") +self:AddTransition("Patrolling","Refuel","Refuelling") +self:AddTransition("*","Takeoff","Airborne") +self:AddTransition("*","Return","Returning") +self:AddTransition("*","Hold","Holding") +self:AddTransition("*","Home","Home") +self:AddTransition("*","LostControl","LostControl") +self:AddTransition("*","Fuel","Fuel") +self:AddTransition("*","Damaged","Damaged") +self:AddTransition("*","Eject","*") +self:AddTransition("*","Crash","Crashed") +self:AddTransition("*","PilotDead","*") +self.IdleCount=0 +return self +end +function GROUP:OnEventTakeoff(EventData,Fsm) +Fsm:Takeoff() +self:UnHandleEvent(EVENTS.Takeoff) +end +function AI_AIR:SetDispatcher(Dispatcher) +self.Dispatcher=Dispatcher +end +function AI_AIR:GetDispatcher() +return self.Dispatcher +end +function AI_AIR:SetTargetDistance(Coordinate) +local CurrentCoord=self.Controllable:GetCoordinate() +self.TargetDistance=CurrentCoord:Get2DDistance(Coordinate) +self.ClosestTargetDistance=(not self.ClosestTargetDistance or self.ClosestTargetDistance>self.TargetDistance)and self.TargetDistance or self.ClosestTargetDistance +end +function AI_AIR:ClearTargetDistance() +self.TargetDistance=nil +self.ClosestTargetDistance=nil +end +function AI_AIR:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) +self:F2({PatrolMinSpeed,PatrolMaxSpeed}) +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +end +function AI_AIR:SetRTBSpeed(RTBMinSpeed,RTBMaxSpeed) +self:F({RTBMinSpeed,RTBMaxSpeed}) +self.RTBMinSpeed=RTBMinSpeed +self.RTBMaxSpeed=RTBMaxSpeed +end +function AI_AIR:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) +self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +end +function AI_AIR:SetHomeAirbase(HomeAirbase) +self:F2({HomeAirbase}) +self.HomeAirbase=HomeAirbase +end +function AI_AIR:SetTanker(TankerName) +self:F2({TankerName}) +self.TankerName=TankerName +end +function AI_AIR:SetDisengageRadius(DisengageRadius) +self:F2({DisengageRadius}) +self.DisengageRadius=DisengageRadius +end +function AI_AIR:SetStatusOff() +self:F2() +self.CheckStatus=false +end +function AI_AIR:SetFuelThreshold(FuelThresholdPercentage,OutOfFuelOrbitTime) +self.FuelThresholdPercentage=FuelThresholdPercentage +self.OutOfFuelOrbitTime=OutOfFuelOrbitTime +self.Controllable:OptionRTBBingoFuel(false) +return self +end +function AI_AIR:SetDamageThreshold(PatrolDamageThreshold) +self.PatrolManageDamage=true +self.PatrolDamageThreshold=PatrolDamageThreshold +return self +end +function AI_AIR:onafterStart(Controllable,From,Event,To) +self:__Status(10) +self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead) +self:HandleEvent(EVENTS.Crash,self.OnCrash) +self:HandleEvent(EVENTS.Ejection,self.OnEjection) +Controllable:OptionROEHoldFire() +Controllable:OptionROTVertical() +end +function AI_AIR:onafterReturn(Controllable,From,Event,To) +self:__RTB(self.TaskDelay) +end +function AI_AIR:onbeforeStatus() +return self.CheckStatus +end +function AI_AIR:onafterStatus() +if self.Controllable and self.Controllable:IsAlive()then +local RTB=false +local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate()) +if not self:Is("Holding")and not self:Is("Returning")then +local DistanceFromHomeBase=self.HomeAirbase:GetCoordinate():Get2DDistance(self.Controllable:GetCoordinate()) +if DistanceFromHomeBase>self.DisengageRadius then +self:I(self.Controllable:GetName().." is too far from home base, RTB!") +self:Hold(300) +RTB=false +end +end +if not self:Is("Fuel")and not self:Is("Home")and not self:is("Refuelling")then +local Fuel=self.Controllable:GetFuelMin() +if Fuel=10 then +if Damage~=InitialLife then +self:Damaged() +else +self:I(self.Controllable:GetName().." control lost! ") +self:LostControl() +end +else +self.IdleCount=self.IdleCount+1 +end +end +else +self.IdleCount=0 +end +if RTB==true then +self:__RTB(self.TaskDelay) +end +if not self:Is("Home")then +self:__Status(10) +end +end +end +function AI_AIR.RTBRoute(AIGroup,Fsm) +AIGroup:F({"AI_AIR.RTBRoute:",AIGroup:GetName()}) +if AIGroup:IsAlive()then +Fsm:RTB() +end +end +function AI_AIR.RTBHold(AIGroup,Fsm) +AIGroup:F({"AI_AIR.RTBHold:",AIGroup:GetName()}) +if AIGroup:IsAlive()then +Fsm:__RTB(Fsm.TaskDelay) +Fsm:Return() +local Task=AIGroup:TaskOrbitCircle(4000,400) +AIGroup:SetTask(Task) +end +end +function AI_AIR:onafterRTB(AIGroup,From,Event,To) +self:F({AIGroup,From,Event,To}) +if AIGroup and AIGroup:IsAlive()then +self:I("Group "..AIGroup:GetName().." ... RTB! ( "..self:GetState().." )") +self:ClearTargetDistance() +local EngageRoute={} +local FromCoord=AIGroup:GetCoordinate() +local ToTargetCoord=self.HomeAirbase:GetCoordinate() +local ToTargetVec3=ToTargetCoord:GetVec3() +ToTargetVec3.y=ToTargetCoord:GetLandHeight()+1000 +local ToTargetCoord2=COORDINATE:NewFromVec3(ToTargetVec3) +if not self.RTBMinSpeed or not self.RTBMaxSpeed then +local RTBSpeedMax=AIGroup:GetSpeedMax() +self:SetRTBSpeed(RTBSpeedMax*0.5,RTBSpeedMax*0.6) +end +local RTBSpeed=math.random(self.RTBMinSpeed,self.RTBMaxSpeed) +local Distance=FromCoord:Get2DDistance(ToTargetCoord2) +local ToAirbaseCoord=ToTargetCoord2 +if Distance<5000 then +self:I("RTB and near the airbase!") +self:Home() +return +end +if not AIGroup:InAir()==true then +self:I("Not anymore in the air, considered Home.") +self:Home() +return +end +local FromRTBRoutePoint=FromCoord:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +RTBSpeed, +true +) +local ToRTBRoutePoint=ToAirbaseCoord:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +RTBSpeed, +true +) +EngageRoute[#EngageRoute+1]=FromRTBRoutePoint +EngageRoute[#EngageRoute+1]=ToRTBRoutePoint +local Tasks={} +Tasks[#Tasks+1]=AIGroup:TaskFunction("AI_AIR.RTBRoute",self) +EngageRoute[#EngageRoute].task=AIGroup:TaskCombo(Tasks) +AIGroup:OptionROEHoldFire() +AIGroup:OptionROTEvadeFire() +AIGroup:Route(EngageRoute,self.TaskDelay) +end +end +function AI_AIR:onafterHome(AIGroup,From,Event,To) +self:F({AIGroup,From,Event,To}) +self:I("Group "..self.Controllable:GetName().." ... Home! ( "..self:GetState().." )") +if AIGroup and AIGroup:IsAlive()then +end +end +function AI_AIR:onafterHold(AIGroup,From,Event,To,HoldTime) +self:F({AIGroup,From,Event,To}) +self:I("Group "..self.Controllable:GetName().." ... Holding! ( "..self:GetState().." )") +if AIGroup and AIGroup:IsAlive()then +local OrbitTask=AIGroup:TaskOrbitCircle(math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude),self.PatrolMinSpeed) +local TimedOrbitTask=AIGroup:TaskControlled(OrbitTask,AIGroup:TaskCondition(nil,nil,nil,nil,HoldTime,nil)) +local RTBTask=AIGroup:TaskFunction("AI_AIR.RTBHold",self) +local OrbitHoldTask=AIGroup:TaskOrbitCircle(4000,self.PatrolMinSpeed) +AIGroup:SetTask(AIGroup:TaskCombo({TimedOrbitTask,RTBTask,OrbitHoldTask}),1) +end +end +function AI_AIR.Resume(AIGroup,Fsm) +AIGroup:I({"AI_AIR.Resume:",AIGroup:GetName()}) +if AIGroup:IsAlive()then +Fsm:__RTB(Fsm.TaskDelay) +end +end +function AI_AIR:onafterRefuel(AIGroup,From,Event,To) +self:F({AIGroup,From,Event,To}) +if AIGroup and AIGroup:IsAlive()then +local Tanker=GROUP:FindByName(self.TankerName) +if Tanker and Tanker:IsAlive()and Tanker:IsAirPlane()then +self:I("Group "..self.Controllable:GetName().." ... Refuelling! State="..self:GetState()..", Refuelling tanker "..self.TankerName) +local RefuelRoute={} +local FromRefuelCoord=AIGroup:GetCoordinate() +local ToRefuelCoord=Tanker:GetCoordinate() +local ToRefuelSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +local FromRefuelRoutePoint=FromRefuelCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true) +local ToRefuelRoutePoint=Tanker:GetCoordinate():WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToRefuelSpeed,true) +self:F({ToRefuelSpeed=ToRefuelSpeed}) +RefuelRoute[#RefuelRoute+1]=FromRefuelRoutePoint +RefuelRoute[#RefuelRoute+1]=ToRefuelRoutePoint +AIGroup:OptionROEHoldFire() +AIGroup:OptionROTEvadeFire() +local classname=self:GetClassName() +if classname=="AI_A2A_CAP"then +classname="AI_AIR_PATROL" +end +env.info("FF refueling classname="..classname) +local Tasks={} +Tasks[#Tasks+1]=AIGroup:TaskRefueling() +Tasks[#Tasks+1]=AIGroup:TaskFunction(classname..".Resume",self) +RefuelRoute[#RefuelRoute].task=AIGroup:TaskCombo(Tasks) +AIGroup:Route(RefuelRoute,self.TaskDelay) +else +self:RTB() +end +end +end +function AI_AIR:onafterDead() +self:SetStatusOff() +end +function AI_AIR:OnCrash(EventData) +if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then +if#self.Controllable:GetUnits()==1 then +self:__Crash(self.TaskDelay,EventData) +end +end +end +function AI_AIR:OnEjection(EventData) +if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then +self:__Eject(self.TaskDelay,EventData) +end +end +function AI_AIR:OnPilotDead(EventData) +if self.Controllable:IsAlive()and EventData.IniDCSGroupName==self.Controllable:GetName()then +self:__PilotDead(self.TaskDelay,EventData) +end +end +AI_AIR_PATROL={ +ClassName="AI_AIR_PATROL", +} +function AI_AIR_PATROL:New(AI_Air,AIGroup,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local self=BASE:Inherit(self,AI_Air) +local SpeedMax=AIGroup:GetSpeedMax() +self.PatrolZone=PatrolZone +self.PatrolFloorAltitude=PatrolFloorAltitude or 1000 +self.PatrolCeilingAltitude=PatrolCeilingAltitude or 1500 +self.PatrolMinSpeed=PatrolMinSpeed or SpeedMax*0.5 +self.PatrolMaxSpeed=PatrolMaxSpeed or SpeedMax*0.75 +self.PatrolAltType=PatrolAltType or"RADIO" +self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling") +self:AddTransition("Patrolling","PatrolRoute","Patrolling") +self:AddTransition("*","Reset","Patrolling") +return self +end +function AI_AIR_PATROL:SetEngageRange(EngageRange) +self:F2() +if EngageRange then +self.EngageRange=EngageRange +else +self.EngageRange=nil +end +end +function AI_AIR_PATROL:SetRaceTrackPattern(LegMin,LegMax,HeadingMin,HeadingMax,DurationMin,DurationMax,CapCoordinates) +self.racetrack=true +self.racetracklegmin=LegMin or 10000 +self.racetracklegmax=LegMax or 15000 +self.racetrackheadingmin=HeadingMin or 0 +self.racetrackheadingmax=HeadingMax or 180 +self.racetrackdurationmin=DurationMin +self.racetrackdurationmax=DurationMax +if self.racetrackdurationmax and not self.racetrackdurationmin then +self.racetrackdurationmin=self.racetrackdurationmax +end +self.racetrackcapcoordinates=CapCoordinates +end +function AI_AIR_PATROL:onafterPatrol(AIPatrol,From,Event,To) +self:F2() +self:ClearTargetDistance() +self:__PatrolRoute(self.TaskDelay) +AIPatrol:OnReSpawn( +function(PatrolGroup) +self:__Reset(self.TaskDelay) +self:__PatrolRoute(self.TaskDelay) +end +) +end +function AI_AIR_PATROL.___PatrolRoute(AIPatrol,Fsm) +AIPatrol:F({"AI_AIR_PATROL.___PatrolRoute:",AIPatrol:GetName()}) +if AIPatrol and AIPatrol:IsAlive()then +Fsm:PatrolRoute() +end +end +function AI_AIR_PATROL:onafterPatrolRoute(AIPatrol,From,Event,To) +self:F2() +if From=="RTB"then +return +end +if AIPatrol and AIPatrol:IsAlive()then +local PatrolRoute={} +local CurrentCoord=AIPatrol:GetCoordinate() +local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) +local ToTargetCoord=self.PatrolZone:GetRandomPointVec2() +ToTargetCoord:SetAlt(altitude) +self:SetTargetDistance(ToTargetCoord) +local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +local speedkmh=ToTargetSpeed +local FromWP=CurrentCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true) +PatrolRoute[#PatrolRoute+1]=FromWP +if self.racetrack then +local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax) +local leg=math.random(self.racetracklegmin,self.racetracklegmax) +local duration=self.racetrackdurationmin +if self.racetrackdurationmax then +duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax) +end +local c0=self.PatrolZone:GetRandomCoordinate() +if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then +c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] +end +local c1=c0:SetAltitude(altitude) +local c2=c1:Translate(leg,heading):SetAltitude(altitude) +self:SetTargetDistance(c0) +self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration))) +local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2) +local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self) +local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil) +local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond) +PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit") +else +local ToWP=ToTargetCoord:WaypointAir(self.PatrolAltType,POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,ToTargetSpeed,true) +PatrolRoute[#PatrolRoute+1]=ToWP +local Tasks={} +Tasks[#Tasks+1]=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute",self) +PatrolRoute[#PatrolRoute].task=AIPatrol:TaskCombo(Tasks) +end +AIPatrol:OptionROEReturnFire() +AIPatrol:OptionROTEvadeFire() +AIPatrol:Route(PatrolRoute,self.TaskDelay) +end +end +function AI_AIR_PATROL.Resume(AIPatrol,Fsm) +AIPatrol:F({"AI_AIR_PATROL.Resume:",AIPatrol:GetName()}) +if AIPatrol and AIPatrol:IsAlive()then +Fsm:__Reset(Fsm.TaskDelay) +Fsm:__PatrolRoute(Fsm.TaskDelay) +end +end +AI_AIR_ENGAGE={ +ClassName="AI_AIR_ENGAGE", +} +function AI_AIR_ENGAGE:New(AI_Air,AIGroup,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local self=BASE:Inherit(self,AI_Air) +self.Accomplished=false +self.Engaging=false +local SpeedMax=AIGroup:GetSpeedMax() +self.EngageMinSpeed=EngageMinSpeed or SpeedMax*0.5 +self.EngageMaxSpeed=EngageMaxSpeed or SpeedMax*0.75 +self.EngageFloorAltitude=EngageFloorAltitude or 1000 +self.EngageCeilingAltitude=EngageCeilingAltitude or 1500 +self.EngageAltType=EngageAltType or"RADIO" +self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"EngageRoute","Engaging") +self:AddTransition({"Started","Engaging","Returning","Airborne","Patrolling"},"Engage","Engaging") +self:AddTransition("Engaging","Fired","Engaging") +self:AddTransition("*","Destroy","*") +self:AddTransition("Engaging","Abort","Patrolling") +self:AddTransition("Engaging","Accomplish","Patrolling") +self:AddTransition({"Patrolling","Engaging"},"Refuel","Refuelling") +return self +end +function AI_AIR_ENGAGE:onafterStart(AIGroup,From,Event,To) +self:GetParent(self,AI_AIR_ENGAGE).onafterStart(self,AIGroup,From,Event,To) +AIGroup:HandleEvent(EVENTS.Takeoff,nil,self) +end +function AI_AIR_ENGAGE:onafterEngage(AIGroup,From,Event,To) +self:HandleEvent(EVENTS.Dead) +end +function AI_AIR_ENGAGE:onbeforeEngage(AIGroup,From,Event,To) +if self.Accomplished==true then +return false +end +return true +end +function AI_AIR_ENGAGE:onafterAbort(AIGroup,From,Event,To) +AIGroup:ClearTasks() +self:Return() +end +function AI_AIR_ENGAGE:onafterAccomplish(AIGroup,From,Event,To) +self.Accomplished=true +end +function AI_AIR_ENGAGE:onafterDestroy(AIGroup,From,Event,To,EventData) +if EventData.IniUnit then +self.AttackUnits[EventData.IniUnit]=nil +end +end +function AI_AIR_ENGAGE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.AttackUnits and self.AttackUnits[EventData.IniUnit]then +self:__Destroy(self.TaskDelay,EventData) +end +end +end +function AI_AIR_ENGAGE.___EngageRoute(AIGroup,Fsm,AttackSetUnit) +Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s",tostring(AIGroup:GetName()))) +if AIGroup and AIGroup:IsAlive()then +Fsm:__EngageRoute(Fsm.TaskDelay or 0.1,AttackSetUnit) +end +end +function AI_AIR_ENGAGE:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) +self:I({DefenderGroup,From,Event,To,AttackSetUnit}) +local DefenderGroupName=DefenderGroup:GetName() +self.AttackSetUnit=AttackSetUnit +local AttackCount=AttackSetUnit:CountAlive() +if AttackCount>0 then +if DefenderGroup:IsAlive()then +local EngageAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude) +local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed) +local DefenderCoord=DefenderGroup:GetPointVec3() +DefenderCoord:SetY(EngageAltitude) +local TargetCoord=AttackSetUnit:GetFirst():GetPointVec3() +TargetCoord:SetY(EngageAltitude) +local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord) +local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000) +if TargetDistance<=EngageDistance*9 then +self:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km",TargetDistance/1000)) +self:__Engage(0.1,AttackSetUnit) +else +self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km",TargetDistance/1000)) +local EngageRoute={} +local AttackTasks={} +local FromWP=DefenderCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=FromWP +self:SetTargetDistance(TargetCoord) +local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord)) +local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true) +local ToWP=ToCoord:WaypointAir(self.PatrolAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=ToWP +AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___EngageRoute",self,AttackSetUnit) +EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks) +DefenderGroup:OptionROEReturnFire() +DefenderGroup:OptionROTEvadeFire() +DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1) +end +end +else +self:I(DefenderGroupName..": No targets found -> Going RTB") +self:Return() +end +end +function AI_AIR_ENGAGE.___Engage(AIGroup,Fsm,AttackSetUnit) +Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s",tostring(AIGroup:GetName()))) +if AIGroup and AIGroup:IsAlive()then +local delay=Fsm.TaskDelay or 0.1 +Fsm:__Engage(delay,AttackSetUnit) +end +end +function AI_AIR_ENGAGE:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({DefenderGroup,From,Event,To,AttackSetUnit}) +local DefenderGroupName=DefenderGroup:GetName() +self.AttackSetUnit=AttackSetUnit +local AttackCount=AttackSetUnit:CountAlive() +self:T({AttackCount=AttackCount}) +if AttackCount>0 then +if DefenderGroup and DefenderGroup:IsAlive()then +local EngageAltitude=math.random(self.EngageFloorAltitude or 500,self.EngageCeilingAltitude or 1000) +local EngageSpeed=math.random(self.EngageMinSpeed,self.EngageMaxSpeed) +local DefenderCoord=DefenderGroup:GetPointVec3() +DefenderCoord:SetY(EngageAltitude) +local TargetCoord=AttackSetUnit:GetFirst():GetPointVec3() +TargetCoord:SetY(EngageAltitude) +local TargetDistance=DefenderCoord:Get2DDistance(TargetCoord) +local EngageDistance=(DefenderGroup:IsHelicopter()and 5000)or(DefenderGroup:IsAirPlane()and 10000) +local EngageRoute={} +local AttackTasks={} +local FromWP=DefenderCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=FromWP +self:SetTargetDistance(TargetCoord) +local FromEngageAngle=DefenderCoord:GetAngleDegrees(DefenderCoord:GetDirectionVec3(TargetCoord)) +local ToCoord=DefenderCoord:Translate(EngageDistance,FromEngageAngle,true) +local ToWP=ToCoord:WaypointAir(self.EngageAltType or"RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,EngageSpeed,true) +EngageRoute[#EngageRoute+1]=ToWP +if TargetDistance<=EngageDistance*9 then +local AttackUnitTasks=self:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) +if#AttackUnitTasks==0 then +self:I(DefenderGroupName..": No valid targets found -> Going RTB") +self:Return() +return +else +local text=string.format("%s: Engaging targets at distance %.2f NM",DefenderGroupName,UTILS.MetersToNM(TargetDistance)) +self:I(text) +DefenderGroup:OptionROEOpenFire() +DefenderGroup:OptionROTEvadeFire() +DefenderGroup:OptionKeepWeaponsOnThreat() +AttackTasks[#AttackTasks+1]=DefenderGroup:TaskCombo(AttackUnitTasks) +end +end +AttackTasks[#AttackTasks+1]=DefenderGroup:TaskFunction("AI_AIR_ENGAGE.___Engage",self,AttackSetUnit) +EngageRoute[#EngageRoute].task=DefenderGroup:TaskCombo(AttackTasks) +DefenderGroup:Route(EngageRoute,self.TaskDelay or 0.1) +end +else +self:I(DefenderGroupName..": No targets found -> returning.") +self:Return() +return +end +end +function AI_AIR_ENGAGE.Resume(AIEngage,Fsm) +AIEngage:F({"Resume:",AIEngage:GetName()}) +if AIEngage and AIEngage:IsAlive()then +Fsm:__Reset(Fsm.TaskDelay or 0.1) +Fsm:__EngageRoute(Fsm.TaskDelay or 0.2,Fsm.AttackSetUnit) +end +end +AI_A2A_PATROL={ +ClassName="AI_A2A_PATROL", +} +function AI_A2A_PATROL:New(AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local AI_Air=AI_AIR:New(AIPatrol) +local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AIPatrol,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local self=BASE:Inherit(self,AI_Air_Patrol) +self:SetFuelThreshold(.2,60) +self:SetDamageThreshold(0.4) +self:SetDisengageRadius(70000) +self.PatrolZone=PatrolZone +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +self.PatrolAltType=PatrolAltType or"BARO" +self:AddTransition({"Started","Airborne","Refuelling"},"Patrol","Patrolling") +self:AddTransition("Patrolling","Route","Patrolling") +self:AddTransition("*","Reset","Patrolling") +return self +end +function AI_A2A_PATROL:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) +self:F2({PatrolMinSpeed,PatrolMaxSpeed}) +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +end +function AI_A2A_PATROL:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) +self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +end +function AI_A2A_PATROL:onafterPatrol(AIPatrol,From,Event,To) +self:F2() +self:ClearTargetDistance() +self:__Route(1) +AIPatrol:OnReSpawn( +function(PatrolGroup) +self:__Reset(1) +self:__Route(5) +end +) +end +function AI_A2A_PATROL.PatrolRoute(AIPatrol,Fsm) +AIPatrol:F({"AI_A2A_PATROL.PatrolRoute:",AIPatrol:GetName()}) +if AIPatrol and AIPatrol:IsAlive()then +Fsm:Route() +end +end +function AI_A2A_PATROL:onafterRoute(AIPatrol,From,Event,To) +self:F2() +if From=="RTB"then +return +end +if AIPatrol and AIPatrol:IsAlive()then +local PatrolRoute={} +local CurrentCoord=AIPatrol:GetCoordinate() +local altitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) +local speedkmh=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil,speedkmh,{},"Current") +if self.racetrack then +local heading=math.random(self.racetrackheadingmin,self.racetrackheadingmax) +local leg=math.random(self.racetracklegmin,self.racetracklegmax) +local duration=self.racetrackdurationmin +if self.racetrackdurationmax then +duration=math.random(self.racetrackdurationmin,self.racetrackdurationmax) +end +local c0=self.PatrolZone:GetRandomCoordinate() +if self.racetrackcapcoordinates and#self.racetrackcapcoordinates>0 then +c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] +end +local c1=c0:SetAltitude(altitude) +local c2=c1:Translate(leg,heading):SetAltitude(altitude) +self:SetTargetDistance(c0) +self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec",UTILS.KmphToKnots(speedkmh),UTILS.MetersToFeet(altitude),heading,leg,tostring(duration))) +local taskOrbit=AIPatrol:TaskOrbit(c1,altitude,UTILS.KmphToMps(speedkmh),c2) +local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self) +local taskCond=AIPatrol:TaskCondition(nil,nil,nil,nil,duration,nil) +local taskCont=AIPatrol:TaskControlled(taskOrbit,taskCond) +PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskCont,taskPatrol},"CAP Orbit") +else +local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() +ToTargetCoord:SetAltitude(altitude) +self:SetTargetDistance(ToTargetCoord) +local taskReRoute=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute",self) +PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType,speedkmh,{taskReRoute},"Patrol Point") +end +AIPatrol:OptionROEReturnFire() +AIPatrol:OptionROTEvadeFire() +AIPatrol:Route(PatrolRoute,0.5) +end +end +AI_A2A_CAP={ +ClassName="AI_A2A_CAP", +} +function AI_A2A_CAP:New2(AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) +local AI_Air=AI_AIR:New(AICap) +local AI_Air_Patrol=AI_AIR_PATROL:New(AI_Air,AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air_Patrol,AICap,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local self=BASE:Inherit(self,AI_Air_Engage) +self:SetFuelThreshold(.2,60) +self:SetDamageThreshold(0.4) +self:SetDisengageRadius(70000) +return self +end +function AI_A2A_CAP:New(AICap,PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,PatrolAltType) +return self:New2(AICap,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,PatrolZone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) +end +function AI_A2A_CAP:onafterStart(AICap,From,Event,To) +self:GetParent(self,AI_A2A_CAP).onafterStart(self,AICap,From,Event,To) +AICap:HandleEvent(EVENTS.Takeoff,nil,self) +end +function AI_A2A_CAP:SetEngageZone(EngageZone) +self:F2() +if EngageZone then +self.EngageZone=EngageZone +else +self.EngageZone=nil +end +end +function AI_A2A_CAP:SetEngageRange(EngageRange) +self:F2() +if EngageRange then +self.EngageRange=EngageRange +else +self.EngageRange=nil +end +end +function AI_A2A_CAP:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) +local AttackUnitTasks={} +for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do +local AttackUnit=AttackUnit +if AttackUnit and AttackUnit:IsAlive()and AttackUnit:IsAir()then +self:T({"Attacking Task:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()}) +AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit) +end +end +return AttackUnitTasks +end +AI_A2A_GCI={ +ClassName="AI_A2A_GCI", +} +function AI_A2A_GCI:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local AI_Air=AI_AIR:New(AIIntercept) +local AI_Air_Engage=AI_AIR_ENGAGE:New(AI_Air,AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local self=BASE:Inherit(self,AI_Air_Engage) +self:SetFuelThreshold(.2,60) +self:SetDamageThreshold(0.4) +self:SetDisengageRadius(70000) +return self +end +function AI_A2A_GCI:New(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +return self:New2(AIIntercept,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +end +function AI_A2A_GCI:onafterStart(AIIntercept,From,Event,To) +self:GetParent(self,AI_A2A_GCI).onafterStart(self,AIIntercept,From,Event,To) +end +function AI_A2A_GCI:CreateAttackUnitTasks(AttackSetUnit,DefenderGroup,EngageAltitude) +local AttackUnitTasks={} +for AttackUnitID,AttackUnit in pairs(self.AttackSetUnit:GetSet())do +local AttackUnit=AttackUnit +self:T({"Attacking Unit:",AttackUnit:GetName(),AttackUnit:IsAlive(),AttackUnit:IsAir()}) +if AttackUnit:IsAlive()and AttackUnit:IsAir()then +AttackUnitTasks[#AttackUnitTasks+1]=DefenderGroup:TaskAttackUnit(AttackUnit) +end +end +return AttackUnitTasks +end +do +AI_A2A_DISPATCHER={ +ClassName="AI_A2A_DISPATCHER", +Detection=nil, +} +AI_A2A_DISPATCHER.Takeoff=GROUP.Takeoff +AI_A2A_DISPATCHER.Landing={ +NearAirbase=1, +AtRunway=2, +AtEngineShutdown=3, +} +function AI_A2A_DISPATCHER:New(Detection) +local self=BASE:Inherit(self,DETECTION_MANAGER:New(nil,Detection)) +self.Detection=Detection +self.DefenderSquadrons={} +self.DefenderSpawns={} +self.DefenderTasks={} +self.DefenderDefault={} +self.SetSendPlayerMessages=false +self.Detection:FilterCategories({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) +self.Detection:SetRefreshTimeInterval(30) +self:SetEngageRadius() +self:SetGciRadius() +self:SetIntercept(300) +self:SetDisengageRadius(300000) +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air) +self:SetDefaultTakeoffInAirAltitude(500) +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase) +self:SetDefaultOverhead(1) +self:SetDefaultGrouping(1) +self:SetDefaultFuelThreshold(0.15,0) +self:SetDefaultDamageThreshold(0.4) +self:SetDefaultCapTimeInterval(180,600) +self:SetDefaultCapLimit(1) +self:AddTransition("Started","Assign","Started") +self:AddTransition("*","CAP","*") +self:AddTransition("*","GCI","*") +self:AddTransition("*","ENGAGE","*") +self:HandleEvent(EVENTS.Crash,self.OnEventCrashOrDead) +self:HandleEvent(EVENTS.Dead,self.OnEventCrashOrDead) +self:HandleEvent(EVENTS.Land) +self:HandleEvent(EVENTS.EngineShutdown) +self:HandleEvent(EVENTS.BaseCaptured) +self:SetTacticalDisplay(false) +self.DefenderCAPIndex=0 +self:__Start(5) +return self +end +function AI_A2A_DISPATCHER:onafterStart(From,Event,To) +self:GetParent(self,AI_A2A_DISPATCHER).onafterStart(self,From,Event,To) +for SquadronName,_DefenderSquadron in pairs(self.DefenderSquadrons)do +local DefenderSquadron=_DefenderSquadron +DefenderSquadron.Resources={} +if DefenderSquadron.ResourceCount then +for Resource=1,DefenderSquadron.ResourceCount do +self:ParkDefender(DefenderSquadron) +end +end +end +end +function AI_A2A_DISPATCHER:ParkDefender(DefenderSquadron) +local TemplateID=math.random(1,#DefenderSquadron.Spawn) +local Spawn=DefenderSquadron.Spawn[TemplateID] +Spawn:InitGrouping(1) +local SpawnGroup +if self:IsSquadronVisible(DefenderSquadron.Name)then +local Grouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping +Grouping=1 +Spawn:InitGrouping(Grouping) +SpawnGroup=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,SPAWN.Takeoff.Cold) +local GroupName=SpawnGroup:GetName() +DefenderSquadron.Resources=DefenderSquadron.Resources or{} +DefenderSquadron.Resources[TemplateID]=DefenderSquadron.Resources[TemplateID]or{} +DefenderSquadron.Resources[TemplateID][GroupName]={} +DefenderSquadron.Resources[TemplateID][GroupName]=SpawnGroup +self.uncontrolled=self.uncontrolled or{} +self.uncontrolled[DefenderSquadron.Name]=self.uncontrolled[DefenderSquadron.Name]or{} +table.insert(self.uncontrolled[DefenderSquadron.Name],{group=SpawnGroup,name=GroupName,grouping=Grouping}) +end +end +function AI_A2A_DISPATCHER:OnEventBaseCaptured(EventData) +local AirbaseName=EventData.PlaceName +self:I("Captured "..AirbaseName) +for SquadronName,Squadron in pairs(self.DefenderSquadrons)do +if Squadron.AirbaseName==AirbaseName then +Squadron.ResourceCount=-999 +Squadron.Captured=true +self:I("Squadron "..SquadronName.." captured.") +end +end +end +function AI_A2A_DISPATCHER:OnEventCrashOrDead(EventData) +self.Detection:ForgetDetectedUnit(EventData.IniUnitName) +end +function AI_A2A_DISPATCHER:OnEventLand(EventData) +self:F("Landed") +local DefenderUnit=EventData.IniUnit +local Defender=EventData.IniGroup +local Squadron=self:GetSquadronFromDefender(Defender) +if Squadron then +self:F({SquadronName=Squadron.Name}) +local LandingMethod=self:GetSquadronLanding(Squadron.Name) +if LandingMethod==AI_A2A_DISPATCHER.Landing.AtRunway then +local DefenderSize=Defender:GetSize() +if DefenderSize==1 then +self:RemoveDefenderFromSquadron(Squadron,Defender) +end +DefenderUnit:Destroy() +self:ParkDefender(Squadron) +return +end +if DefenderUnit:GetLife()~=DefenderUnit:GetLife0()then +DefenderUnit:Destroy() +return +end +end +end +function AI_A2A_DISPATCHER:OnEventEngineShutdown(EventData) +local DefenderUnit=EventData.IniUnit +local Defender=EventData.IniGroup +local Squadron=self:GetSquadronFromDefender(Defender) +if Squadron then +self:F({SquadronName=Squadron.Name}) +local LandingMethod=self:GetSquadronLanding(Squadron.Name) +if LandingMethod==AI_A2A_DISPATCHER.Landing.AtEngineShutdown and +not DefenderUnit:InAir()then +local DefenderSize=Defender:GetSize() +if DefenderSize==1 then +self:RemoveDefenderFromSquadron(Squadron,Defender) +end +DefenderUnit:Destroy() +self:ParkDefender(Squadron) +end +end +end +function AI_A2A_DISPATCHER:SetEngageRadius(EngageRadius) +self.Detection:SetFriendliesRange(EngageRadius or 100000) +return self +end +function AI_A2A_DISPATCHER:SetDisengageRadius(DisengageRadius) +self.DisengageRadius=DisengageRadius or 300000 +return self +end +function AI_A2A_DISPATCHER:SetGciRadius(GciRadius) +self.GciRadius=GciRadius or 200000 +return self +end +function AI_A2A_DISPATCHER:SetBorderZone(BorderZone) +self.Detection:SetAcceptZones(BorderZone) +return self +end +function AI_A2A_DISPATCHER:SetTacticalDisplay(TacticalDisplay) +self.TacticalDisplay=TacticalDisplay +return self +end +function AI_A2A_DISPATCHER:SetDefaultDamageThreshold(DamageThreshold) +self.DefenderDefault.DamageThreshold=DamageThreshold +return self +end +function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval(CapMinSeconds,CapMaxSeconds) +self.DefenderDefault.CapMinSeconds=CapMinSeconds +self.DefenderDefault.CapMaxSeconds=CapMaxSeconds +return self +end +function AI_A2A_DISPATCHER:SetDefaultCapLimit(CapLimit) +self.DefenderDefault.CapLimit=CapLimit +return self +end +function AI_A2A_DISPATCHER:SetIntercept(InterceptDelay) +self.DefenderDefault.InterceptDelay=InterceptDelay +local Detection=self.Detection +Detection:SetIntercept(true,InterceptDelay) +return self +end +function AI_A2A_DISPATCHER:GetAIFriendliesNearBy(DetectedItem) +local FriendliesNearBy=self.Detection:GetFriendliesDistance(DetectedItem) +return FriendliesNearBy +end +function AI_A2A_DISPATCHER:GetDefenderTasks() +return self.DefenderTasks or{} +end +function AI_A2A_DISPATCHER:GetDefenderTask(Defender) +return self.DefenderTasks[Defender] +end +function AI_A2A_DISPATCHER:GetDefenderTaskFsm(Defender) +return self:GetDefenderTask(Defender).Fsm +end +function AI_A2A_DISPATCHER:GetDefenderTaskTarget(Defender) +return self:GetDefenderTask(Defender).Target +end +function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName(Defender) +return self:GetDefenderTask(Defender).SquadronName +end +function AI_A2A_DISPATCHER:ClearDefenderTask(Defender) +if Defender and Defender:IsAlive()and self.DefenderTasks[Defender]then +local Target=self.DefenderTasks[Defender].Target +local Message="Clearing ("..self.DefenderTasks[Defender].Type..") " +Message=Message..Defender:GetName() +if Target then +Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or"" +end +self:F({Target=Message}) +end +self.DefenderTasks[Defender]=nil +return self +end +function AI_A2A_DISPATCHER:ClearDefenderTaskTarget(Defender) +local DefenderTask=self:GetDefenderTask(Defender) +if Defender and Defender:IsAlive()and DefenderTask then +local Target=DefenderTask.Target +local Message="Clearing ("..DefenderTask.Type..") " +Message=Message..Defender:GetName() +if Target then +Message=Message..(Target and(" from "..Target.Index.." ["..Target.Set:Count().."]"))or"" +end +self:F({Target=Message}) +end +if Defender and DefenderTask and DefenderTask.Target then +DefenderTask.Target=nil +end +return self +end +function AI_A2A_DISPATCHER:SetDefenderTask(SquadronName,Defender,Type,Fsm,Target) +self:F({SquadronName=SquadronName,Defender=Defender:GetName(),Type=Type,Target=Target}) +self.DefenderTasks[Defender]=self.DefenderTasks[Defender]or{} +self.DefenderTasks[Defender].Type=Type +self.DefenderTasks[Defender].Fsm=Fsm +self.DefenderTasks[Defender].SquadronName=SquadronName +if Target then +self:SetDefenderTaskTarget(Defender,Target) +end +return self +end +function AI_A2A_DISPATCHER:SetDefenderTaskTarget(Defender,AttackerDetection) +local Message="("..self.DefenderTasks[Defender].Type..") " +Message=Message..Defender:GetName() +Message=Message..(AttackerDetection and(" target "..AttackerDetection.Index.." ["..AttackerDetection.Set:Count().."]"))or"" +self:F({AttackerDetection=Message}) +if AttackerDetection then +self.DefenderTasks[Defender].Target=AttackerDetection +end +return self +end +function AI_A2A_DISPATCHER:SetSquadron(SquadronName,AirbaseName,TemplatePrefixes,ResourceCount) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +DefenderSquadron.Name=SquadronName +DefenderSquadron.Airbase=AIRBASE:FindByName(AirbaseName) +DefenderSquadron.AirbaseName=DefenderSquadron.Airbase:GetName() +if not DefenderSquadron.Airbase then +error("Cannot find airbase with name:"..AirbaseName) +end +DefenderSquadron.Spawn={} +if type(TemplatePrefixes)=="string"then +local SpawnTemplate=TemplatePrefixes +self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate) +DefenderSquadron.Spawn[1]=self.DefenderSpawns[SpawnTemplate] +else +for TemplateID,SpawnTemplate in pairs(TemplatePrefixes)do +self.DefenderSpawns[SpawnTemplate]=self.DefenderSpawns[SpawnTemplate]or SPAWN:New(SpawnTemplate) +DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1]=self.DefenderSpawns[SpawnTemplate] +end +end +DefenderSquadron.ResourceCount=ResourceCount +DefenderSquadron.TemplatePrefixes=TemplatePrefixes +DefenderSquadron.Captured=false +self:SetSquadronLanguage(SquadronName,"EN") +self:F({Squadron={SquadronName,AirbaseName,TemplatePrefixes,ResourceCount}}) +return self +end +function AI_A2A_DISPATCHER:GetSquadron(SquadronName) +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +if not DefenderSquadron then +error("Unknown Squadron:"..SquadronName) +end +return DefenderSquadron +end +function AI_A2A_DISPATCHER:SetSquadronVisible(SquadronName) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Uncontrolled=true +DefenderSquadron.Grouping=1 +local nfreeparking=DefenderSquadron.Airbase:GetFreeParkingSpotsNumber(AIRBASE.TerminalType.FighterAircraft,true) +DefenderSquadron.ResourceCount=DefenderSquadron.ResourceCount or nfreeparking +DefenderSquadron.ResourceCount=math.min(DefenderSquadron.ResourceCount,nfreeparking) +for SpawnTemplate,_DefenderSpawn in pairs(self.DefenderSpawns)do +local DefenderSpawn=_DefenderSpawn +DefenderSpawn:InitUnControlled(true) +end +end +function AI_A2A_DISPATCHER:IsSquadronVisible(SquadronName) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +if DefenderSquadron then +return DefenderSquadron.Uncontrolled==true +end +return nil +end +function AI_A2A_DISPATCHER:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +local Cap=self.DefenderSquadrons[SquadronName].Cap +Cap.Name=SquadronName +Cap.EngageMinSpeed=EngageMinSpeed +Cap.EngageMaxSpeed=EngageMaxSpeed +Cap.EngageFloorAltitude=EngageFloorAltitude +Cap.EngageCeilingAltitude=EngageCeilingAltitude +Cap.Zone=Zone +Cap.PatrolMinSpeed=PatrolMinSpeed +Cap.PatrolMaxSpeed=PatrolMaxSpeed +Cap.PatrolFloorAltitude=PatrolFloorAltitude +Cap.PatrolCeilingAltitude=PatrolCeilingAltitude +Cap.PatrolAltType=PatrolAltType +Cap.EngageAltType=EngageAltType +self:SetSquadronCapInterval(SquadronName,self.DefenderDefault.CapLimit,self.DefenderDefault.CapMinSeconds,self.DefenderDefault.CapMaxSeconds,1) +self:I({CAP={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageAltType}}) +local RecceSet=self.Detection:GetDetectionSet() +RecceSet:FilterPrefixes(DefenderSquadron.TemplatePrefixes) +RecceSet:FilterStart() +self.Detection:SetFriendlyPrefixes(DefenderSquadron.TemplatePrefixes) +return self +end +function AI_A2A_DISPATCHER:SetSquadronCap(SquadronName,Zone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +return self:SetSquadronCap2(SquadronName,EngageMinSpeed,EngageMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,AltType) +end +function AI_A2A_DISPATCHER:SetSquadronCapInterval(SquadronName,CapLimit,LowInterval,HighInterval,Probability) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +local Cap=self.DefenderSquadrons[SquadronName].Cap +if Cap then +Cap.LowInterval=LowInterval or 180 +Cap.HighInterval=HighInterval or 600 +Cap.Probability=Probability or 1 +Cap.CapLimit=CapLimit or 1 +Cap.Scheduler=Cap.Scheduler or SCHEDULER:New(self) +local Scheduler=Cap.Scheduler +local ScheduleID=Cap.ScheduleID +local Variance=(Cap.HighInterval-Cap.LowInterval)/2 +local Repeat=Cap.LowInterval+Variance +local Randomization=Variance/Repeat +local Start=math.random(1,Cap.HighInterval) +if ScheduleID then +Scheduler:Stop(ScheduleID) +end +Cap.ScheduleID=Scheduler:Schedule(self,self.SchedulerCAP,{SquadronName},Start,Repeat,Randomization) +else +error("This squadron does not exist:"..SquadronName) +end +end +function AI_A2A_DISPATCHER:GetCAPDelay(SquadronName) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +local Cap=self.DefenderSquadrons[SquadronName].Cap +if Cap then +return math.random(Cap.LowInterval,Cap.HighInterval) +else +error("This squadron does not exist:"..SquadronName) +end +end +function AI_A2A_DISPATCHER:CanCAP(SquadronName) +self:F({SquadronName=SquadronName}) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:GetSquadron(SquadronName) +if DefenderSquadron.Captured==false then +if(not DefenderSquadron.ResourceCount)or(DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount>0)then +local Cap=DefenderSquadron.Cap +if Cap then +local CapCount=self:CountCapAirborne(SquadronName) +self:F({CapCount=CapCount}) +if CapCount0)then +local Gci=DefenderSquadron.Gci +if Gci then +return DefenderSquadron +end +end +end +return nil +end +function AI_A2A_DISPATCHER:SetSquadronGci2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{} +local Intercept=self.DefenderSquadrons[SquadronName].Gci +Intercept.Name=SquadronName +Intercept.EngageMinSpeed=EngageMinSpeed +Intercept.EngageMaxSpeed=EngageMaxSpeed +Intercept.EngageFloorAltitude=EngageFloorAltitude +Intercept.EngageCeilingAltitude=EngageCeilingAltitude +Intercept.EngageAltType=EngageAltType +self:I({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2A_DISPATCHER:SetSquadronGci(SquadronName,EngageMinSpeed,EngageMaxSpeed) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Gci=self.DefenderSquadrons[SquadronName].Gci or{} +local Intercept=self.DefenderSquadrons[SquadronName].Gci +Intercept.Name=SquadronName +Intercept.EngageMinSpeed=EngageMinSpeed +Intercept.EngageMaxSpeed=EngageMaxSpeed +self:F({GCI={SquadronName,EngageMinSpeed,EngageMaxSpeed}}) +end +function AI_A2A_DISPATCHER:SetDefaultOverhead(Overhead) +self.DefenderDefault.Overhead=Overhead +return self +end +function AI_A2A_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Overhead=Overhead +return self +end +function AI_A2A_DISPATCHER:SetDefaultGrouping(Grouping) +self.DefenderDefault.Grouping=Grouping +return self +end +function AI_A2A_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Grouping=Grouping +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoff(Takeoff) +self.DefenderDefault.Takeoff=Takeoff +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Takeoff=Takeoff +return self +end +function AI_A2A_DISPATCHER:GetDefaultTakeoff() +return self.DefenderDefault.Takeoff +end +function AI_A2A_DISPATCHER:GetSquadronTakeoff(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Air) +return self +end +function AI_A2A_DISPATCHER:SetSendMessages(onoff) +self.SetSendPlayerMessages=onoff +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Air) +if TakeoffAltitude then +self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +end +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() +self:SetDefaultTakeoff(AI_A2A_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2A_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) +self.DefenderDefault.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2A_DISPATCHER:SetDefaultLanding(Landing) +self.DefenderDefault.Landing=Landing +return self +end +function AI_A2A_DISPATCHER:SetSquadronLanding(SquadronName,Landing) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Landing=Landing +return self +end +function AI_A2A_DISPATCHER:GetDefaultLanding() +return self.DefenderDefault.Landing +end +function AI_A2A_DISPATCHER:GetSquadronLanding(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Landing or self.DefenderDefault.Landing +end +function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() +self:SetDefaultLanding(AI_A2A_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2A_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2A_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) +self.DefenderDefault.FuelThreshold=FuelThreshold +return self +end +function AI_A2A_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.FuelThreshold=FuelThreshold +return self +end +function AI_A2A_DISPATCHER:SetDefaultTanker(TankerName) +self.DefenderDefault.TankerName=TankerName +return self +end +function AI_A2A_DISPATCHER:SetSquadronTanker(SquadronName,TankerName) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TankerName=TankerName +return self +end +function AI_A2A_DISPATCHER:SetSquadronLanguage(SquadronName,Language) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Language=Language +if DefenderSquadron.RadioQueue then +DefenderSquadron.RadioQueue:SetLanguage(Language) +end +return self +end +function AI_A2A_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.RadioFrequency=RadioFrequency +DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM +DefenderSquadron.RadioPower=RadioPower or 100 +if DefenderSquadron.RadioQueue then +DefenderSquadron.RadioQueue:Stop() +end +DefenderSquadron.RadioQueue=nil +DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation) +DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower +DefenderSquadron.RadioQueue:Start(0.5) +DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language) +end +function AI_A2A_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +self.Defenders[DefenderName]=Squadron +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount-Size +end +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize() +end +self.Defenders[DefenderName]=nil +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2A_DISPATCHER:GetSquadronFromDefender(Defender) +self.Defenders=self.Defenders or{} +if Defender~=nil then +local DefenderName=Defender:GetName() +self:F({DefenderName=DefenderName}) +return self.Defenders[DefenderName] +else +return nil +end +end +function AI_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +if DetectedItem.IsDetected==false then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function AI_A2A_DISPATCHER:CountCapAirborne(SquadronName) +local CapCount=0 +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +if DefenderSquadron then +for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do +if DefenderTask.SquadronName==SquadronName then +if DefenderTask.Type=="CAP"then +if AIGroup and AIGroup:IsAlive()then +if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling")or DefenderTask.Fsm:Is("Started")then +CapCount=CapCount+1 +end +end +end +end +end +end +return CapCount +end +function AI_A2A_DISPATCHER:CountDefendersEngaged(AttackerDetection) +local DefenderCount=0 +local DetectedSet=AttackerDetection.Set +local DefenderTasks=self:GetDefenderTasks() +for DefenderGroup,DefenderTask in pairs(DefenderTasks)do +local Defender=DefenderGroup +local DefenderTaskTarget=DefenderTask.Target +local DefenderSquadronName=DefenderTask.SquadronName +if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then +local Squadron=self:GetSquadron(DefenderSquadronName) +local SquadronOverhead=Squadron.Overhead or self.DefenderDefault.Overhead +local DefenderSize=Defender:GetInitialSize() +if DefenderSize then +DefenderCount=DefenderCount+DefenderSize/SquadronOverhead +self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) +else +DefenderCount=0 +end +end +end +self:F({DefenderCount=DefenderCount}) +return DefenderCount +end +function AI_A2A_DISPATCHER:CountDefendersToBeEngaged(AttackerDetection,DefenderCount) +local Friendlies=nil +local AttackerSet=AttackerDetection.Set +local AttackerCount=AttackerSet:Count() +local DefenderFriendlies=self:GetAIFriendliesNearBy(AttackerDetection) +for FriendlyDistance,AIFriendly in UTILS.spairs(DefenderFriendlies or{})do +if AttackerCount>DefenderCount then +local Friendly=AIFriendly:GetGroup() +if Friendly and Friendly:IsAlive()then +local DefenderTask=self:GetDefenderTask(Friendly) +if DefenderTask then +if DefenderTask.Type=="CAP"or DefenderTask.Type=="GCI"then +if DefenderTask.Target==nil then +if DefenderTask.Fsm:Is("Returning")or DefenderTask.Fsm:Is("Patrolling")then +Friendlies=Friendlies or{} +Friendlies[Friendly]=Friendly +DefenderCount=DefenderCount+Friendly:GetSize() +self:F({Friendly=Friendly:GetName(),FriendlyDistance=FriendlyDistance}) +end +end +end +end +end +else +break +end +end +return Friendlies +end +function AI_A2A_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded) +local SquadronName=DefenderSquadron.Name +DefendersNeeded=DefendersNeeded or 4 +local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping +DefenderGrouping=(DefenderGrouping0 then +local id=math.random(n) +local Defender=self.uncontrolled[SquadronName][id].group +Defender:StartUncontrolled() +DefenderGrouping=self.uncontrolled[SquadronName][id].grouping +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +table.remove(self.uncontrolled[SquadronName],id) +return Defender,DefenderGrouping +else +return nil,0 +end +local TemplateID=math.random(1,#DefenderSquadron.Spawn) +else +local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)] +if DefenderGrouping then +Spawn:InitGrouping(DefenderGrouping) +else +Spawn:InitGrouping() +end +local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) +local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude) +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +return Defender,DefenderGrouping +end +return nil,nil +end +function AI_A2A_DISPATCHER:onafterCAP(From,Event,To,SquadronName) +self:F({SquadronName=SquadronName}) +self.DefenderSquadrons[SquadronName]=self.DefenderSquadrons[SquadronName]or{} +self.DefenderSquadrons[SquadronName].Cap=self.DefenderSquadrons[SquadronName].Cap or{} +local DefenderSquadron=self:CanCAP(SquadronName) +if DefenderSquadron then +local Cap=DefenderSquadron.Cap +if Cap then +local DefenderCAP,DefenderGrouping=self:ResourceActivate(DefenderSquadron) +if DefenderCAP then +local AI_A2A_Fsm=AI_A2A_CAP:New2(DefenderCAP,Cap.EngageMinSpeed,Cap.EngageMaxSpeed,Cap.EngageFloorAltitude,Cap.EngageCeilingAltitude,Cap.EngageAltType,Cap.Zone,Cap.PatrolMinSpeed,Cap.PatrolMaxSpeed,Cap.PatrolFloorAltitude,Cap.PatrolCeilingAltitude,Cap.PatrolAltType) +AI_A2A_Fsm:SetDispatcher(self) +AI_A2A_Fsm:SetHomeAirbase(DefenderSquadron.Airbase) +AI_A2A_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) +AI_A2A_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) +AI_A2A_Fsm:SetDisengageRadius(self.DisengageRadius) +AI_A2A_Fsm:SetTanker(DefenderSquadron.TankerName or self.DefenderDefault.TankerName) +if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then +AI_A2A_Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, +DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, +DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, +DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, +DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, +DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, +DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates) +end +AI_A2A_Fsm:Start() +self:SetDefenderTask(SquadronName,DefenderCAP,"CAP",AI_A2A_Fsm) +function AI_A2A_Fsm:onafterTakeoff(DefenderGroup,From,Event,To) +if DefenderGroup and DefenderGroup:IsAlive()then +self:F({"CAP Takeoff",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2A_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron then +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." Wheels up.",DefenderGroup) +end +AI_A2A_Fsm:__Patrol(2) +end +end +end +function AI_A2A_Fsm:onafterPatrolRoute(DefenderGroup,From,Event,To) +if DefenderGroup and DefenderGroup:IsAlive()then +self:F({"CAP PatrolRoute",DefenderGroup:GetName()}) +self:GetParent(self).onafterPatrolRoute(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", patrolling.",DefenderGroup) +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +end +function AI_A2A_Fsm:onafterRTB(DefenderGroup,From,Event,To) +if DefenderGroup and DefenderGroup:IsAlive()then +self:F({"CAP RTB",DefenderGroup:GetName()}) +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup) +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +end +function AI_A2A_Fsm:onafterHome(Defender,From,Event,To,Action) +if Defender and Defender:IsAlive()then +self:F({"CAP Home",Defender:GetName()}) +self:GetParent(self).onafterHome(self,Defender,From,Event,To) +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(Defender) +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) +Defender:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) +Defender:Destroy() +Dispatcher:ParkDefender(Squadron) +end +end +end +end +end +end +end +function AI_A2A_DISPATCHER:onafterENGAGE(From,Event,To,AttackerDetection,Defenders) +self:F("ENGAGING Detection ID="..tostring(AttackerDetection.ID)) +if Defenders then +for DefenderID,Defender in pairs(Defenders)do +local Fsm=self:GetDefenderTaskFsm(Defender) +Fsm:EngageRoute(AttackerDetection.Set) +self:SetDefenderTaskTarget(Defender,AttackerDetection) +end +end +end +function AI_A2A_DISPATCHER:onafterGCI(From,Event,To,AttackerDetection,DefendersMissing,DefenderFriendlies) +self:F("GCI Detection ID="..tostring(AttackerDetection.ID)) +self:F({From,Event,To,AttackerDetection.Index,DefendersMissing,DefenderFriendlies}) +local AttackerSet=AttackerDetection.Set +local AttackerUnit=AttackerSet:GetFirst() +if AttackerUnit and AttackerUnit:IsAlive()then +local AttackerCount=AttackerSet:Count() +local DefenderCount=0 +for DefenderID,DefenderGroup in pairs(DefenderFriendlies or{})do +local Fsm=self:GetDefenderTaskFsm(DefenderGroup) +Fsm:__EngageRoute(0.1,AttackerSet) +self:SetDefenderTaskTarget(DefenderGroup,AttackerDetection) +DefenderCount=DefenderCount+DefenderGroup:GetSize() +end +self:F({DefenderCount=DefenderCount,DefendersMissing=DefendersMissing}) +DefenderCount=DefendersMissing +local ClosestDistance=0 +local ClosestDefenderSquadronName=nil +local BreakLoop=false +while(DefenderCount>0 and not BreakLoop)do +self:F({DefenderSquadrons=self.DefenderSquadrons}) +for SquadronName,DefenderSquadron in pairs(self.DefenderSquadrons or{})do +self:F({GCI=DefenderSquadron.Gci}) +for InterceptID,Intercept in pairs(DefenderSquadron.Gci or{})do +self:F({DefenderSquadron}) +local SpawnCoord=DefenderSquadron.Airbase:GetCoordinate() +local AttackerCoord=AttackerUnit:GetCoordinate() +local InterceptCoord=AttackerDetection.InterceptCoord +self:F({InterceptCoord=InterceptCoord}) +if InterceptCoord then +local InterceptDistance=SpawnCoord:Get2DDistance(InterceptCoord) +local AirbaseDistance=SpawnCoord:Get2DDistance(AttackerCoord) +self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord}) +if ClosestDistance==0 or InterceptDistanceDefenderSquadron.ResourceCount then +DefendersNeeded=DefenderSquadron.ResourceCount +BreakLoop=true +end +while(DefendersNeeded>0)do +local DefenderGCI,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded) +DefendersNeeded=DefendersNeeded-DefenderGrouping +if DefenderGCI then +DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead +local Fsm=AI_A2A_GCI:New2(DefenderGCI,Gci.EngageMinSpeed,Gci.EngageMaxSpeed,Gci.EngageFloorAltitude,Gci.EngageCeilingAltitude,Gci.EngageAltType) +Fsm:SetDispatcher(self) +Fsm:SetHomeAirbase(DefenderSquadron.Airbase) +Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) +Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) +Fsm:SetDisengageRadius(self.DisengageRadius) +Fsm:Start() +self:SetDefenderTask(ClosestDefenderSquadronName,DefenderGCI,"GCI",Fsm,AttackerDetection) +function Fsm:onafterTakeoff(DefenderGroup,From,Event,To) +self:F({"GCI Birth",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup) +if DefenderTarget then +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." wheels up.",DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." колеÑ�а вверх.",DefenderGroup) +end +Fsm:EngageRoute(DefenderTarget.Set) +end +end +function Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"GCI Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and AttackSetUnit:Count()>0 then +local FirstUnit=AttackSetUnit:GetFirst() +local Coordinate=FirstUnit:GetCoordinate() +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", intercepting bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", перехват Ñ�амолетов в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +elseif Squadron.Language=="DE"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", Eindringlinge abfangen bei"..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +end +end +self:GetParent(Fsm).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit) +end +function Fsm:onafterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"GCI Engage",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron and AttackSetUnit:Count()>0 then +local FirstUnit=AttackSetUnit:GetFirst() +local Coordinate=FirstUnit:GetCoordinate() +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging bogeys at "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", захватывающие Ñ�амолеты в "..Coordinate:ToStringA2A(DefenderGroup,nil,Squadron.Language),DefenderGroup) +end +end +self:GetParent(Fsm).onafterEngage(self,DefenderGroup,From,Event,To,AttackSetUnit) +end +function Fsm:onafterRTB(DefenderGroup,From,Event,To) +self:F({"GCI RTB",DefenderGroup:GetName()}) +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron then +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." returning to base.",DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", возвращаÑ�Ñ�ÑŒ на базу.",DefenderGroup) +end +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +function Fsm:onafterLostControl(Defender,From,Event,To) +self:F({"GCI LostControl",Defender:GetName()}) +self:GetParent(self).onafterHome(self,Defender,From,Event,To) +local Dispatcher=Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(Defender) +if Defender:IsAboveRunway()then +Dispatcher:RemoveDefenderFromSquadron(Squadron,Defender) +Defender:Destroy() +end +end +function Fsm:onafterHome(DefenderGroup,From,Event,To,Action) +self:F({"GCI Home",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron.Language=="EN"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName.." landing at base.",DefenderGroup) +elseif Squadron.Language=="RU"and self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", захватывающие Ñ�амолеты в поÑ�адка на базу.",DefenderGroup) +end +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2A_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +Dispatcher:ParkDefender(Squadron) +end +end +end +end +end +else +BreakLoop=true +break +end +else +break +end +end +end +end +function AI_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem) +self:F({DetectedItem.ItemID}) +local DefenderCount=self:CountDefendersEngaged(DetectedItem) +local DefenderGroups=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount) +self:F({DefenderCount=DefenderCount}) +if DefenderGroups and DetectedItem.IsDetected==true then +return DefenderGroups +end +return nil +end +function AI_A2A_DISPATCHER:EvaluateGCI(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:Count() +local DefenderCount=self:CountDefendersEngaged(DetectedItem) +local DefendersMissing=AttackerCount-DefenderCount +self:F({AttackerCount=AttackerCount,DefenderCount=DefenderCount,DefendersMissing=DefendersMissing}) +local Friendlies=self:CountDefendersToBeEngaged(DetectedItem,DefenderCount) +if DetectedItem.IsDetected==true then +return DefendersMissing,Friendlies +end +return nil,nil +end +function AI_A2A_DISPATCHER:Order(DetectedItem) +local detection=self.Detection +local ShortestDistance=999999999 +local AttackCoordinate=detection:GetDetectedItemCoordinate(DetectedItem) +if AttackCoordinate then +for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do +self:T({DefenderSquadron=DefenderSquadron.Name}) +local Airbase=DefenderSquadron.Airbase +local AirbaseCoordinate=Airbase:GetCoordinate() +local EvaluateDistance=AttackCoordinate:Get2DDistance(AirbaseCoordinate) +if EvaluateDistance<=ShortestDistance then +ShortestDistance=EvaluateDistance +end +end +end +return ShortestDistance +end +function AI_A2A_DISPATCHER:ShowTacticalDisplay(Detection) +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local TaskReport=REPORT:New() +local Report=REPORT:New("Tactical Overview:") +local DefenderGroupCount=0 +for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b)return self:Order(t[a])0 then +self:F({DefendersMissing=DefendersMissing}) +self:GCI(DetectedItem,DefendersMissing,Friendlies) +end +end +end +if self.TacticalDisplay then +self:ShowTacticalDisplay(Detection) +end +return true +end +end +do +function AI_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) +local PlayerTypes={} +local PlayersCount=0 +if PlayersNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do +local PlayerUnit=PlayerUnitData +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerUnit:IsAirPlane()and PlayerName~=nil then +local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() +PlayersCount=PlayersCount+1 +local PlayerType=PlayerUnit:GetTypeName() +PlayerTypes[PlayerName]=PlayerType +if DetectedTreatLevel0 then +for PlayerName,PlayerType in pairs(PlayerTypes)do +PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) +end +else +PlayerTypesReport:Add("-") +end +return PlayersCount,PlayerTypesReport +end +function AI_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem) +local FriendlyTypes={} +local FriendliesCount=0 +if FriendlyUnitsNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do +local FriendlyUnit=FriendlyUnitData +if FriendlyUnit:IsAirPlane()then +local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() +FriendliesCount=FriendliesCount+1 +local FriendlyType=FriendlyUnit:GetTypeName() +FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 +if DetectedTreatLevel0 then +for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do +FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) +end +else +FriendlyTypesReport:Add("-") +end +return FriendliesCount,FriendlyTypesReport +end +function AI_A2A_DISPATCHER:SchedulerCAP(SquadronName) +self:CAP(SquadronName) +end +end +do +AI_A2A_GCICAP={ +ClassName="AI_A2A_GCICAP", +Detection=nil, +} +function AI_A2A_GCICAP:New(EWRPrefixes,TemplatePrefixes,CapPrefixes,CapLimit,GroupingRadius,EngageRadius,GciRadius,ResourceCount) +local EWRSetGroup=SET_GROUP:New() +EWRSetGroup:FilterPrefixes(EWRPrefixes) +EWRSetGroup:FilterStart() +local Detection=DETECTION_AREAS:New(EWRSetGroup,GroupingRadius or 30000) +local self=BASE:Inherit(self,AI_A2A_DISPATCHER:New(Detection)) +self:SetEngageRadius(EngageRadius) +self:SetGciRadius(GciRadius) +local EWRFirst=EWRSetGroup:GetFirst() +local EWRCoalition=EWRFirst:GetCoalition() +local AirbaseNames={} +for AirbaseID,AirbaseData in pairs(_DATABASE.AIRBASES)do +local Airbase=AirbaseData +local AirbaseName=Airbase:GetName() +if Airbase:GetCoalition()==EWRCoalition then +table.insert(AirbaseNames,AirbaseName) +end +end +self.Templates=SET_GROUP +:New() +:FilterPrefixes(TemplatePrefixes) +:FilterOnce() +self:I({Airbases=AirbaseNames}) +self:I("Defining Templates for Airbases ...") +for AirbaseID,AirbaseName in pairs(AirbaseNames)do +local Airbase=_DATABASE:FindAirbase(AirbaseName) +local AirbaseName=Airbase:GetName() +local AirbaseCoord=Airbase:GetCoordinate() +local AirbaseZone=ZONE_RADIUS:New("Airbase",AirbaseCoord:GetVec2(),3000) +local Templates=nil +self:I({Airbase=AirbaseName}) +for TemplateID,Template in pairs(self.Templates:GetSet())do +local Template=Template +local TemplateCoord=Template:GetCoordinate() +if AirbaseZone:IsVec2InZone(TemplateCoord:GetVec2())then +Templates=Templates or{} +table.insert(Templates,Template:GetName()) +self:I({Template=Template:GetName()}) +end +end +if Templates then +self:SetSquadron(AirbaseName,AirbaseName,Templates,ResourceCount) +end +end +self.CAPTemplates=SET_GROUP:New() +self.CAPTemplates:FilterPrefixes(CapPrefixes) +self.CAPTemplates:FilterOnce() +self:I("Setting up CAP ...") +for CAPID,CAPTemplate in pairs(self.CAPTemplates:GetSet())do +local CAPZone=ZONE_POLYGON:New(CAPTemplate:GetName(),CAPTemplate) +local AirbaseDistance=99999999 +local AirbaseClosest=nil +self:I({CAPZoneGroup=CAPID}) +for AirbaseID,AirbaseName in pairs(AirbaseNames)do +local Airbase=_DATABASE:FindAirbase(AirbaseName) +local AirbaseName=Airbase:GetName() +local AirbaseCoord=Airbase:GetCoordinate() +local Squadron=self.DefenderSquadrons[AirbaseName] +if Squadron then +local Distance=AirbaseCoord:Get2DDistance(CAPZone:GetCoordinate()) +self:I({AirbaseDistance=Distance}) +if Distance0)then +local Patrol=DefenderSquadron[DefenseTaskType] +if Patrol and Patrol.Patrol==true then +local PatrolCount=self:CountPatrolAirborne(SquadronName,DefenseTaskType) +self:F({PatrolCount=PatrolCount,PatrolLimit=Patrol.PatrolLimit,PatrolProbability=Patrol.Probability}) +if PatrolCount0)then +if DefenderSquadron[DefenseTaskType]and(DefenderSquadron[DefenseTaskType].Defend==true)then +return DefenderSquadron,DefenderSquadron[DefenseTaskType] +end +end +end +return nil +end +function AI_A2G_DISPATCHER:SetSquadronEngageLimit(SquadronName,EngageLimit,DefenseTaskType) +local DefenderSquadron=self:GetSquadron(SquadronName) +local Defense=DefenderSquadron[DefenseTaskType] +if Defense then +Defense.EngageLimit=EngageLimit or 1 +else +error("This squadron does not exist:"..SquadronName) +end +end +function AI_A2G_DISPATCHER:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.SEAD=DefenderSquadron.SEAD or{} +local Sead=DefenderSquadron.SEAD +Sead.Name=SquadronName +Sead.EngageMinSpeed=EngageMinSpeed +Sead.EngageMaxSpeed=EngageMaxSpeed +Sead.EngageFloorAltitude=EngageFloorAltitude or 500 +Sead.EngageCeilingAltitude=EngageCeilingAltitude or 1000 +Sead.EngageAltType=EngageAltType +Sead.Defend=true +self:I({SEAD={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +return self +end +function AI_A2G_DISPATCHER:SetSquadronSead(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) +return self:SetSquadronSead2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") +end +function AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit(SquadronName,EngageLimit) +self:SetSquadronEngageLimit(SquadronName,EngageLimit,"SEAD") +end +function AI_A2G_DISPATCHER:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.SEAD=DefenderSquadron.SEAD or{} +local SeadPatrol=DefenderSquadron.SEAD +SeadPatrol.Name=SquadronName +SeadPatrol.Zone=Zone +SeadPatrol.PatrolFloorAltitude=PatrolFloorAltitude +SeadPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude +SeadPatrol.EngageFloorAltitude=EngageFloorAltitude +SeadPatrol.EngageCeilingAltitude=EngageCeilingAltitude +SeadPatrol.PatrolMinSpeed=PatrolMinSpeed +SeadPatrol.PatrolMaxSpeed=PatrolMaxSpeed +SeadPatrol.EngageMinSpeed=EngageMinSpeed +SeadPatrol.EngageMaxSpeed=EngageMaxSpeed +SeadPatrol.PatrolAltType=PatrolAltType +SeadPatrol.EngageAltType=EngageAltType +SeadPatrol.Patrol=true +self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"SEAD") +self:I({SEAD={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2G_DISPATCHER:SetSquadronSeadPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +self:SetSquadronSeadPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) +end +function AI_A2G_DISPATCHER:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.CAS=DefenderSquadron.CAS or{} +local Cas=DefenderSquadron.CAS +Cas.Name=SquadronName +Cas.EngageMinSpeed=EngageMinSpeed +Cas.EngageMaxSpeed=EngageMaxSpeed +Cas.EngageFloorAltitude=EngageFloorAltitude or 500 +Cas.EngageCeilingAltitude=EngageCeilingAltitude or 1000 +Cas.EngageAltType=EngageAltType +Cas.Defend=true +self:I({CAS={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +return self +end +function AI_A2G_DISPATCHER:SetSquadronCas(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) +return self:SetSquadronCas2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") +end +function AI_A2G_DISPATCHER:SetSquadronCasEngageLimit(SquadronName,EngageLimit) +self:SetSquadronEngageLimit(SquadronName,EngageLimit,"CAS") +end +function AI_A2G_DISPATCHER:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.CAS=DefenderSquadron.CAS or{} +local CasPatrol=DefenderSquadron.CAS +CasPatrol.Name=SquadronName +CasPatrol.Zone=Zone +CasPatrol.PatrolFloorAltitude=PatrolFloorAltitude +CasPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude +CasPatrol.EngageFloorAltitude=EngageFloorAltitude +CasPatrol.EngageCeilingAltitude=EngageCeilingAltitude +CasPatrol.PatrolMinSpeed=PatrolMinSpeed +CasPatrol.PatrolMaxSpeed=PatrolMaxSpeed +CasPatrol.EngageMinSpeed=EngageMinSpeed +CasPatrol.EngageMaxSpeed=EngageMaxSpeed +CasPatrol.PatrolAltType=PatrolAltType +CasPatrol.EngageAltType=EngageAltType +CasPatrol.Patrol=true +self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"CAS") +self:I({CAS={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2G_DISPATCHER:SetSquadronCasPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +self:SetSquadronCasPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) +end +function AI_A2G_DISPATCHER:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.BAI=DefenderSquadron.BAI or{} +local Bai=DefenderSquadron.BAI +Bai.Name=SquadronName +Bai.EngageMinSpeed=EngageMinSpeed +Bai.EngageMaxSpeed=EngageMaxSpeed +Bai.EngageFloorAltitude=EngageFloorAltitude or 500 +Bai.EngageCeilingAltitude=EngageCeilingAltitude or 1000 +Bai.EngageAltType=EngageAltType +Bai.Defend=true +self:I({BAI={SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +return self +end +function AI_A2G_DISPATCHER:SetSquadronBai(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude) +return self:SetSquadronBai2(SquadronName,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,"RADIO") +end +function AI_A2G_DISPATCHER:SetSquadronBaiEngageLimit(SquadronName,EngageLimit) +self:SetSquadronEngageLimit(SquadronName,EngageLimit,"BAI") +end +function AI_A2G_DISPATCHER:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.BAI=DefenderSquadron.BAI or{} +local BaiPatrol=DefenderSquadron.BAI +BaiPatrol.Name=SquadronName +BaiPatrol.Zone=Zone +BaiPatrol.PatrolFloorAltitude=PatrolFloorAltitude +BaiPatrol.PatrolCeilingAltitude=PatrolCeilingAltitude +BaiPatrol.EngageFloorAltitude=EngageFloorAltitude +BaiPatrol.EngageCeilingAltitude=EngageCeilingAltitude +BaiPatrol.PatrolMinSpeed=PatrolMinSpeed +BaiPatrol.PatrolMaxSpeed=PatrolMaxSpeed +BaiPatrol.EngageMinSpeed=EngageMinSpeed +BaiPatrol.EngageMaxSpeed=EngageMaxSpeed +BaiPatrol.PatrolAltType=PatrolAltType +BaiPatrol.EngageAltType=EngageAltType +BaiPatrol.Patrol=true +self:SetSquadronPatrolInterval(SquadronName,self.DefenderDefault.PatrolLimit,self.DefenderDefault.PatrolMinSeconds,self.DefenderDefault.PatrolMaxSeconds,1,"BAI") +self:I({BAI={Zone:GetName(),PatrolMinSpeed,PatrolMaxSpeed,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolAltType,EngageMinSpeed,EngageMaxSpeed,EngageFloorAltitude,EngageCeilingAltitude,EngageAltType}}) +end +function AI_A2G_DISPATCHER:SetSquadronBaiPatrol(SquadronName,Zone,FloorAltitude,CeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageMinSpeed,EngageMaxSpeed,AltType) +self:SetSquadronBaiPatrol2(SquadronName,Zone,PatrolMinSpeed,PatrolMaxSpeed,FloorAltitude,CeilingAltitude,AltType,EngageMinSpeed,EngageMaxSpeed,FloorAltitude,CeilingAltitude,AltType) +end +function AI_A2G_DISPATCHER:SetDefaultOverhead(Overhead) +self.DefenderDefault.Overhead=Overhead +return self +end +function AI_A2G_DISPATCHER:SetSquadronOverhead(SquadronName,Overhead) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Overhead=Overhead +return self +end +function AI_A2G_DISPATCHER:GetSquadronOverhead(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Overhead or self.DefenderDefault.Overhead +end +function AI_A2G_DISPATCHER:SetDefaultGrouping(Grouping) +self.DefenderDefault.Grouping=Grouping +return self +end +function AI_A2G_DISPATCHER:SetSquadronGrouping(SquadronName,Grouping) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Grouping=Grouping +return self +end +function AI_A2G_DISPATCHER:SetSquadronEngageProbability(SquadronName,EngageProbability) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.EngageProbability=EngageProbability +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoff(Takeoff) +self.DefenderDefault.Takeoff=Takeoff +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoff(SquadronName,Takeoff) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Takeoff=Takeoff +return self +end +function AI_A2G_DISPATCHER:GetDefaultTakeoff() +return self.DefenderDefault.Takeoff +end +function AI_A2G_DISPATCHER:GetSquadronTakeoff(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffInAir() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Air) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffInAir(SquadronName,TakeoffAltitude) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Air) +if TakeoffAltitude then +self:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +end +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffFromRunway() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffFromRunway(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Runway) +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingHot() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingHot(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Hot) +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingCold() +self:SetDefaultTakeoff(AI_A2G_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingCold(SquadronName) +self:SetSquadronTakeoff(SquadronName,AI_A2G_DISPATCHER.Takeoff.Cold) +return self +end +function AI_A2G_DISPATCHER:SetDefaultTakeoffInAirAltitude(TakeoffAltitude) +self.DefenderDefault.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2G_DISPATCHER:SetSquadronTakeoffInAirAltitude(SquadronName,TakeoffAltitude) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TakeoffAltitude=TakeoffAltitude +return self +end +function AI_A2G_DISPATCHER:SetDefaultLanding(Landing) +self.DefenderDefault.Landing=Landing +return self +end +function AI_A2G_DISPATCHER:SetSquadronLanding(SquadronName,Landing) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.Landing=Landing +return self +end +function AI_A2G_DISPATCHER:GetDefaultLanding() +return self.DefenderDefault.Landing +end +function AI_A2G_DISPATCHER:GetSquadronLanding(SquadronName) +local DefenderSquadron=self:GetSquadron(SquadronName) +return DefenderSquadron.Landing or self.DefenderDefault.Landing +end +function AI_A2G_DISPATCHER:SetDefaultLandingNearAirbase() +self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2G_DISPATCHER:SetSquadronLandingNearAirbase(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.NearAirbase) +return self +end +function AI_A2G_DISPATCHER:SetDefaultLandingAtRunway() +self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2G_DISPATCHER:SetSquadronLandingAtRunway(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtRunway) +return self +end +function AI_A2G_DISPATCHER:SetDefaultLandingAtEngineShutdown() +self:SetDefaultLanding(AI_A2G_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2G_DISPATCHER:SetSquadronLandingAtEngineShutdown(SquadronName) +self:SetSquadronLanding(SquadronName,AI_A2G_DISPATCHER.Landing.AtEngineShutdown) +return self +end +function AI_A2G_DISPATCHER:SetDefaultFuelThreshold(FuelThreshold) +self.DefenderDefault.FuelThreshold=FuelThreshold +return self +end +function AI_A2G_DISPATCHER:SetSquadronFuelThreshold(SquadronName,FuelThreshold) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.FuelThreshold=FuelThreshold +return self +end +function AI_A2G_DISPATCHER:SetDefaultTanker(TankerName) +self.DefenderDefault.TankerName=TankerName +return self +end +function AI_A2G_DISPATCHER:SetSquadronTanker(SquadronName,TankerName) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.TankerName=TankerName +return self +end +function AI_A2G_DISPATCHER:SetSquadronRadioFrequency(SquadronName,RadioFrequency,RadioModulation,RadioPower) +local DefenderSquadron=self:GetSquadron(SquadronName) +DefenderSquadron.RadioFrequency=RadioFrequency +DefenderSquadron.RadioModulation=RadioModulation or radio.modulation.AM +DefenderSquadron.RadioPower=RadioPower or 100 +if DefenderSquadron.RadioQueue then +DefenderSquadron.RadioQueue:Stop() +end +DefenderSquadron.RadioQueue=nil +DefenderSquadron.RadioQueue=RADIOSPEECH:New(DefenderSquadron.RadioFrequency,DefenderSquadron.RadioModulation) +DefenderSquadron.RadioQueue.power=DefenderSquadron.RadioPower +DefenderSquadron.RadioQueue:Start(0.5) +DefenderSquadron.RadioQueue:SetLanguage(DefenderSquadron.Language) +end +function AI_A2G_DISPATCHER:AddDefenderToSquadron(Squadron,Defender,Size) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +self.Defenders[DefenderName]=Squadron +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount-Size +end +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2G_DISPATCHER:RemoveDefenderFromSquadron(Squadron,Defender) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +if Squadron.ResourceCount then +Squadron.ResourceCount=Squadron.ResourceCount+Defender:GetSize() +end +self.Defenders[DefenderName]=nil +self:F({DefenderName=DefenderName,SquadronResourceCount=Squadron.ResourceCount}) +end +function AI_A2G_DISPATCHER:GetSquadronFromDefender(Defender) +self.Defenders=self.Defenders or{} +local DefenderName=Defender:GetName() +self:F({DefenderName=DefenderName}) +return self.Defenders[DefenderName] +end +function AI_A2G_DISPATCHER:CountPatrolAirborne(SquadronName,DefenseTaskType) +local PatrolCount=0 +local DefenderSquadron=self.DefenderSquadrons[SquadronName] +if DefenderSquadron then +for AIGroup,DefenderTask in pairs(self:GetDefenderTasks())do +if DefenderTask.SquadronName==SquadronName then +if DefenderTask.Type==DefenseTaskType then +if AIGroup:IsAlive()then +if DefenderTask.Fsm:Is("Patrolling")or DefenderTask.Fsm:Is("Engaging")or DefenderTask.Fsm:Is("Refuelling") +or DefenderTask.Fsm:Is("Started")then +PatrolCount=PatrolCount+1 +end +end +end +end +end +end +return PatrolCount +end +function AI_A2G_DISPATCHER:CountDefendersEngaged(AttackerDetection,AttackerCount) +local DefendersEngaged=0 +local DefendersTotal=0 +local AttackerSet=AttackerDetection.Set +local DefendersMissing=AttackerCount +local DefenderTasks=self:GetDefenderTasks() +for DefenderGroup,DefenderTask in pairs(DefenderTasks)do +local Defender=DefenderGroup +local DefenderTaskTarget=DefenderTask.Target +local DefenderSquadronName=DefenderTask.SquadronName +local DefenderSize=DefenderTask.Size +if DefenderTask.Target then +self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) +DefendersTotal=DefendersTotal+DefenderSize +if DefenderTaskTarget and DefenderTaskTarget.Index==AttackerDetection.Index then +local SquadronOverhead=self:GetSquadronOverhead(DefenderSquadronName) +self:F({SquadronOverhead=SquadronOverhead}) +if DefenderSize then +DefendersEngaged=DefendersEngaged+DefenderSize +DefendersMissing=DefendersMissing-DefenderSize/SquadronOverhead +self:F("Defender Group Name: "..Defender:GetName()..", Size: "..DefenderSize) +else +DefendersEngaged=0 +end +end +end +end +for QueueID,QueueItem in pairs(self.DefenseQueue)do +local QueueItem=QueueItem +if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID==AttackerDetection.ItemID then +DefendersMissing=DefendersMissing-QueueItem.DefendersNeeded/QueueItem.DefenderSquadron.Overhead +self:F({QueueItemName=QueueItem.Defense,QueueItem_ItemID=QueueItem.AttackerDetection.ItemID,DetectedItem=AttackerDetection.ItemID,DefendersMissing=DefendersMissing}) +end +end +self:F({DefenderCount=DefendersEngaged}) +return DefendersTotal,DefendersEngaged,DefendersMissing +end +function AI_A2G_DISPATCHER:CountDefenders(AttackerDetection,DefenderCount,DefenderTaskType) +local Friendlies=nil +local AttackerSet=AttackerDetection.Set +local AttackerCount=AttackerSet:Count() +local DefenderFriendlies=self:GetDefenderFriendliesNearBy(AttackerDetection) +for FriendlyDistance,DefenderFriendlyUnit in UTILS.spairs(DefenderFriendlies or{})do +if AttackerCount>DefenderCount then +local FriendlyGroup=DefenderFriendlyUnit:GetGroup() +if FriendlyGroup and FriendlyGroup:IsAlive()then +local DefenderTask=self:GetDefenderTask(FriendlyGroup) +if DefenderTask then +if DefenderTaskType==DefenderTask.Type then +if DefenderTask.Target==nil then +if DefenderTask.Fsm:Is("Returning") +or DefenderTask.Fsm:Is("Patrolling")then +Friendlies=Friendlies or{} +Friendlies[FriendlyGroup]=FriendlyGroup +DefenderCount=DefenderCount+FriendlyGroup:GetSize() +self:F({Friendly=FriendlyGroup:GetName(),FriendlyDistance=FriendlyDistance}) +end +end +end +end +end +else +break +end +end +return Friendlies +end +function AI_A2G_DISPATCHER:ResourceActivate(DefenderSquadron,DefendersNeeded) +local SquadronName=DefenderSquadron.Name +DefendersNeeded=DefendersNeeded or 4 +local DefenderGrouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping +DefenderGrouping=(DefenderGroupingDefenderGrouping then +break +end +end +if DefenderPatrolTemplate then +local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) +local SpawnGroup=GROUP:Register(DefenderName) +DefenderPatrolTemplate.lateActivation=nil +DefenderPatrolTemplate.uncontrolled=nil +local Takeoff=self:GetSquadronTakeoff(SquadronName) +DefenderPatrolTemplate.route.points[1].type=GROUPTEMPLATE.Takeoff[Takeoff][1] +DefenderPatrolTemplate.route.points[1].action=GROUPTEMPLATE.Takeoff[Takeoff][2] +local Defender=_DATABASE:Spawn(DefenderPatrolTemplate) +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +Defender:Activate() +return Defender,DefenderGrouping +end +else +local Spawn=DefenderSquadron.Spawn[math.random(1,#DefenderSquadron.Spawn)] +if DefenderGrouping then +Spawn:InitGrouping(DefenderGrouping) +else +Spawn:InitGrouping() +end +local TakeoffMethod=self:GetSquadronTakeoff(SquadronName) +local Defender=Spawn:SpawnAtAirbase(DefenderSquadron.Airbase,TakeoffMethod,DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude) +self:AddDefenderToSquadron(DefenderSquadron,Defender,DefenderGrouping) +return Defender,DefenderGrouping +end +return nil,nil +end +function AI_A2G_DISPATCHER:onafterPatrol(From,Event,To,SquadronName,DefenseTaskType) +local DefenderSquadron,Patrol=self:CanPatrol(SquadronName,DefenseTaskType) +if DefenderSquadron then +local DefendersNeeded +local DefendersGrouping=(DefenderSquadron.Grouping or self.DefenderDefault.Grouping) +if DefenderSquadron.ResourceCount==nil then +DefendersNeeded=DefendersGrouping +else +if DefenderSquadron.ResourceCount>=DefendersGrouping then +DefendersNeeded=DefendersGrouping +else +DefendersNeeded=DefenderSquadron.ResourceCount +end +end +if Patrol then +self:ResourceQueue(true,DefenderSquadron,DefendersNeeded,Patrol,DefenseTaskType,nil,SquadronName) +end +end +end +function AI_A2G_DISPATCHER:ResourceQueue(Patrol,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName) +self:F({DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName}) +local DefenseQueueItem={} +DefenseQueueItem.Patrol=Patrol +DefenseQueueItem.DefenderSquadron=DefenderSquadron +DefenseQueueItem.DefendersNeeded=DefendersNeeded +DefenseQueueItem.Defense=Defense +DefenseQueueItem.DefenseTaskType=DefenseTaskType +DefenseQueueItem.AttackerDetection=AttackerDetection +DefenseQueueItem.SquadronName=SquadronName +table.insert(self.DefenseQueue,DefenseQueueItem) +self:F({QueueItems=#self.DefenseQueue}) +end +function AI_A2G_DISPATCHER:ResourceTakeoff() +for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do +self:F({DefenseQueueID}) +end +for SquadronName,Squadron in pairs(self.DefenderSquadrons)do +if#self.DefenseQueue>0 then +self:F({SquadronName,Squadron.Name,Squadron.TakeoffTime,Squadron.TakeoffInterval,timer.getTime()}) +local DefenseQueueItem=self.DefenseQueue[1] +self:F({DefenderSquadron=DefenseQueueItem.DefenderSquadron}) +if DefenseQueueItem.SquadronName==SquadronName then +if Squadron.TakeoffTime+Squadron.TakeoffInterval0 then +local FirstUnit=AttackSetUnit:GetFirst() +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", moving on to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +end +end +function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"Engage Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local FirstUnit=AttackSetUnit:GetFirst() +if FirstUnit then +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +end +end +function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To) +self:F({"RTB",DefenderGroup:GetName()}) +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup) +end +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To) +self:F({"LostControl",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", lost control.") +end +if DefenderGroup:IsAboveRunway()then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +end +function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action) +self:F({"Home",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup) +end +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +Dispatcher:ResourcePark(Squadron,DefenderGroup) +end +end +end +end +function AI_A2G_DISPATCHER:ResourceEngage(DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,AttackerDetection,SquadronName) +self:F({DefenderSquadron=DefenderSquadron}) +self:F({DefendersNeeded=DefendersNeeded}) +self:F({Defense=Defense}) +self:F({DefenseTaskType=DefenseTaskType}) +self:F({AttackerDetection=AttackerDetection}) +self:F({SquadronName=SquadronName}) +local DefenderGroup,DefenderGrouping=self:ResourceActivate(DefenderSquadron,DefendersNeeded) +if DefenderGroup then +local AI_A2G_ENGAGE={SEAD=AI_A2G_SEAD,BAI=AI_A2G_BAI,CAS=AI_A2G_CAS} +local AI_A2G_Fsm=AI_A2G_ENGAGE[DefenseTaskType]:New(DefenderGroup,Defense.EngageMinSpeed,Defense.EngageMaxSpeed,Defense.EngageFloorAltitude,Defense.EngageCeilingAltitude,Defense.EngageAltType) +AI_A2G_Fsm:SetDispatcher(self) +AI_A2G_Fsm:SetHomeAirbase(DefenderSquadron.Airbase) +AI_A2G_Fsm:SetFuelThreshold(DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold,60) +AI_A2G_Fsm:SetDamageThreshold(self.DefenderDefault.DamageThreshold) +AI_A2G_Fsm:SetDisengageRadius(self.DisengageRadius) +AI_A2G_Fsm:Start() +self:SetDefenderTask(SquadronName,DefenderGroup,DefenseTaskType,AI_A2G_Fsm,AttackerDetection,DefenderGrouping) +function AI_A2G_Fsm:onafterTakeoff(DefenderGroup,From,Event,To) +self:F({"Defender Birth",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local DefenderTarget=Dispatcher:GetDefenderTaskTarget(DefenderGroup) +self:F({DefenderTarget=DefenderTarget}) +if DefenderTarget then +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", wheels up.",DefenderGroup) +end +AI_A2G_Fsm:EngageRoute(DefenderTarget.Set) +end +end +function AI_A2G_Fsm:onafterEngageRoute(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"Engage Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if Squadron then +local FirstUnit=AttackSetUnit:GetFirst() +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", on route to ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +end +self:GetParent(self).onafterEngageRoute(self,DefenderGroup,From,Event,To,AttackSetUnit) +end +function AI_A2G_Fsm:OnAfterEngage(DefenderGroup,From,Event,To,AttackSetUnit) +self:F({"Engage Route",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +local FirstUnit=AttackSetUnit:GetFirst() +if FirstUnit then +local Coordinate=FirstUnit:GetCoordinate() +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", engaging ground target at "..Coordinate:ToStringA2G(DefenderGroup),DefenderGroup) +end +end +end +function AI_A2G_Fsm:onafterRTB(DefenderGroup,From,Event,To) +self:F({"Defender RTB",DefenderGroup:GetName()}) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", returning to base.",DefenderGroup) +end +self:GetParent(self).onafterRTB(self,DefenderGroup,From,Event,To) +Dispatcher:ClearDefenderTaskTarget(DefenderGroup) +end +function AI_A2G_Fsm:onafterLostControl(DefenderGroup,From,Event,To) +self:F({"Defender LostControl",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=AI_A2G_Fsm:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,"Squadron "..Squadron.Name..", "..DefenderName.." lost control.") +end +if DefenderGroup:IsAboveRunway()then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +end +function AI_A2G_Fsm:onafterHome(DefenderGroup,From,Event,To,Action) +self:F({"Defender Home",DefenderGroup:GetName()}) +self:GetParent(self).onafterHome(self,DefenderGroup,From,Event,To) +local DefenderName=DefenderGroup:GetCallsign() +local Dispatcher=self:GetDispatcher() +local Squadron=Dispatcher:GetSquadronFromDefender(DefenderGroup) +if self.SetSendPlayerMessages then +Dispatcher:MessageToPlayers(Squadron,DefenderName..", landing at base.",DefenderGroup) +end +if Action and Action=="Destroy"then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +end +if Dispatcher:GetSquadronLanding(Squadron.Name)==AI_A2G_DISPATCHER.Landing.NearAirbase then +Dispatcher:RemoveDefenderFromSquadron(Squadron,DefenderGroup) +DefenderGroup:Destroy() +Dispatcher:ResourcePark(Squadron,DefenderGroup) +end +end +end +end +function AI_A2G_DISPATCHER:onafterEngage(From,Event,To,AttackerDetection,Defenders) +if Defenders then +for DefenderID,Defender in pairs(Defenders or{})do +local Fsm=self:GetDefenderTaskFsm(Defender) +Fsm:Engage(AttackerDetection.Set) +self:SetDefenderTaskTarget(Defender,AttackerDetection) +end +end +end +function AI_A2G_DISPATCHER:HasDefenseLine(DefenseCoordinate,DetectedItem) +local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) +local c1=DefenseCoordinate +local c2=AttackCoordinate +local a=c1.z-c2.z +local b=c2.x-c1.x +local c=c1.x*c2.z-c2.x*c1.z +local ok=true +for AttackItemID,CheckAttackItem in pairs(self.Detection:GetDetectedItems())do +if AttackItemID~=DetectedItem.ID then +local CheckAttackCoordinate=self.Detection:GetDetectedItemCoordinate(CheckAttackItem) +local x=CheckAttackCoordinate.x +local y=CheckAttackCoordinate.z +local r=5000 +local IntersectDistance=(math.abs(a*x+b*y+c))/math.sqrt(a*a+b*b) +self:F({IntersectDistance=IntersectDistance,x=x,y=y}) +local IntersectAttackDistance=CheckAttackCoordinate:Get2DDistance(DefenseCoordinate) +self:F({IntersectAttackDistance=IntersectAttackDistance,EvaluateDistance=EvaluateDistance}) +if IntersectDistance0 and not BreakLoop)do +self:F({DefenderSquadrons=self.DefenderSquadrons}) +for SquadronName,DefenderSquadron in UTILS.rpairs(self.DefenderSquadrons or{})do +if DefenderSquadron[DefenseTaskType]then +local AirbaseCoordinate=DefenderSquadron.Airbase:GetCoordinate() +local AttackerCoord=AttackerUnit:GetCoordinate() +local InterceptCoord=DetectedItem.InterceptCoord +self:F({InterceptCoord=InterceptCoord}) +if InterceptCoord then +local InterceptDistance=AirbaseCoordinate:Get2DDistance(InterceptCoord) +local AirbaseDistance=AirbaseCoordinate:Get2DDistance(AttackerCoord) +self:F({InterceptDistance=InterceptDistance,AirbaseDistance=AirbaseDistance,InterceptCoord=InterceptCoord}) +if AirbaseDistance<=self.DefenseRadius then +local HasDefenseLine=self:HasDefenseLine(AirbaseCoordinate,DetectedItem) +if HasDefenseLine==true then +local EngageProbability=(DefenderSquadron.EngageProbability or 1) +local Probability=math.random() +if Probability=DefendersLimit then +DefendersNeeded=0 +BreakLoop=true +else +if DefendersTotal+DefendersNeeded>DefendersLimit then +DefendersNeeded=DefendersLimit-DefendersTotal +end +end +end +if DefenderSquadron.ResourceCount and DefendersNeeded>DefenderSquadron.ResourceCount then +DefendersNeeded=DefenderSquadron.ResourceCount +BreakLoop=true +end +while(DefendersNeeded>0)do +self:ResourceQueue(false,DefenderSquadron,DefendersNeeded,Defense,DefenseTaskType,DetectedItem,EngageSquadronName) +DefendersNeeded=DefendersNeeded-DefenderGrouping +DefenderCount=DefenderCount-DefenderGrouping/DefenderOverhead +end +else +BreakLoop=true +break +end +else +break +end +end +end +end +function AI_A2G_DISPATCHER:Evaluate_SEAD(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:HasSEAD() +if(AttackerCount>0)then +local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) +self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"SEAD") +if DetectedItem.IsDetected==true then +return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups +end +end +return 0,0,0 +end +function AI_A2G_DISPATCHER:Evaluate_CAS(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:Count() +local AttackerRadarCount=AttackerSet:HasSEAD() +local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local IsCas=(AttackerRadarCount==0)and(IsFriendliesNearBy==true) +if IsCas==true then +local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) +self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"CAS") +if DetectedItem.IsDetected==true then +return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups +end +end +return 0,0,0 +end +function AI_A2G_DISPATCHER:Evaluate_BAI(DetectedItem) +self:F({DetectedItem.ItemID}) +local AttackerSet=DetectedItem.Set +local AttackerCount=AttackerSet:Count() +local AttackerRadarCount=AttackerSet:HasSEAD() +local IsFriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local IsBai=(AttackerRadarCount==0)and(IsFriendliesNearBy==false) +if IsBai==true then +local DefendersTotal,DefendersEngaged,DefendersMissing=self:CountDefendersEngaged(DetectedItem,AttackerCount) +self:F({AttackerCount=AttackerCount,DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +local DefenderGroups=self:CountDefenders(DetectedItem,DefendersEngaged,"BAI") +if DetectedItem.IsDetected==true then +return DefendersTotal,DefendersEngaged,DefendersMissing,DefenderGroups +end +end +return 0,0,0 +end +function AI_A2G_DISPATCHER:Keys(DetectedItem) +self:F({DetectedItem=DetectedItem}) +local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local ShortestDistance=999999999 +for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do +local DefenseCoordinate=DefenseCoordinate +local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) +if EvaluateDistance<=ShortestDistance then +ShortestDistance=EvaluateDistance +end +end +return ShortestDistance +end +function AI_A2G_DISPATCHER:Order(DetectedItem) +local AttackCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local ShortestDistance=999999999 +for DefenseCoordinateName,DefenseCoordinate in pairs(self.DefenseCoordinates)do +local DefenseCoordinate=DefenseCoordinate +local EvaluateDistance=AttackCoordinate:Get2DDistance(DefenseCoordinate) +if EvaluateDistance<=ShortestDistance then +ShortestDistance=EvaluateDistance +end +end +return ShortestDistance +end +function AI_A2G_DISPATCHER:ShowTacticalDisplay(Detection) +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local TaskReport=REPORT:New() +local DefenseTotal=0 +local Report=REPORT:New("\nTactical Overview") +local DefenderGroupCount=0 +local DefendersTotal=0 +for DetectedItemID,DetectedItem in UTILS.spairs(Detection:GetDetectedItems(),function(t,a,b)return self:Order(t[a])0 then +self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"SEAD") +end +end +do +local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_CAS(DetectedItem) +if DefendersMissing>0 then +self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"CAS") +end +end +do +local DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies=self:Evaluate_BAI(DetectedItem) +if DefendersMissing>0 then +self:F({DefendersTotal=DefendersTotal,DefendersEngaged=DefendersEngaged,DefendersMissing=DefendersMissing}) +self:Defend(DetectedItem,DefendersTotal,DefendersEngaged,DefendersMissing,Friendlies,"BAI") +end +end +end +for Defender,DefenderTask in pairs(self:GetDefenderTasks())do +local Defender=Defender +if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then +DefenseTotal=DefenseTotal+1 +end +end +for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do +local DefenseQueueItem=DefenseQueueItem +if DefenseQueueItem.AttackerDetection and DefenseQueueItem.AttackerDetection.Index and DefenseQueueItem.AttackerDetection.Index==DetectedItem.Index then +DefenseTotal=DefenseTotal+1 +end +end +if self.TacticalDisplay then +local ThreatLevel=DetectedItem.Set:CalculateThreatLevelA2G() +Report:Add(string.format(" - %1s%s ( %4s ): ( #%d - %4s ) %s",(DetectedItem.IsDetected==true)and"!"or" ",DetectedItem.ItemID,DetectedItem.Index,DetectedItem.Set:Count(),DetectedItem.Type or" --- ",string.rep("■",ThreatLevel))) +for Defender,DefenderTask in pairs(self:GetDefenderTasks())do +local Defender=Defender +if DefenderTask.Target and DefenderTask.Target.Index==DetectedItem.Index then +if Defender:IsAlive()then +DefenderGroupCount=DefenderGroupCount+1 +local Fuel=Defender:GetFuelMin()*100 +local Damage=Defender:GetLife()/Defender:GetLife0()*100 +Report:Add(string.format(" - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", +Defender:GetName(), +DefenderTask.Type, +DefenderTask.Fsm:GetState(), +Defender:GetSize(), +Fuel, +Damage, +Defender:HasTask()==true and"Executing"or"Idle")) +end +end +end +end +end +end +if self.TacticalDisplay then +Report:Add("\n - No Targets:") +local TaskCount=0 +for Defender,DefenderTask in pairs(self:GetDefenderTasks())do +TaskCount=TaskCount+1 +local Defender=Defender +if not DefenderTask.Target then +if Defender:IsAlive()then +local DefenderHasTask=Defender:HasTask() +local Fuel=Defender:GetFuelMin()*100 +local Damage=Defender:GetLife()/Defender:GetLife0()*100 +DefenderGroupCount=DefenderGroupCount+1 +Report:Add(string.format(" - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", +Defender:GetName(), +DefenderTask.Type, +DefenderTask.Fsm:GetState(), +Defender:GetSize(), +Fuel, +Damage, +Defender:HasTask()==true and"Executing"or"Idle")) +end +end +end +Report:Add(string.format("\n - %d Tasks - %d Defender Groups",TaskCount,DefenderGroupCount)) +Report:Add(string.format("\n - %d Queued Aircraft Launches",#self.DefenseQueue)) +for DefenseQueueID,DefenseQueueItem in pairs(self.DefenseQueue)do +local DefenseQueueItem=DefenseQueueItem +Report:Add(string.format(" - %s - %s",DefenseQueueItem.SquadronName,DefenseQueueItem.DefenderSquadron.TakeoffTime,DefenseQueueItem.DefenderSquadron.TakeoffInterval)) +end +Report:Add(string.format("\n - Squadron Resources: ",#self.DefenseQueue)) +for DefenderSquadronName,DefenderSquadron in pairs(self.DefenderSquadrons)do +Report:Add(string.format(" - %s - %s",DefenderSquadronName,DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount)or"n/a")) +end +self:F(Report:Text("\n")) +trigger.action.outText(Report:Text("\n"),25) +end +return true +end +end +do +function AI_A2G_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) +local PlayerTypes={} +local PlayersCount=0 +if PlayersNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do +local PlayerUnit=PlayerUnitData +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerUnit:IsAirPlane()and PlayerName~=nil then +local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() +PlayersCount=PlayersCount+1 +local PlayerType=PlayerUnit:GetTypeName() +PlayerTypes[PlayerName]=PlayerType +if DetectedTreatLevel0 then +for PlayerName,PlayerType in pairs(PlayerTypes)do +PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) +end +else +PlayerTypesReport:Add("-") +end +return PlayersCount,PlayerTypesReport +end +function AI_A2G_DISPATCHER:GetFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem) +local FriendlyTypes={} +local FriendliesCount=0 +if FriendlyUnitsNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do +local FriendlyUnit=FriendlyUnitData +if FriendlyUnit:IsAirPlane()then +local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() +FriendliesCount=FriendliesCount+1 +local FriendlyType=FriendlyUnit:GetTypeName() +FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 +if DetectedTreatLevel0 then +for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do +FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) +end +else +FriendlyTypesReport:Add("-") +end +return FriendliesCount,FriendlyTypesReport +end +function AI_A2G_DISPATCHER:SchedulerPatrol(SquadronName) +local PatrolTaskTypes={"SEAD","CAS","BAI"} +local PatrolTaskType=PatrolTaskTypes[math.random(1,3)] +self:Patrol(SquadronName,PatrolTaskType) +end +function AI_A2G_DISPATCHER:SetSendMessages(onoff) +self.SetSendPlayerMessages=onoff +end +end +AI_PATROL_ZONE={ +ClassName="AI_PATROL_ZONE", +} +function AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New()) +self.PatrolZone=PatrolZone +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +self.PatrolAltType=PatrolAltType or"BARO" +self:SetRefreshTimeInterval(30) +self.CheckStatus=true +self:ManageFuel(.2,60) +self:ManageDamage(1) +self.DetectedUnits={} +self:SetStartState("None") +self:AddTransition("*","Stop","Stopped") +self:AddTransition("None","Start","Patrolling") +self:AddTransition("Patrolling","Route","Patrolling") +self:AddTransition("*","Status","*") +self:AddTransition("*","Detect","*") +self:AddTransition("*","Detected","*") +self:AddTransition("*","RTB","Returning") +self:AddTransition("*","Reset","Patrolling") +self:AddTransition("*","Eject","*") +self:AddTransition("*","Crash","Crashed") +self:AddTransition("*","PilotDead","*") +return self +end +function AI_PATROL_ZONE:SetSpeed(PatrolMinSpeed,PatrolMaxSpeed) +self:F2({PatrolMinSpeed,PatrolMaxSpeed}) +self.PatrolMinSpeed=PatrolMinSpeed +self.PatrolMaxSpeed=PatrolMaxSpeed +end +function AI_PATROL_ZONE:SetAltitude(PatrolFloorAltitude,PatrolCeilingAltitude) +self:F2({PatrolFloorAltitude,PatrolCeilingAltitude}) +self.PatrolFloorAltitude=PatrolFloorAltitude +self.PatrolCeilingAltitude=PatrolCeilingAltitude +end +function AI_PATROL_ZONE:SetDetectionOn() +self:F2() +self.DetectOn=true +end +function AI_PATROL_ZONE:SetDetectionOff() +self:F2() +self.DetectOn=false +end +function AI_PATROL_ZONE:SetStatusOff() +self:F2() +self.CheckStatus=false +end +function AI_PATROL_ZONE:SetDetectionActivated() +self:F2() +self:ClearDetectedUnits() +self.DetectActivated=true +self:__Detect(-self.DetectInterval) +end +function AI_PATROL_ZONE:SetDetectionDeactivated() +self:F2() +self:ClearDetectedUnits() +self.DetectActivated=false +end +function AI_PATROL_ZONE:SetRefreshTimeInterval(Seconds) +self:F2() +if Seconds then +self.DetectInterval=Seconds +else +self.DetectInterval=30 +end +end +function AI_PATROL_ZONE:SetDetectionZone(DetectionZone) +self:F2() +if DetectionZone then +self.DetectZone=DetectionZone +else +self.DetectZone=nil +end +end +function AI_PATROL_ZONE:GetDetectedUnits() +self:F2() +return self.DetectedUnits +end +function AI_PATROL_ZONE:ClearDetectedUnits() +self:F2() +self.DetectedUnits={} +end +function AI_PATROL_ZONE:ManageFuel(PatrolFuelThresholdPercentage,PatrolOutOfFuelOrbitTime) +self.PatrolFuelThresholdPercentage=PatrolFuelThresholdPercentage +self.PatrolOutOfFuelOrbitTime=PatrolOutOfFuelOrbitTime +return self +end +function AI_PATROL_ZONE:ManageDamage(PatrolDamageThreshold) +self.PatrolManageDamage=true +self.PatrolDamageThreshold=PatrolDamageThreshold +return self +end +function AI_PATROL_ZONE:onafterStart(Controllable,From,Event,To) +self:F2() +self:__Route(1) +self:__Status(60) +self:SetDetectionActivated() +self:HandleEvent(EVENTS.PilotDead,self.OnPilotDead) +self:HandleEvent(EVENTS.Crash,self.OnCrash) +self:HandleEvent(EVENTS.Ejection,self.OnEjection) +Controllable:OptionROEHoldFire() +Controllable:OptionROTVertical() +self.Controllable:OnReSpawn( +function(PatrolGroup) +self:T("ReSpawn") +self:__Reset(1) +self:__Route(5) +end +) +self:SetDetectionOn() +end +function AI_PATROL_ZONE:onbeforeDetect(Controllable,From,Event,To) +return self.DetectOn and self.DetectActivated +end +function AI_PATROL_ZONE:onafterDetect(Controllable,From,Event,To) +local Detected=false +local DetectedTargets=Controllable:GetDetectedTargets() +for TargetID,Target in pairs(DetectedTargets or{})do +local TargetObject=Target.object +if TargetObject and TargetObject:isExist()and TargetObject.id_<50000000 then +local TargetUnit=UNIT:Find(TargetObject) +if TargetUnit and TargetUnit:IsAlive()then +local TargetUnitName=TargetUnit:GetName() +if self.DetectionZone then +if TargetUnit:IsInZone(self.DetectionZone)then +self:T({"Detected ",TargetUnit}) +if self.DetectedUnits[TargetUnit]==nil then +self.DetectedUnits[TargetUnit]=true +end +Detected=true +end +else +if self.DetectedUnits[TargetUnit]==nil then +self.DetectedUnits[TargetUnit]=true +end +Detected=true +end +end +end +end +self:__Detect(-self.DetectInterval) +if Detected==true then +self:__Detected(1.5) +end +end +function AI_PATROL_ZONE:_NewPatrolRoute(AIControllable) +local PatrolZone=AIControllable:GetState(AIControllable,"PatrolZone") +PatrolZone:__Route(1) +end +function AI_PATROL_ZONE:onafterRoute(Controllable,From,Event,To) +self:F2() +if From=="RTB"then +return +end +if self.Controllable:IsAlive()then +local PatrolRoute={} +if self.Controllable:InAir()==false then +self:T("Not in the air, finding route path within PatrolZone") +local CurrentVec2=self.Controllable:GetVec2() +local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude() +local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToPatrolZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TakeOffParking, +POINT_VEC3.RoutePointAction.FromParkingArea, +ToPatrolZoneSpeed, +true +) +PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint +else +self:T("In the air, finding route path within PatrolZone") +local CurrentVec2=self.Controllable:GetVec2() +local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude() +local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToPatrolZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +ToPatrolZoneSpeed, +true +) +PatrolRoute[#PatrolRoute+1]=CurrentRoutePoint +end +local ToTargetVec2=self.PatrolZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetAltitude=math.random(self.PatrolFloorAltitude,self.PatrolCeilingAltitude) +local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed}) +local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) +local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +ToTargetSpeed, +true +) +PatrolRoute[#PatrolRoute+1]=ToTargetRoutePoint +self.Controllable:WayPointInitialize(PatrolRoute) +self.Controllable:SetState(self.Controllable,"PatrolZone",self) +self.Controllable:WayPointFunction(#PatrolRoute,1,"AI_PATROL_ZONE:_NewPatrolRoute") +self.Controllable:WayPointExecute(1,2) +end +end +function AI_PATROL_ZONE:onbeforeStatus() +return self.CheckStatus +end +function AI_PATROL_ZONE:onafterStatus() +self:F2() +if self.Controllable and self.Controllable:IsAlive()then +local RTB=false +local Fuel=self.Controllable:GetFuelMin() +if Fuel Engaging') +self:__Engage(1) +end +end +end +function AI_CAP_ZONE:onafterAbort(Controllable,From,Event,To) +Controllable:ClearTasks() +self:__Route(1) +end +function AI_CAP_ZONE:onafterEngage(Controllable,From,Event,To) +if Controllable and Controllable:IsAlive()then +local EngageRoute={} +local CurrentVec2=self.Controllable:GetVec2() +local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude() +local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToEngageZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +ToEngageZoneSpeed, +true +) +EngageRoute[#EngageRoute+1]=CurrentRoutePoint +local ToTargetVec2=self.PatrolZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetAltitude=math.random(self.EngageFloorAltitude,self.EngageCeilingAltitude) +local ToTargetSpeed=math.random(self.PatrolMinSpeed,self.PatrolMaxSpeed) +self:T2({self.PatrolMinSpeed,self.PatrolMaxSpeed,ToTargetSpeed}) +local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,ToTargetAltitude,ToTargetVec2.y) +local ToPatrolRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +ToTargetSpeed, +true +) +EngageRoute[#EngageRoute+1]=ToPatrolRoutePoint +Controllable:OptionROEOpenFire() +Controllable:OptionROTEvadeFire() +local AttackTasks={} +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +self:T({DetectedUnit,DetectedUnit:IsAlive(),DetectedUnit:IsAir()}) +if DetectedUnit:IsAlive()and DetectedUnit:IsAir()then +if self.EngageZone then +if DetectedUnit:IsInZone(self.EngageZone)then +self:F({"Within Zone and Engaging ",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) +end +else +if self.EngageRange then +if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3())<=self.EngageRange then +self:F({"Within Range and Engaging",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) +end +else +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit) +end +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +if#AttackTasks==0 then +self:F("No targets found -> Going back to Patrolling") +self:__Abort(1) +self:__Route(1) +self:SetDetectionActivated() +else +AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAP_ZONE.EngageRoute",self) +EngageRoute[1].task=Controllable:TaskCombo(AttackTasks) +self:SetDetectionDeactivated() +end +Controllable:Route(EngageRoute,0.5) +end +end +function AI_CAP_ZONE:onafterAccomplish(Controllable,From,Event,To) +self.Accomplished=true +self:SetDetectionOff() +end +function AI_CAP_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) +if EventData.IniUnit then +self.DetectedUnits[EventData.IniUnit]=nil +end +end +function AI_CAP_ZONE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then +self:__Destroy(1,EventData) +end +end +end +AI_CAS_ZONE={ +ClassName="AI_CAS_ZONE", +} +function AI_CAS_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType) +local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)) +self.EngageZone=EngageZone +self.Accomplished=false +self:SetDetectionZone(self.EngageZone) +self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging") +self:AddTransition("Engaging","Target","Engaging") +self:AddTransition("Engaging","Fired","Engaging") +self:AddTransition("*","Destroy","*") +self:AddTransition("Engaging","Abort","Patrolling") +self:AddTransition("Engaging","Accomplish","Patrolling") +return self +end +function AI_CAS_ZONE:SetEngageZone(EngageZone) +self:F2() +if EngageZone then +self.EngageZone=EngageZone +else +self.EngageZone=nil +end +end +function AI_CAS_ZONE:onafterStart(Controllable,From,Event,To) +self:GetParent(self).onafterStart(self,Controllable,From,Event,To) +self:HandleEvent(EVENTS.Dead) +self:SetDetectionDeactivated() +end +function AI_CAS_ZONE.EngageRoute(EngageGroup,Fsm) +EngageGroup:F({"AI_CAS_ZONE.EngageRoute:",EngageGroup:GetName()}) +if EngageGroup:IsAlive()then +Fsm:__Engage(1,Fsm.EngageSpeed,Fsm.EngageAltitude,Fsm.EngageWeaponExpend,Fsm.EngageAttackQty,Fsm.EngageDirection) +end +end +function AI_CAS_ZONE:onbeforeEngage(Controllable,From,Event,To) +if self.Accomplished==true then +return false +end +end +function AI_CAS_ZONE:onafterTarget(Controllable,From,Event,To) +if Controllable:IsAlive()then +local AttackTasks={} +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +if Detected==true then +self:F({"Target: ",DetectedUnit}) +self.DetectedUnits[DetectedUnit]=false +local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil) +self.Controllable:PushTask(AttackTask,1) +end +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +self:__Target(-10) +end +end +function AI_CAS_ZONE:onafterAbort(Controllable,From,Event,To) +Controllable:ClearTasks() +self:__Route(1) +end +function AI_CAS_ZONE:onafterEngage(Controllable,From,Event,To, +EngageSpeed, +EngageAltitude, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection) +self:F("onafterEngage") +self.EngageSpeed=EngageSpeed or 400 +self.EngageAltitude=EngageAltitude or 2000 +self.EngageWeaponExpend=EngageWeaponExpend +self.EngageAttackQty=EngageAttackQty +self.EngageDirection=EngageDirection +if Controllable:IsAlive()then +Controllable:OptionROEOpenFire() +Controllable:OptionROTVertical() +local EngageRoute={} +local CurrentVec2=self.Controllable:GetVec2() +local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude() +local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToEngageZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=CurrentRoutePoint +local AttackTasks={} +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +self:T(DetectedUnit) +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +self:F({"Engaging ",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackUnit(DetectedUnit, +true, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection +) +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +AttackTasks[#AttackTasks+1]=Controllable:TaskFunction("AI_CAS_ZONE.EngageRoute",self) +EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks) +local ToTargetVec2=self.EngageZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) +local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=ToTargetRoutePoint +Controllable:Route(EngageRoute,0.5) +self:SetRefreshTimeInterval(2) +self:SetDetectionActivated() +self:__Target(-2) +end +end +function AI_CAS_ZONE:onafterAccomplish(Controllable,From,Event,To) +self.Accomplished=true +self:SetDetectionDeactivated() +end +function AI_CAS_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) +if EventData.IniUnit then +self.DetectedUnits[EventData.IniUnit]=nil +end +end +function AI_CAS_ZONE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then +self:__Destroy(1,EventData) +end +end +end +AI_BAI_ZONE={ +ClassName="AI_BAI_ZONE", +} +function AI_BAI_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,EngageZone,PatrolAltType) +local self=BASE:Inherit(self,AI_PATROL_ZONE:New(PatrolZone,PatrolFloorAltitude,PatrolCeilingAltitude,PatrolMinSpeed,PatrolMaxSpeed,PatrolAltType)) +self.EngageZone=EngageZone +self.Accomplished=false +self:SetDetectionZone(self.EngageZone) +self:SearchOn() +self:AddTransition({"Patrolling","Engaging"},"Engage","Engaging") +self:AddTransition("Engaging","Target","Engaging") +self:AddTransition("Engaging","Fired","Engaging") +self:AddTransition("*","Destroy","*") +self:AddTransition("Engaging","Abort","Patrolling") +self:AddTransition("Engaging","Accomplish","Patrolling") +return self +end +function AI_BAI_ZONE:SetEngageZone(EngageZone) +self:F2() +if EngageZone then +self.EngageZone=EngageZone +else +self.EngageZone=nil +end +end +function AI_BAI_ZONE:SearchOnOff(Search) +self.Search=Search +return self +end +function AI_BAI_ZONE:SearchOff() +self:SearchOnOff(false) +return self +end +function AI_BAI_ZONE:SearchOn() +self:SearchOnOff(true) +return self +end +function AI_BAI_ZONE:onafterStart(Controllable,From,Event,To) +self:GetParent(self).onafterStart(self,Controllable,From,Event,To) +self:HandleEvent(EVENTS.Dead) +self:SetDetectionDeactivated() +end +function _NewEngageRoute(AIControllable) +AIControllable:T("NewEngageRoute") +local EngageZone=AIControllable:GetState(AIControllable,"EngageZone") +EngageZone:__Engage(1,EngageZone.EngageSpeed,EngageZone.EngageAltitude,EngageZone.EngageWeaponExpend,EngageZone.EngageAttackQty,EngageZone.EngageDirection) +end +function AI_BAI_ZONE:onbeforeEngage(Controllable,From,Event,To) +if self.Accomplished==true then +return false +end +end +function AI_BAI_ZONE:onafterTarget(Controllable,From,Event,To) +self:F({"onafterTarget",self.Search,Controllable:IsAlive()}) +if Controllable:IsAlive()then +local AttackTasks={} +if self.Search==true then +for DetectedUnit,Detected in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnit +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +if Detected==true then +self:F({"Target: ",DetectedUnit}) +self.DetectedUnits[DetectedUnit]=false +local AttackTask=Controllable:TaskAttackUnit(DetectedUnit,false,self.EngageWeaponExpend,self.EngageAttackQty,self.EngageDirection,self.EngageAltitude,nil) +self.Controllable:PushTask(AttackTask,1) +end +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +else +self:F("Attack zone") +local AttackTask=Controllable:TaskAttackMapObject( +self.EngageZone:GetPointVec2():GetVec2(), +true, +self.EngageWeaponExpend, +self.EngageAttackQty, +self.EngageDirection, +self.EngageAltitude +) +self.Controllable:PushTask(AttackTask,1) +end +self:__Target(-10) +end +end +function AI_BAI_ZONE:onafterAbort(Controllable,From,Event,To) +Controllable:ClearTasks() +self:__Route(1) +end +function AI_BAI_ZONE:onafterEngage(Controllable,From,Event,To, +EngageSpeed, +EngageAltitude, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection) +self:F("onafterEngage") +self.EngageSpeed=EngageSpeed or 400 +self.EngageAltitude=EngageAltitude or 2000 +self.EngageWeaponExpend=EngageWeaponExpend +self.EngageAttackQty=EngageAttackQty +self.EngageDirection=EngageDirection +if Controllable:IsAlive()then +local EngageRoute={} +local CurrentVec2=self.Controllable:GetVec2() +local CurrentAltitude=self.Controllable:GetUnit(1):GetAltitude() +local CurrentPointVec3=POINT_VEC3:New(CurrentVec2.x,CurrentAltitude,CurrentVec2.y) +local ToEngageZoneSpeed=self.PatrolMaxSpeed +local CurrentRoutePoint=CurrentPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=CurrentRoutePoint +local AttackTasks={} +if self.Search==true then +for DetectedUnitID,DetectedUnitData in pairs(self.DetectedUnits)do +local DetectedUnit=DetectedUnitData +self:T(DetectedUnit) +if DetectedUnit:IsAlive()then +if DetectedUnit:IsInZone(self.EngageZone)then +self:F({"Engaging ",DetectedUnit}) +AttackTasks[#AttackTasks+1]=Controllable:TaskBombing( +DetectedUnit:GetPointVec2():GetVec2(), +true, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection, +EngageAltitude +) +end +else +self.DetectedUnits[DetectedUnit]=nil +end +end +else +self:F("Attack zone") +AttackTasks[#AttackTasks+1]=Controllable:TaskAttackMapObject( +self.EngageZone:GetPointVec2():GetVec2(), +true, +EngageWeaponExpend, +EngageAttackQty, +EngageDirection, +EngageAltitude +) +end +EngageRoute[#EngageRoute].task=Controllable:TaskCombo(AttackTasks) +local ToTargetVec2=self.EngageZone:GetRandomVec2() +self:T2(ToTargetVec2) +local ToTargetPointVec3=POINT_VEC3:New(ToTargetVec2.x,self.EngageAltitude,ToTargetVec2.y) +local ToTargetRoutePoint=ToTargetPointVec3:WaypointAir( +self.PatrolAltType, +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +self.EngageSpeed, +true +) +EngageRoute[#EngageRoute+1]=ToTargetRoutePoint +Controllable:OptionROEOpenFire() +Controllable:OptionROTVertical() +Controllable:WayPointInitialize(EngageRoute) +Controllable:SetState(Controllable,"EngageZone",self) +Controllable:WayPointFunction(#EngageRoute,1,"_NewEngageRoute") +Controllable:WayPointExecute(1) +self:SetRefreshTimeInterval(2) +self:SetDetectionActivated() +self:__Target(-2) +end +end +function AI_BAI_ZONE:onafterAccomplish(Controllable,From,Event,To) +self.Accomplished=true +self:SetDetectionDeactivated() +end +function AI_BAI_ZONE:onafterDestroy(Controllable,From,Event,To,EventData) +if EventData.IniUnit then +self.DetectedUnits[EventData.IniUnit]=nil +end +end +function AI_BAI_ZONE:OnEventDead(EventData) +self:F({"EventDead",EventData}) +if EventData.IniDCSUnit then +if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit]then +self:__Destroy(1,EventData) +end +end +end +AI_FORMATION={ +ClassName="AI_FORMATION", +FollowName=nil, +FollowUnit=nil, +FollowGroupSet=nil, +FollowMode=1, +MODE={ +FOLLOW=1, +MISSION=2, +}, +FollowScheduler=nil, +OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, +OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, +dtFollow=0.5, +} +AI_FORMATION.__Enum={} +AI_FORMATION.__Enum.Formation={ +None=0, +Mission=1, +Line=2, +Trail=3, +Stack=4, +LeftLine=5, +RightLine=6, +LeftWing=7, +RightWing=8, +Vic=9, +Box=10, +} +AI_FORMATION.__Enum.Mode={ +Mission="M", +Formation="F", +Attack="A", +Reconnaissance="R", +} +AI_FORMATION.__Enum.ReportType={ +Airborne="*", +Airborne="A", +GroundRadar="R", +Ground="G", +} +function AI_FORMATION:New(FollowUnit,FollowGroupSet,FollowName,FollowBriefing) +local self=BASE:Inherit(self,FSM_SET:New(FollowGroupSet)) +self:F({FollowUnit,FollowGroupSet,FollowName}) +self.FollowUnit=FollowUnit +self.FollowGroupSet=FollowGroupSet +self.FollowGroupSet:ForEachGroup( +function(FollowGroup) +FollowGroup:SetState(self,"Mode",self.__Enum.Mode.Formation) +end +) +self:SetFlightModeFormation() +self:SetFlightRandomization(2) +self:SetStartState("None") +self:AddTransition("*","Stop","Stopped") +self:AddTransition({"None","Stopped"},"Start","Following") +self:AddTransition("*","FormationLine","*") +self:AddTransition("*","FormationTrail","*") +self:AddTransition("*","FormationStack","*") +self:AddTransition("*","FormationLeftLine","*") +self:AddTransition("*","FormationRightLine","*") +self:AddTransition("*","FormationLeftWing","*") +self:AddTransition("*","FormationRightWing","*") +self:AddTransition("*","FormationCenterWing","*") +self:AddTransition("*","FormationVic","*") +self:AddTransition("*","FormationBox","*") +self:AddTransition("*","Follow","Following") +self:FormationLeftLine(500,0,250,250) +self.FollowName=FollowName +self.FollowBriefing=FollowBriefing +self.CT1=0 +self.GT1=0 +self.FollowMode=AI_FORMATION.MODE.MISSION +return self +end +function AI_FORMATION:SetFollowTimeInterval(dt) +self.dtFollow=dt or 0.5 +return self +end +function AI_FORMATION:TestSmokeDirectionVector(SmokeDirection) +self.SmokeDirectionVector=(SmokeDirection==true)and true or false +return self +end +function AI_FORMATION:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation) +self:F({FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,Formation}) +XStart=XStart or self.XStart +XSpace=XSpace or self.XSpace +YStart=YStart or self.YStart +YSpace=YSpace or self.YSpace +ZStart=ZStart or self.ZStart +ZSpace=ZSpace or self.ZSpace +FollowGroupSet:Flush(self) +local FollowSet=FollowGroupSet:GetSet() +local i=1 +for FollowID,FollowGroup in pairs(FollowSet)do +local PointVec3=POINT_VEC3:New() +PointVec3:SetX(XStart+i*XSpace) +PointVec3:SetY(YStart+i*YSpace) +PointVec3:SetZ(ZStart+i*ZSpace) +local Vec3=PointVec3:GetVec3() +FollowGroup:SetState(self,"FormationVec3",Vec3) +i=i+1 +FollowGroup:SetState(FollowGroup,"Formation",Formation) +end +return self +end +function AI_FORMATION:onafterFormationTrail(FollowGroupSet,From,Event,To,XStart,XSpace,YStart) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0,self.__Enum.Formation.Trail) +return self +end +function AI_FORMATION:onafterFormationStack(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0,self.__Enum.Formation.Stack) +return self +end +function AI_FORMATION:onafterFormationLeftLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftLine) +return self +end +function AI_FORMATION:onafterFormationRightLine(FollowGroupSet,From,Event,To,XStart,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightLine) +return self +end +function AI_FORMATION:onafterFormationLeftWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftWing) +return self +end +function AI_FORMATION:onafterFormationRightWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,ZStart,ZSpace) +self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightWing) +return self +end +function AI_FORMATION:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +local FollowSet=FollowGroupSet:GetSet() +local i=0 +for FollowID,FollowGroup in pairs(FollowSet)do +local PointVec3=POINT_VEC3:New() +local Side=(i%2==0)and 1 or-1 +local Row=i/2+1 +PointVec3:SetX(XStart+Row*XSpace) +PointVec3:SetY(YStart) +PointVec3:SetZ(Side*(ZStart+i*ZSpace)) +local Vec3=PointVec3:GetVec3() +FollowGroup:SetState(self,"FormationVec3",Vec3) +i=i+1 +FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Vic) +end +return self +end +function AI_FORMATION:onafterFormationVic(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +return self +end +function AI_FORMATION:onafterFormationBox(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +local FollowSet=FollowGroupSet:GetSet() +local i=0 +for FollowID,FollowGroup in pairs(FollowSet)do +local PointVec3=POINT_VEC3:New() +local ZIndex=i%ZLevels +local XIndex=math.floor(i/ZLevels) +local YIndex=math.floor(i/ZLevels) +PointVec3:SetX(XStart+XIndex*XSpace) +PointVec3:SetY(YStart+YIndex*YSpace) +PointVec3:SetZ(-ZStart-(ZSpace*ZLevels/2)+ZSpace*ZIndex) +local Vec3=PointVec3:GetVec3() +FollowGroup:SetState(self,"FormationVec3",Vec3) +i=i+1 +FollowGroup:SetState(FollowGroup,"Formation",self.__Enum.Formation.Box) +end +return self +end +function AI_FORMATION:SetFlightRandomization(FlightRandomization) +self.FlightRandomization=FlightRandomization +return self +end +function AI_FORMATION:GetFlightMode(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) +end +return FollowGroup:GetState(FollowGroup,"Mode") +end +function AI_FORMATION:SetFlightModeMission(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) +else +self.FollowGroupSet:ForSomeGroupAlive( +function(FollowGroup) +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Mission) +end +) +end +return self +end +function AI_FORMATION:SetFlightModeAttack(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack) +else +self.FollowGroupSet:ForSomeGroupAlive( +function(FollowGroup) +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Attack) +end +) +end +return self +end +function AI_FORMATION:SetFlightModeFormation(FollowGroup) +if FollowGroup then +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation) +else +self.FollowGroupSet:ForSomeGroupAlive( +function(FollowGroup) +FollowGroup:SetState(FollowGroup,"PreviousMode",FollowGroup:GetState(FollowGroup,"Mode")) +FollowGroup:SetState(FollowGroup,"Mode",self.__Enum.Mode.Formation) +end +) +end +return self +end +function AI_FORMATION:onafterStop(FollowGroupSet,From,Event,To) +self:E("Stopping formation.") +end +function AI_FORMATION:onbeforeFollow(FollowGroupSet,From,Event,To) +if From=="Stopped"then +return false +end +return true +end +function AI_FORMATION:onenterFollowing(FollowGroupSet) +if self.FollowUnit:IsAlive()then +local ClientUnit=self.FollowUnit +local CT1,CT2,CV1,CV2 +CT1=ClientUnit:GetState(self,"CT1") +local CuVec3=ClientUnit:GetVec3() +if CT1==nil or CT1==0 then +ClientUnit:SetState(self,"CV1",CuVec3) +ClientUnit:SetState(self,"CT1",timer.getTime()) +else +CT1=ClientUnit:GetState(self,"CT1") +CT2=timer.getTime() +CV1=ClientUnit:GetState(self,"CV1") +CV2=CuVec3 +ClientUnit:SetState(self,"CT1",CT2) +ClientUnit:SetState(self,"CV1",CV2) +end +for _,_group in pairs(FollowGroupSet:GetSet())do +local group=_group +if group and group:IsAlive()then +self:FollowMe(group,ClientUnit,CT1,CV1,CT2,CV2) +end +end +self:__Follow(-self.dtFollow) +end +end +function AI_FORMATION:FollowMe(FollowGroup,ClientUnit,CT1,CV1,CT2,CV2) +if FollowGroup:GetState(FollowGroup,"Mode")==self.__Enum.Mode.Formation and not self:Is("Stopped")then +self:T({Mode=FollowGroup:GetState(FollowGroup,"Mode")}) +FollowGroup:OptionROTEvadeFire() +FollowGroup:OptionROEReturnFire() +local GroupUnit=FollowGroup:GetUnit(1) +local GuVec3=GroupUnit:GetVec3() +local FollowFormation=FollowGroup:GetState(self,"FormationVec3") +if FollowFormation then +local FollowDistance=FollowFormation.x +local GT1=GroupUnit:GetState(self,"GT1") +if CT1==nil or CT1==0 or GT1==nil or GT1==0 then +GroupUnit:SetState(self,"GV1",GuVec3) +GroupUnit:SetState(self,"GT1",timer.getTime()) +else +local CD=((CV2.x-CV1.x)^2+(CV2.y-CV1.y)^2+(CV2.z-CV1.z)^2)^0.5 +local CT=CT2-CT1 +local CS=(3600/CT)*(CD/1000)/3.6 +local CDv={x=CV2.x-CV1.x,y=CV2.y-CV1.y,z=CV2.z-CV1.z} +local Ca=math.atan2(CDv.x,CDv.z) +local GT1=GroupUnit:GetState(self,"GT1") +local GT2=timer.getTime() +local GV1=GroupUnit:GetState(self,"GV1") +local GV2=GuVec3 +GV2.x=GV2.x+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) +GV2.y=GV2.y+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) +GV2.z=GV2.z+math.random(-self.FlightRandomization/2,self.FlightRandomization/2) +GroupUnit:SetState(self,"GT1",GT2) +GroupUnit:SetState(self,"GV1",GV2) +local GD=((GV2.x-GV1.x)^2+(GV2.y-GV1.y)^2+(GV2.z-GV1.z)^2)^0.5 +local GT=GT2-GT1 +local GDv={x=GV2.x-CV1.x,y=GV2.y-CV1.y,z=GV2.z-CV1.z} +local Alpha_T=math.atan2(GDv.x,GDv.z)-math.atan2(CDv.x,CDv.z) +local Alpha_R=(Alpha_T<0)and Alpha_T+2*math.pi or Alpha_T +local Position=math.cos(Alpha_R) +local GD=((GDv.x)^2+(GDv.z)^2)^0.5 +local Distance=GD*Position+-CS*0.5 +local GV={x=GV2.x-CV2.x,y=GV2.y-CV2.y,z=GV2.z-CV2.z} +local GH2={x=GV2.x,y=CV2.y+FollowFormation.y,z=GV2.z} +local alpha=math.atan2(GV.x,GV.z) +local GVx=FollowFormation.z*math.cos(Ca)+FollowFormation.x*math.sin(Ca) +local GVz=FollowFormation.x*math.cos(Ca)-FollowFormation.z*math.sin(Ca) +local Inclination=(Distance+FollowFormation.x)/10 +if Inclination<-30 then +Inclination=-30 +end +local CVI={ +x=CV2.x+CS*10*math.sin(Ca), +y=GH2.y+Inclination, +y=GH2.y, +z=CV2.z+CS*10*math.cos(Ca), +} +local DV={x=CV2.x-CVI.x,y=CV2.y-CVI.y,z=CV2.z-CVI.z} +local DVu={x=DV.x/FollowDistance,y=DV.y,z=DV.z/FollowDistance} +local GDV={x=CVI.x,y=CVI.y,z=CVI.z} +local ADDx=FollowFormation.x*math.cos(alpha)-FollowFormation.z*math.sin(alpha) +local ADDz=FollowFormation.z*math.cos(alpha)+FollowFormation.x*math.sin(alpha) +local GDV_Formation={ +x=GDV.x-GVx, +y=GDV.y, +z=GDV.z-GVz +} +if self.SmokeDirectionVector==true then +trigger.action.smoke(GDV,trigger.smokeColor.Green) +trigger.action.smoke(GDV_Formation,trigger.smokeColor.White) +end +local Time=120 +local Speed=-(Distance+FollowFormation.x)/Time +if Distance>-10000 then +Speed=-(Distance+FollowFormation.x)/60 +end +if Distance>-2500 then +Speed=-(Distance+FollowFormation.x)/20 +end +local GS=Speed+CS +FollowGroup:RouteToVec3(GDV_Formation,GS) +end +end +end +end +AI_ESCORT={ +ClassName="AI_ESCORT", +EscortName=nil, +EscortUnit=nil, +EscortGroup=nil, +EscortMode=1, +Targets={}, +FollowScheduler=nil, +ReportTargets=true, +OptionROE=AI.Option.Air.val.ROE.OPEN_FIRE, +OptionReactionOnThreat=AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, +SmokeDirectionVector=false, +TaskPoints={} +} +AI_ESCORT.Detection=nil +function AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing) +local self=BASE:Inherit(self,AI_FORMATION:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing)) +self:F({EscortUnit,EscortGroupSet}) +self.PlayerUnit=self.FollowUnit +self.PlayerGroup=self.FollowUnit:GetGroup() +self.EscortName=EscortName +self.EscortGroupSet=EscortGroupSet +self.EscortGroupSet:SetSomeIteratorLimit(8) +self.EscortBriefing=EscortBriefing +self.Menu={} +self.FollowDistance=100 +self.CT1=0 +self.GT1=0 +EscortGroupSet:ForEachGroup( +function(EscortGroup) +if not self.PlayerUnit._EscortGroups then +self.PlayerUnit._EscortGroups={} +end +if not self.PlayerUnit._EscortGroups[EscortGroup:GetName()]then +self.PlayerUnit._EscortGroups[EscortGroup:GetName()]={} +self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortGroup=EscortGroup +self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortName=self.EscortName +self.PlayerUnit._EscortGroups[EscortGroup:GetName()].Detection=self.Detection +end +end +) +self:SetFlightReportType(self.__Enum.ReportType.All) +return self +end +function AI_ESCORT:_InitFlightMenus() +self:SetFlightMenuJoinUp() +self:SetFlightMenuFormation("Trail") +self:SetFlightMenuFormation("Stack") +self:SetFlightMenuFormation("LeftLine") +self:SetFlightMenuFormation("RightLine") +self:SetFlightMenuFormation("LeftWing") +self:SetFlightMenuFormation("RightWing") +self:SetFlightMenuFormation("Vic") +self:SetFlightMenuFormation("Box") +self:SetFlightMenuHoldAtEscortPosition() +self:SetFlightMenuHoldAtLeaderPosition() +self:SetFlightMenuFlare() +self:SetFlightMenuSmoke() +self:SetFlightMenuROE() +self:SetFlightMenuROT() +self:SetFlightMenuTargets() +self:SetFlightMenuReportType() +end +function AI_ESCORT:_InitEscortMenus(EscortGroup) +EscortGroup.EscortMenu=MENU_GROUP:New(self.PlayerGroup,EscortGroup:GetCallsign(),self.MainMenu) +self:SetEscortMenuJoinUp(EscortGroup) +self:SetEscortMenuResumeMission(EscortGroup) +self:SetEscortMenuHoldAtEscortPosition(EscortGroup) +self:SetEscortMenuHoldAtLeaderPosition(EscortGroup) +self:SetEscortMenuFlare(EscortGroup) +self:SetEscortMenuSmoke(EscortGroup) +self:SetEscortMenuROE(EscortGroup) +self:SetEscortMenuROT(EscortGroup) +self:SetEscortMenuTargets(EscortGroup) +end +function AI_ESCORT:_InitEscortRoute(EscortGroup) +EscortGroup.MissionRoute=EscortGroup:GetTaskRoute() +end +function AI_ESCORT:onafterStart(EscortGroupSet) +self:F() +EscortGroupSet:ForEachGroup( +function(EscortGroup) +EscortGroup:WayPointInitialize() +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEOpenFire() +end +) +local LeaderEscort=EscortGroupSet:GetFirst() +if LeaderEscort then +local Report=REPORT:New("Escort reporting:") +Report:Add("Joining Up "..EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.PlayerUnit)) +LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit) +end +self.Detection=DETECTION_AREAS:New(EscortGroupSet,5000) +self.Detection:InitDetectVisual(true) +self.Detection:InitDetectIRST(true) +self.Detection:InitDetectOptical(true) +self.Detection:InitDetectRadar(true) +self.Detection:InitDetectRWR(true) +self.Detection:SetAcceptRange(100000) +self.Detection:__Start(30) +self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName) +self.FlightMenu=MENU_GROUP:New(self.PlayerGroup,"Flight",self.MainMenu) +self:_InitFlightMenus() +self.EscortGroupSet:ForSomeGroupAlive( +function(EscortGroup) +self:_InitEscortMenus(EscortGroup) +self:_InitEscortRoute(EscortGroup) +self:SetFlightModeFormation(EscortGroup) +function EscortGroup:OnEventDeadOrCrash(EventData) +self:F({"EventDead",EventData}) +self.EscortMenu:Remove() +end +EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash) +EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash) +end +) +end +function AI_ESCORT:onafterStop(EscortGroupSet) +self:F() +EscortGroupSet:ForEachGroup( +function(EscortGroup) +EscortGroup:WayPointInitialize() +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEOpenFire() +end +) +self.Detection:Stop() +self.MainMenu:Remove() +end +function AI_ESCORT:SetDetection(Detection) +self.Detection=Detection +self.EscortGroup.Detection=self.Detection +self.PlayerUnit._EscortGroups[self.EscortGroup:GetName()].Detection=self.EscortGroup.Detection +Detection:__Start(1) +end +function AI_ESCORT:TestSmokeDirectionVector(SmokeDirection) +self.SmokeDirectionVector=(SmokeDirection==true)and true or false +end +function AI_ESCORT:MenusHelicopters(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +self:F() +self.XStart=XStart or 50 +self.XSpace=XSpace or 50 +self.YStart=YStart or 50 +self.YSpace=YSpace or 50 +self.ZStart=ZStart or 50 +self.ZSpace=ZSpace or 50 +self.ZLevels=ZLevels or 10 +self:MenuJoinUp() +self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) +self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) +self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) +self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) +self:MenuHoldAtEscortPosition(30) +self:MenuHoldAtEscortPosition(100) +self:MenuHoldAtEscortPosition(500) +self:MenuHoldAtLeaderPosition(30,500) +self:MenuFlare() +self:MenuSmoke() +self:MenuTargets(60) +self:MenuAssistedAttack() +self:MenuROE() +self:MenuROT() +return self +end +function AI_ESCORT:MenusAirplanes(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +self:F() +self.XStart=XStart or 50 +self.XSpace=XSpace or 50 +self.YStart=YStart or 50 +self.YSpace=YSpace or 50 +self.ZStart=ZStart or 50 +self.ZSpace=ZSpace or 50 +self.ZLevels=ZLevels or 10 +self:MenuJoinUp() +self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) +self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) +self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) +self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) +self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) +self:MenuHoldAtEscortPosition(1000,500) +self:MenuHoldAtLeaderPosition(1000,500) +self:MenuFlare() +self:MenuSmoke() +self:MenuTargets(60) +self:MenuAssistedAttack() +self:MenuROE() +self:MenuROT() +return self +end +function AI_ESCORT:SetFlightMenuFormation(Formation) +local FormationID="Formation"..Formation +local MenuFormation=self.Menu[FormationID] +if MenuFormation then +local Arguments=MenuFormation.Arguments +local FlightMenuFormation=MENU_GROUP:New(self.PlayerGroup,"Formation",self.MainMenu) +local MenuFlightFormationID=MENU_GROUP_COMMAND:New(self.PlayerGroup,Formation,FlightMenuFormation, +function(self,Formation,...) +self.EscortGroupSet:ForSomeGroupAlive( +function(EscortGroup,self,Formation,Arguments) +if EscortGroup:IsAir()then +self:E({FormationID=FormationID}) +self[FormationID](self,unpack(Arguments)) +end +end,self,Formation,Arguments +) +end,self,Formation,Arguments +) +end +return self +end +function AI_ESCORT:MenuFormation(Formation,...) +local FormationID="Formation"..Formation +self.Menu[FormationID]=self.Menu[FormationID]or{} +self.Menu[FormationID].Arguments=arg +end +function AI_ESCORT:MenuFormationTrail(XStart,XSpace,YStart) +self:MenuFormation("Trail",XStart,XSpace,YStart) +return self +end +function AI_ESCORT:MenuFormationStack(XStart,XSpace,YStart,YSpace) +self:MenuFormation("Stack",XStart,XSpace,YStart,YSpace) +return self +end +function AI_ESCORT:MenuFormationLeftLine(XStart,YStart,ZStart,ZSpace) +self:MenuFormation("LeftLine",XStart,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationRightLine(XStart,YStart,ZStart,ZSpace) +self:MenuFormation("RightLine",XStart,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationLeftWing(XStart,XSpace,YStart,ZStart,ZSpace) +self:MenuFormation("LeftWing",XStart,XSpace,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationRightWing(XStart,XSpace,YStart,ZStart,ZSpace) +self:MenuFormation("RightWing",XStart,XSpace,YStart,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationCenterWing(XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +self:MenuFormation("CenterWing",XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationVic(XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +self:MenuFormation("Vic",XStart,XSpace,YStart,YSpace,ZStart,ZSpace) +return self +end +function AI_ESCORT:MenuFormationBox(XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +self:MenuFormation("Box",XStart,XSpace,YStart,YSpace,ZStart,ZSpace,ZLevels) +return self +end +function AI_ESCORT:SetFlightMenuJoinUp() +if self.Menu.JoinUp==true then +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",FlightMenuReportNavigation,AI_ESCORT._FlightJoinUp,self) +end +end +function AI_ESCORT:SetEscortMenuJoinUp(EscortGroup) +if self.Menu.JoinUp==true then +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuJoinUp=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Join Up",EscortMenuReportNavigation,AI_ESCORT._JoinUp,self,EscortGroup) +end +end +end +function AI_ESCORT:MenuJoinUp() +self.Menu.JoinUp=true +return self +end +function AI_ESCORT:SetFlightMenuHoldAtEscortPosition() +for _,MenuHoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition)do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuHoldPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +MenuHoldAtEscortPosition.MenuText, +FlightMenuReportNavigation, +AI_ESCORT._FlightHoldPosition, +self, +nil, +MenuHoldAtEscortPosition.Height, +MenuHoldAtEscortPosition.Speed +) +end +return self +end +function AI_ESCORT:SetEscortMenuHoldAtEscortPosition(EscortGroup) +for _,HoldAtEscortPosition in pairs(self.Menu.HoldAtEscortPosition)do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuHoldPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +HoldAtEscortPosition.MenuText, +EscortMenuReportNavigation, +AI_ESCORT._HoldPosition, +self, +EscortGroup, +EscortGroup, +HoldAtEscortPosition.Height, +HoldAtEscortPosition.Speed +) +end +end +return self +end +function AI_ESCORT:MenuHoldAtEscortPosition(Height,Speed,MenuTextFormat) +self:F({Height,Speed,MenuTextFormat}) +if not Height then +Height=30 +end +if not Speed then +Speed=0 +end +local MenuText="" +if not MenuTextFormat then +if Speed==0 then +MenuText=string.format("Hold at %d meter",Height) +else +MenuText=string.format("Hold at %d meter at %d",Height,Speed) +end +else +if Speed==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Speed) +end +end +self.Menu.HoldAtEscortPosition=self.Menu.HoldAtEscortPosition or{} +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition+1]={} +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Height=Height +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Speed=Speed +self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].MenuText=MenuText +return self +end +function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition() +for _,MenuHoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition)do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +MenuHoldAtLeaderPosition.MenuText, +FlightMenuReportNavigation, +AI_ESCORT._FlightHoldPosition, +self, +self.PlayerGroup, +MenuHoldAtLeaderPosition.Height, +MenuHoldAtLeaderPosition.Speed +) +end +return self +end +function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition(EscortGroup) +for _,HoldAtLeaderPosition in pairs(self.Menu.HoldAtLeaderPosition)do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuHoldAtLeaderPosition=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +HoldAtLeaderPosition.MenuText, +EscortMenuReportNavigation, +AI_ESCORT._HoldPosition, +self, +self.PlayerGroup, +EscortGroup, +HoldAtLeaderPosition.Height, +HoldAtLeaderPosition.Speed +) +end +end +return self +end +function AI_ESCORT:MenuHoldAtLeaderPosition(Height,Speed,MenuTextFormat) +self:F({Height,Speed,MenuTextFormat}) +if not Height then +Height=30 +end +if not Speed then +Speed=0 +end +local MenuText="" +if not MenuTextFormat then +if Speed==0 then +MenuText=string.format("Rejoin and hold at %d meter",Height) +else +MenuText=string.format("Rejoin and hold at %d meter at %d",Height,Speed) +end +else +if Speed==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Speed) +end +end +self.Menu.HoldAtLeaderPosition=self.Menu.HoldAtLeaderPosition or{} +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition+1]={} +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Height=Height +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Speed=Speed +self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].MenuText=MenuText +return self +end +function AI_ESCORT:MenuScanForTargets(Height,Seconds,MenuTextFormat) +self:F({Height,Seconds,MenuTextFormat}) +if self.EscortGroup:IsAir()then +if not self.EscortMenuScan then +self.EscortMenuScan=MENU_GROUP:New(self.PlayerGroup,"Scan for targets",self.EscortMenu) +end +if not Height then +Height=100 +end +if not Seconds then +Seconds=30 +end +local MenuText="" +if not MenuTextFormat then +if Seconds==0 then +MenuText=string.format("At %d meter",Height) +else +MenuText=string.format("At %d meter for %d seconds",Height,Seconds) +end +else +if Seconds==0 then +MenuText=string.format(MenuTextFormat,Height) +else +MenuText=string.format(MenuTextFormat,Height,Seconds) +end +end +if not self.EscortMenuScanForTargets then +self.EscortMenuScanForTargets={} +end +self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1]=MENU_GROUP_COMMAND +:New( +self.PlayerGroup, +MenuText, +self.EscortMenuScan, +AI_ESCORT._ScanTargets, +self, +30 +) +end +return self +end +function AI_ESCORT:SetFlightMenuFlare() +for _,MenuFlare in pairs(self.Menu.Flare)do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,FlightMenuReportNavigation) +local FlightMenuFlareGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Green,"Released a green flare!") +local FlightMenuFlareRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Red,"Released a red flare!") +local FlightMenuFlareWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.White,"Released a white flare!") +local FlightMenuFlareYellowFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",FlightMenuFlare,AI_ESCORT._FlightFlare,self,FLARECOLOR.Yellow,"Released a yellow flare!") +end +return self +end +function AI_ESCORT:SetEscortMenuFlare(EscortGroup) +for _,MenuFlare in pairs(self.Menu.Flare)do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuFlare=MENU_GROUP:New(self.PlayerGroup,MenuFlare.MenuText,EscortMenuReportNavigation) +local EscortMenuFlareGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Green,"Released a green flare!") +local EscortMenuFlareRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Red,"Released a red flare!") +local EscortMenuFlareWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.White,"Released a white flare!") +local EscortMenuFlareYellow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release yellow flare",EscortMenuFlare,AI_ESCORT._Flare,self,EscortGroup,FLARECOLOR.Yellow,"Released a yellow flare!") +end +end +return self +end +function AI_ESCORT:MenuFlare(MenuTextFormat) +self:F() +local MenuText="" +if not MenuTextFormat then +MenuText="Flare" +else +MenuText=MenuTextFormat +end +self.Menu.Flare=self.Menu.Flare or{} +self.Menu.Flare[#self.Menu.Flare+1]={} +self.Menu.Flare[#self.Menu.Flare].MenuText=MenuText +return self +end +function AI_ESCORT:SetFlightMenuSmoke() +for _,MenuSmoke in pairs(self.Menu.Smoke)do +local FlightMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",self.FlightMenu) +local FlightMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,FlightMenuReportNavigation) +local FlightMenuSmokeGreenFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Green,"Releasing green smoke!") +local FlightMenuSmokeRedFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Red,"Releasing red smoke!") +local FlightMenuSmokeWhiteFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.White,"Releasing white smoke!") +local FlightMenuSmokeOrangeFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Orange,"Releasing orange smoke!") +local FlightMenuSmokeBlueFlight=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",FlightMenuSmoke,AI_ESCORT._FlightSmoke,self,SMOKECOLOR.Blue,"Releasing blue smoke!") +end +return self +end +function AI_ESCORT:SetEscortMenuSmoke(EscortGroup) +for _,MenuSmoke in pairs(self.Menu.Smoke)do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuReportNavigation=MENU_GROUP:New(self.PlayerGroup,"Navigation",EscortGroup.EscortMenu) +local EscortMenuSmoke=MENU_GROUP:New(self.PlayerGroup,MenuSmoke.MenuText,EscortMenuReportNavigation) +local EscortMenuSmokeGreen=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release green smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Green,"Releasing green smoke!") +local EscortMenuSmokeRed=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release red smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Red,"Releasing red smoke!") +local EscortMenuSmokeWhite=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release white smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.White,"Releasing white smoke!") +local EscortMenuSmokeOrange=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release orange smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Orange,"Releasing orange smoke!") +local EscortMenuSmokeBlue=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Release blue smoke",EscortMenuSmoke,AI_ESCORT._Smoke,self,EscortGroup,SMOKECOLOR.Blue,"Releasing blue smoke!") +end +end +return self +end +function AI_ESCORT:MenuSmoke(MenuTextFormat) +self:F() +local MenuText="" +if not MenuTextFormat then +MenuText="Smoke" +else +MenuText=MenuTextFormat +end +self.Menu.Smoke=self.Menu.Smoke or{} +self.Menu.Smoke[#self.Menu.Smoke+1]={} +self.Menu.Smoke[#self.Menu.Smoke].MenuText=MenuText +return self +end +function AI_ESCORT:SetFlightMenuReportType() +local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu) +local MenuStamp=FlightMenuReportTargets:GetStamp() +local FlightReportType=self:GetFlightReportType() +if FlightReportType~=self.__Enum.ReportType.All then +local FlightMenuReportTargetsAll=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report all targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAll,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Airborne then +local FlightMenuReportTargetsAirborne=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report airborne targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeAirborne,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.GroundRadar then +local FlightMenuReportTargetsGroundRadar=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report gound radar targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGroundRadar,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +if FlightReportType==self.__Enum.ReportType.All or FlightReportType~=self.__Enum.ReportType.Ground then +local FlightMenuReportTargetsGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report ground targets",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportTypeGround,self) +:SetTag("ReportType") +:SetStamp(MenuStamp) +end +FlightMenuReportTargets:RemoveSubMenus(MenuStamp,"ReportType") +end +function AI_ESCORT:SetFlightMenuTargets() +local FlightMenuReportTargets=MENU_GROUP:New(self.PlayerGroup,"Report targets",self.FlightMenu) +local FlightMenuReportTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets now!",FlightMenuReportTargets,AI_ESCORT._FlightReportNearbyTargetsNow,self) +local FlightMenuReportTargetsOn=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets on",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,true) +local FlightMenuReportTargetsOff=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets off",FlightMenuReportTargets,AI_ESCORT._FlightSwitchReportNearbyTargets,self,false) +self.FlightMenuAttack=MENU_GROUP:New(self.PlayerGroup,"Attack targets",self.FlightMenu) +local FlightMenuAttackNearby=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self):SetTag("Attack") +local FlightMenuAttackNearbyAir=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest airborne targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Air):SetTag("Attack") +local FlightMenuAttackNearbyGround=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Attack nearest ground targets",self.FlightMenuAttack,AI_ESCORT._FlightAttackNearestTarget,self,self.__Enum.ReportType.Ground):SetTag("Attack") +for _,MenuTargets in pairs(self.Menu.Targets)do +MenuTargets.FlightReportTargetsScheduler=SCHEDULER:New(self,self._FlightReportTargetsScheduler,{},MenuTargets.Interval,MenuTargets.Interval) +end +return self +end +function AI_ESCORT:SetEscortMenuTargets(EscortGroup) +for _,MenuTargets in pairs(self.Menu.Targets)do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +EscortGroup.EscortMenuReportNearbyTargetsNow=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Report targets",EscortGroup.EscortMenu,AI_ESCORT._ReportNearbyTargetsNow,self,EscortGroup,true) +EscortGroup.ReportTargetsScheduler=SCHEDULER:New(self,self._ReportTargetsScheduler,{EscortGroup},1,MenuTargets.Interval) +EscortGroup.ResumeScheduler=SCHEDULER:New(self,self._ResumeScheduler,{EscortGroup},1,60) +end +end +return self +end +function AI_ESCORT:MenuTargets(Seconds) +self:F({Seconds}) +if not Seconds then +Seconds=30 +end +self.Menu.Targets=self.Menu.Targets or{} +self.Menu.Targets[#self.Menu.Targets+1]={} +self.Menu.Targets[#self.Menu.Targets].Interval=Seconds +return self +end +function AI_ESCORT:MenuAssistedAttack() +self:F() +self.EscortGroupSet:ForSomeGroupAlive( +function(EscortGroup) +if not EscortGroup:IsAir()then +self.EscortMenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,"Request assistance from",EscortGroup.EscortMenu) +end +end +) +return self +end +function AI_ESCORT:SetFlightMenuROE() +for _,MenuROE in pairs(self.Menu.ROE)do +local FlightMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",self.FlightMenu) +local FlightMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",FlightMenuROE,AI_ESCORT._FlightROEHoldFire,self,"Holding weapons!") +local FlightMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",FlightMenuROE,AI_ESCORT._FlightROEReturnFire,self,"Returning fire!") +local FlightMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",FlightMenuROE,AI_ESCORT._FlightROEOpenFire,self,"Open fire at designated targets!") +local FlightMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",FlightMenuROE,AI_ESCORT._FlightROEWeaponFree,self,"Engaging all targets!") +end +return self +end +function AI_ESCORT:SetEscortMenuROE(EscortGroup) +for _,MenuROE in pairs(self.Menu.ROE)do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuROE=MENU_GROUP:New(self.PlayerGroup,"Rule Of Engagement",EscortGroup.EscortMenu) +if EscortGroup:OptionROEHoldFirePossible()then +local EscortMenuROEHoldFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Hold fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEHoldFire,"Holding weapons!") +end +if EscortGroup:OptionROEReturnFirePossible()then +local EscortMenuROEReturnFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Return fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEReturnFire,"Returning fire!") +end +if EscortGroup:OptionROEOpenFirePossible()then +EscortGroup.EscortMenuROEOpenFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open Fire",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEOpenFire,"Opening fire on designated targets!!") +end +if EscortGroup:OptionROEWeaponFreePossible()then +EscortGroup.EscortMenuROEWeaponFree=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Engage all targets",EscortMenuROE,AI_ESCORT._ROE,self,EscortGroup,EscortGroup.OptionROEWeaponFree,"Opening fire on targets of opportunity!") +end +end +end +return self +end +function AI_ESCORT:MenuROE() +self:F() +self.Menu.ROE=self.Menu.ROE or{} +self.Menu.ROE[#self.Menu.ROE+1]={} +return self +end +function AI_ESCORT:SetFlightMenuROT() +for _,MenuROT in pairs(self.Menu.ROT)do +local FlightMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",self.FlightMenu) +local FlightMenuROTNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",FlightMenuROT,AI_ESCORT._FlightROTNoReaction,self,"Fighting until death!") +local FlightMenuROTPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",FlightMenuROT,AI_ESCORT._FlightROTPassiveDefense,self,"Defending using jammers, chaff and flares!") +local FlightMenuROTEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",FlightMenuROT,AI_ESCORT._FlightROTEvadeFire,self,"Evading on enemy fire!") +local FlightMenuROTVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",FlightMenuROT,AI_ESCORT._FlightROTVertical,self,"Evading on enemy fire with vertical manoeuvres!") +end +return self +end +function AI_ESCORT:SetEscortMenuROT(EscortGroup) +for _,MenuROT in pairs(self.Menu.ROT)do +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +local EscortMenuROT=MENU_GROUP:New(self.PlayerGroup,"Reaction On Threat",EscortGroup.EscortMenu) +if not EscortGroup.EscortMenuEvasion then +if EscortGroup:OptionROTNoReactionPossible()then +local EscortMenuEvasionNoReaction=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Fight until death",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTNoReaction,"Fighting until death!") +end +if EscortGroup:OptionROTPassiveDefensePossible()then +local EscortMenuEvasionPassiveDefense=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Use flares, chaff and jammers",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTPassiveDefense,"Defending using jammers, chaff and flares!") +end +if EscortGroup:OptionROTEvadeFirePossible()then +local EscortMenuEvasionEvadeFire=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Open fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTEvadeFire,"Evading on enemy fire!") +end +if EscortGroup:OptionROTVerticalPossible()then +local EscortMenuOptionEvasionVertical=MENU_GROUP_COMMAND:New(self.PlayerGroup,"Avoid radar and evade fire",EscortMenuROT,AI_ESCORT._ROT,self,EscortGroup,EscortGroup.OptionROTVertical,"Evading on enemy fire with vertical manoeuvres!") +end +end +end +end +return self +end +function AI_ESCORT:MenuROT(MenuTextFormat) +self:F(MenuTextFormat) +self.Menu.ROT=self.Menu.ROT or{} +self.Menu.ROT[#self.Menu.ROT+1]={} +return self +end +function AI_ESCORT:SetEscortMenuResumeMission(EscortGroup) +self:F() +if EscortGroup:IsAir()then +local EscortGroupName=EscortGroup:GetName() +EscortGroup.EscortMenuResumeMission=MENU_GROUP:New(self.PlayerGroup,"Resume from",EscortGroup.EscortMenu) +end +return self +end +function AI_ESCORT:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds) +local EscortUnit=self.PlayerUnit +local OrbitUnit=OrbitGroup:GetUnit(1) +self:SetFlightModeMission(EscortGroup) +local PointFrom={} +local GroupVec3=EscortGroup:GetUnit(1):GetVec3() +PointFrom={} +PointFrom.x=GroupVec3.x +PointFrom.y=GroupVec3.z +PointFrom.speed=250 +PointFrom.type=AI.Task.WaypointType.TURNING_POINT +PointFrom.alt=GroupVec3.y +PointFrom.alt_type=AI.Task.AltitudeType.BARO +local OrbitPoint=OrbitUnit:GetVec2() +local PointTo={} +PointTo.x=OrbitPoint.x +PointTo.y=OrbitPoint.y +PointTo.speed=250 +PointTo.type=AI.Task.WaypointType.TURNING_POINT +PointTo.alt=OrbitHeight +PointTo.alt_type=AI.Task.AltitudeType.BARO +PointTo.task=EscortGroup:TaskOrbitCircleAtVec2(OrbitPoint,OrbitHeight,0) +local Points={PointFrom,PointTo} +EscortGroup:OptionROEHoldFire() +EscortGroup:OptionROTPassiveDefense() +EscortGroup:SetTask(EscortGroup:TaskRoute(Points),1) +EscortGroup:MessageTypeToGroup("Orbiting at current location.",MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightHoldPosition(OrbitGroup,OrbitHeight,OrbitSeconds) +local EscortUnit=self.PlayerUnit +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup,OrbitGroup) +if EscortGroup:IsAir()then +if OrbitGroup==nil then +OrbitGroup=EscortGroup +end +self:_HoldPosition(OrbitGroup,EscortGroup,OrbitHeight,OrbitSeconds) +end +end,OrbitGroup +) +end +function AI_ESCORT:_JoinUp(EscortGroup) +local EscortUnit=self.PlayerUnit +self:SetFlightModeFormation(EscortGroup) +EscortGroup:MessageTypeToGroup("Joining up!",MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightJoinUp() +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_JoinUp(EscortGroup) +end +end +) +end +function AI_ESCORT:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart) +self:FormationTrail(XStart,XSpace,YStart) +end +function AI_ESCORT:_FlightFormationTrail(XStart,XSpace,YStart) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_EscortFormationTrail(EscortGroup,XStart,XSpace,YStart) +end +end +) +end +function AI_ESCORT:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace) +self:FormationStack(XStart,XSpace,YStart,YSpace) +end +function AI_ESCORT:_FlightFormationStack(XStart,XSpace,YStart,YSpace) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_EscortFormationStack(EscortGroup,XStart,XSpace,YStart,YSpace) +end +end +) +end +function AI_ESCORT:_Flare(EscortGroup,Color,Message) +local EscortUnit=self.PlayerUnit +EscortGroup:GetUnit(1):Flare(Color) +EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightFlare(Color,Message) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_Flare(EscortGroup,Color,Message) +end +end +) +end +function AI_ESCORT:_Smoke(EscortGroup,Color,Message) +local EscortUnit=self.PlayerUnit +EscortGroup:GetUnit(1):Smoke(Color) +EscortGroup:MessageTypeToGroup(Message,MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_FlightSmoke(Color,Message) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_Smoke(EscortGroup,Color,Message) +end +end +) +end +function AI_ESCORT:_ReportNearbyTargetsNow(EscortGroup) +local EscortUnit=self.PlayerUnit +self:_ReportTargetsScheduler(EscortGroup) +end +function AI_ESCORT:_FlightReportNearbyTargetsNow() +self:_FlightReportTargetsScheduler() +end +function AI_ESCORT:_FlightSwitchReportNearbyTargets(ReportTargets) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +if EscortGroup:IsAir()then +self:_EscortSwitchReportNearbyTargets(EscortGroup,ReportTargets) +end +end +) +end +function AI_ESCORT:SetFlightReportType(ReportType) +self.FlightReportType=ReportType +end +function AI_ESCORT:GetFlightReportType() +return self.FlightReportType +end +function AI_ESCORT:_FlightSwitchReportTypeAll() +self:SetFlightReportType(self.__Enum.ReportType.All) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting all targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightSwitchReportTypeAirborne() +self:SetFlightReportType(self.__Enum.ReportType.Airborne) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting airborne targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightSwitchReportTypeGroundRadar() +self:SetFlightReportType(self.__Enum.ReportType.Ground) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting ground radar targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightSwitchReportTypeGround() +self:SetFlightReportType(self.__Enum.ReportType.Ground) +self:SetFlightMenuReportType() +local EscortGroup=self.EscortGroupSet:GetFirst() +EscortGroup:MessageTypeToGroup("Reporting ground targets.",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_ScanTargets(ScanDuration) +local EscortGroup=self.EscortGroup +local EscortUnit=self.PlayerUnit +self.FollowScheduler:Stop(self.FollowSchedule) +if EscortGroup:IsHelicopter()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(200,20), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +elseif EscortGroup:IsAirPlane()then +EscortGroup:PushTask( +EscortGroup:TaskControlled( +EscortGroup:TaskOrbitCircle(1000,500), +EscortGroup:TaskCondition(nil,nil,nil,nil,ScanDuration,nil) +),1) +end +EscortGroup:MessageToClient("Scanning targets for "..ScanDuration.." seconds.",ScanDuration,EscortUnit) +if self.EscortMode==AI_ESCORT.MODE.FOLLOW then +self.FollowScheduler:Start(self.FollowSchedule) +end +end +function AI_ESCORT.___Resume(EscortGroup,self) +self:F({self=self}) +local PlayerGroup=self.PlayerGroup +EscortGroup:OptionROEHoldFire() +EscortGroup:OptionROTVertical() +EscortGroup:SetState(EscortGroup,"Mode",EscortGroup:GetState(EscortGroup,"PreviousMode")) +if EscortGroup:GetState(EscortGroup,"Mode")==self.__Enum.Mode.Mission then +EscortGroup:MessageTypeToGroup("Resuming route.",MESSAGE.Type.Information,PlayerGroup) +else +EscortGroup:MessageTypeToGroup("Rejoining formation.",MESSAGE.Type.Information,PlayerGroup) +end +end +function AI_ESCORT:_ResumeMission(EscortGroup,WayPoint) +self:SetFlightModeMission(EscortGroup) +local WayPoints=EscortGroup.MissionRoute +self:T(WayPoint,WayPoints) +for WayPointIgnore=1,WayPoint do +table.remove(WayPoints,1) +end +EscortGroup:SetTask(EscortGroup:TaskRoute(WayPoints),1) +EscortGroup:MessageTypeToGroup("Resuming mission from waypoint ",MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_AttackTarget(EscortGroup,DetectedItem) +self:F(EscortGroup) +self:SetFlightModeAttack(EscortGroup) +if EscortGroup:IsAir()then +EscortGroup:OptionROEOpenFire() +EscortGroup:OptionROTVertical() +EscortGroup:SetState(EscortGroup,"Escort",self) +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +local AttackUnitTasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +AttackUnitTasks[#AttackUnitTasks+1]=EscortGroup:TaskAttackUnit(DetectedUnit) +end +end,Tasks +) +Tasks[#Tasks+1]=EscortGroup:TaskCombo(AttackUnitTasks) +Tasks[#Tasks+1]=EscortGroup:TaskFunction("AI_ESCORT.___Resume",self) +EscortGroup:PushTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +else +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroup:PushTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +end +local DetectedTargetsReport=REPORT:New("Engaging target:\n") +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportSummary=DetectedItemReportSummary:Text(", ") +DetectedTargetsReport:AddIndent(ReportSummary,"-") +EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text(),MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightAttackTarget(DetectedItem) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup,DetectedItem) +if EscortGroup:IsAir()then +self:_AttackTarget(EscortGroup,DetectedItem) +end +end,DetectedItem +) +end +function AI_ESCORT:_FlightAttackNearestTarget(TargetType) +self.Detection:Detect() +self:_FlightReportTargetsScheduler() +local EscortGroup=self.EscortGroupSet:GetFirst() +local AttackDetectedItem=nil +local DetectedItems=self.Detection:GetDetectedItems() +for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 +local HasAir=DetectedItemSet:HasAirUnits()>0 +local FlightReportType=self:GetFlightReportType() +if(TargetType and TargetType==self.__Enum.ReportType.Ground and HasGround)or +(TargetType and TargetType==self.__Enum.ReportType.Air and HasAir)or +(TargetType==nil)then +AttackDetectedItem=DetectedItem +break +end +end +if AttackDetectedItem then +self:_FlightAttackTarget(AttackDetectedItem) +else +EscortGroup:MessageTypeToGroup("Nothing to attack!",MESSAGE.Type.Information,self.PlayerGroup) +end +end +function AI_ESCORT:_AssistTarget(EscortGroup,DetectedItem) +local EscortUnit=self.PlayerUnit +local DetectedSet=self.Detection:GetDetectedItemSet(DetectedItem) +local Tasks={} +DetectedSet:ForEachUnit( +function(DetectedUnit,Tasks) +if DetectedUnit:IsAlive()then +Tasks[#Tasks+1]=EscortGroup:TaskFireAtPoint(DetectedUnit:GetVec2(),50) +end +end,Tasks +) +EscortGroup:SetTask( +EscortGroup:TaskCombo( +Tasks +),1 +) +EscortGroup:MessageTypeToGroup("Assisting attack!",MESSAGE.Type.Information,EscortUnit:GetGroup()) +end +function AI_ESCORT:_ROE(EscortGroup,EscortROEFunction,EscortROEMessage) +pcall(function()EscortROEFunction(EscortGroup)end) +EscortGroup:MessageTypeToGroup(EscortROEMessage,MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightROEHoldFire(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEHoldFire,EscortROEMessage) +end +) +end +function AI_ESCORT:_FlightROEOpenFire(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEOpenFire,EscortROEMessage) +end +) +end +function AI_ESCORT:_FlightROEReturnFire(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEReturnFire,EscortROEMessage) +end +) +end +function AI_ESCORT:_FlightROEWeaponFree(EscortROEMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROE(EscortGroup,EscortGroup.OptionROEWeaponFree,EscortROEMessage) +end +) +end +function AI_ESCORT:_ROT(EscortGroup,EscortROTFunction,EscortROTMessage) +pcall(function()EscortROTFunction(EscortGroup)end) +EscortGroup:MessageTypeToGroup(EscortROTMessage,MESSAGE.Type.Information,self.PlayerGroup) +end +function AI_ESCORT:_FlightROTNoReaction(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTNoReaction,EscortROTMessage) +end +) +end +function AI_ESCORT:_FlightROTPassiveDefense(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTPassiveDefense,EscortROTMessage) +end +) +end +function AI_ESCORT:_FlightROTEvadeFire(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTEvadeFire,EscortROTMessage) +end +) +end +function AI_ESCORT:_FlightROTVertical(EscortROTMessage) +self.EscortGroupSet:ForEachGroupAlive( +function(EscortGroup) +self:_ROT(EscortGroup,EscortGroup.OptionROTVertical,EscortROTMessage) +end +) +end +function AI_ESCORT:RegisterRoute() +self:F() +local EscortGroup=self.EscortGroup +local TaskPoints=EscortGroup:GetTaskRoute() +self:T(TaskPoints) +return TaskPoints +end +function AI_ESCORT:_ResumeScheduler(EscortGroup) +self:F(EscortGroup:GetName()) +if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then +local EscortGroupName=EscortGroup:GetCallsign() +if EscortGroup.EscortMenuResumeMission then +EscortGroup.EscortMenuResumeMission:RemoveSubMenus() +local TaskPoints=EscortGroup.MissionRoute +for WayPointID,WayPoint in pairs(TaskPoints)do +local EscortVec3=EscortGroup:GetVec3() +local Distance=((WayPoint.x-EscortVec3.x)^2+ +(WayPoint.y-EscortVec3.z)^2 +)^0.5/1000 +MENU_GROUP_COMMAND:New(self.PlayerGroup,"Waypoint "..WayPointID.." at "..string.format("%.2f",Distance).."km",EscortGroup.EscortMenuResumeMission,AI_ESCORT._ResumeMission,self,EscortGroup,WayPointID) +end +end +end +end +function AI_ESCORT:Distance(PlayerUnit,DetectedItem) +local DetectedCoordinate=self.Detection:GetDetectedItemCoordinate(DetectedItem) +local PlayerCoordinate=PlayerUnit:GetCoordinate() +return DetectedCoordinate:Get3DDistance(PlayerCoordinate) +end +function AI_ESCORT:_ReportTargetsScheduler(EscortGroup,Report) +self:F(EscortGroup:GetName()) +if EscortGroup:IsAlive()and self.PlayerUnit:IsAlive()then +local EscortGroupName=EscortGroup:GetCallsign() +local DetectedTargetsReport=REPORT:New("Reporting targets:\n") +if EscortGroup.EscortMenuTargetAssistance then +EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus() +end +local DetectedItems=self.Detection:GetDetectedItems() +local ClientEscortTargets=self.Detection +local TimeUpdate=timer.getTime() +local EscortMenuAttackTargets=MENU_GROUP:New(self.PlayerGroup,"Attack targets",EscortGroup.EscortMenu) +local DetectedTargets=false +for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 +local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0 +local HasAir=DetectedItemSet:HasAirUnits()>0 +local FlightReportType=self:GetFlightReportType() +if(FlightReportType==self.__Enum.ReportType.All)or +(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or +(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or +(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then +DetectedTargets=true +local DetectedMenu=self.Detection:DetectedItemReportMenu(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())):Text("\n") +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,EscortGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportSummary=DetectedItemReportSummary:Text(", ") +DetectedTargetsReport:AddIndent(ReportSummary,"-") +if EscortGroup:IsAir()then +MENU_GROUP_COMMAND:New(self.PlayerGroup, +DetectedMenu, +EscortMenuAttackTargets, +AI_ESCORT._AttackTarget, +self, +EscortGroup, +DetectedItem +):SetTag("Escort"):SetTime(TimeUpdate) +else +if self.EscortMenuTargetAssistance then +local MenuTargetAssistance=MENU_GROUP:New(self.PlayerGroup,EscortGroupName,EscortGroup.EscortMenuTargetAssistance) +MENU_GROUP_COMMAND:New(self.PlayerGroup, +DetectedMenu, +MenuTargetAssistance, +AI_ESCORT._AssistTarget, +self, +EscortGroup, +DetectedItem +) +end +end +end +end +EscortMenuAttackTargets:RemoveSubMenus(TimeUpdate,"Escort") +if Report then +if DetectedTargets then +EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup) +else +EscortGroup:MessageTypeToGroup("No targets detected.",MESSAGE.Type.Information,self.PlayerGroup) +end +end +return true +end +return false +end +function AI_ESCORT:_FlightReportTargetsScheduler() +self:F("FlightReportTargetScheduler") +local EscortGroup=self.EscortGroupSet:GetFirst() +local DetectedTargetsReport=REPORT:New("Reporting your targets:\n") +if EscortGroup and(self.PlayerUnit:IsAlive()and EscortGroup:IsAlive())then +local TimeUpdate=timer.getTime() +local DetectedItems=self.Detection:GetDetectedItems() +local DetectedTargets=false +local ClientEscortTargets=self.Detection +for DetectedItemIndex,DetectedItem in UTILS.spairs(DetectedItems,function(t,a,b)return self:Distance(self.PlayerUnit,t[a])0 +local HasGroundRadar=HasGround and DetectedItemSet:HasRadar()>0 +local HasAir=DetectedItemSet:HasAirUnits()>0 +local FlightReportType=self:GetFlightReportType() +if(FlightReportType==self.__Enum.ReportType.All)or +(FlightReportType==self.__Enum.ReportType.Airborne and HasAir)or +(FlightReportType==self.__Enum.ReportType.Ground and HasGround)or +(FlightReportType==self.__Enum.ReportType.GroundRadar and HasGroundRadar)then +DetectedTargets=true +local DetectedItemReportMenu=self.Detection:DetectedItemReportMenu(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportMenuText=DetectedItemReportMenu:Text(", ") +MENU_GROUP_COMMAND:New(self.PlayerGroup, +ReportMenuText, +self.FlightMenuAttack, +AI_ESCORT._FlightAttackTarget, +self, +DetectedItem +):SetTag("Flight"):SetTime(TimeUpdate) +local DetectedItemReportSummary=self.Detection:DetectedItemReportSummary(DetectedItem,self.PlayerGroup,_DATABASE:GetPlayerSettings(self.PlayerUnit:GetPlayerName())) +local ReportSummary=DetectedItemReportSummary:Text(", ") +DetectedTargetsReport:AddIndent(ReportSummary,"-") +end +end +self.FlightMenuAttack:RemoveSubMenus(TimeUpdate,"Flight") +if DetectedTargets then +EscortGroup:MessageTypeToGroup(DetectedTargetsReport:Text("\n"),MESSAGE.Type.Information,self.PlayerGroup) +end +return true +end +return false +end +AI_ESCORT_REQUEST={ +ClassName="AI_ESCORT_REQUEST", +} +function AI_ESCORT_REQUEST:New(EscortUnit,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) +local EscortGroupSet=SET_GROUP:New():FilterDeads():FilterCrashes() +local self=BASE:Inherit(self,AI_ESCORT:New(EscortUnit,EscortGroupSet,EscortName,EscortBriefing)) +self.EscortGroupSet=EscortGroupSet +self.EscortSpawn=EscortSpawn +self.EscortAirbase=EscortAirbase +self.LeaderGroup=self.PlayerUnit:GetGroup() +self.Detection=DETECTION_AREAS:New(self.EscortGroupSet,5000) +self.Detection:__Start(30) +self.SpawnMode=self.__Enum.Mode.Mission +return self +end +function AI_ESCORT_REQUEST:SpawnEscort() +local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot) +self:ScheduleOnce(0.1, +function(EscortGroup) +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEHoldFire() +self.EscortGroupSet:AddGroup(EscortGroup) +local LeaderEscort=self.EscortGroupSet:GetFirst() +local Report=REPORT:New() +Report:Add("Joining Up "..self.EscortGroupSet:GetUnitTypeNames():Text(", ").." from "..LeaderEscort:GetCoordinate():ToString(self.EscortUnit)) +LeaderEscort:MessageTypeToGroup(Report:Text(),MESSAGE.Type.Information,self.PlayerUnit) +self:SetFlightModeFormation(EscortGroup) +self:FormationTrail() +self:_InitFlightMenus() +self:_InitEscortMenus(EscortGroup) +self:_InitEscortRoute(EscortGroup) +function EscortGroup:OnEventDeadOrCrash(EventData) +self:F({"EventDead",EventData}) +self.EscortMenu:Remove() +end +EscortGroup:HandleEvent(EVENTS.Dead,EscortGroup.OnEventDeadOrCrash) +EscortGroup:HandleEvent(EVENTS.Crash,EscortGroup.OnEventDeadOrCrash) +end,EscortGroup +) +end +function AI_ESCORT_REQUEST:onafterStart(EscortGroupSet) +self:F() +if not self.MenuRequestEscort then +self.MainMenu=MENU_GROUP:New(self.PlayerGroup,self.EscortName) +self.MenuRequestEscort=MENU_GROUP_COMMAND:New(self.LeaderGroup,"Request new escort ",self.MainMenu, +function() +self:SpawnEscort() +end +) +end +self:GetParent(self).onafterStart(self,EscortGroupSet) +self:HandleEvent(EVENTS.Dead,self.OnEventDeadOrCrash) +self:HandleEvent(EVENTS.Crash,self.OnEventDeadOrCrash) +end +function AI_ESCORT_REQUEST:onafterStop(EscortGroupSet) +self:F() +EscortGroupSet:ForEachGroup( +function(EscortGroup) +EscortGroup:WayPointInitialize() +EscortGroup:OptionROTVertical() +EscortGroup:OptionROEOpenFire() +end +) +self.Detection:Stop() +self.MainMenu:Remove() +end +function AI_ESCORT_REQUEST:SetEscortSpawnMission() +self.SpawnMode=self.__Enum.Mode.Mission +end +AI_ESCORT_DISPATCHER={ +ClassName="AI_ESCORT_DISPATCHER", +} +AI_ESCORT_DISPATCHER.AI_Escorts={} +function AI_ESCORT_DISPATCHER:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) +local self=BASE:Inherit(self,FSM:New()) +self.CarrierSet=CarrierSet +self.EscortSpawn=EscortSpawn +self.EscortAirbase=EscortAirbase +self.EscortName=EscortName +self.EscortBriefing=EscortBriefing +self:SetStartState("Idle") +self:AddTransition("Monitoring","Monitor","Monitoring") +self:AddTransition("Idle","Start","Monitoring") +self:AddTransition("Monitoring","Stop","Idle") +function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier) +self:F({Carrier=Carrier:GetName()}) +end +return self +end +function AI_ESCORT_DISPATCHER:onafterStart(From,Event,To) +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit) +self:HandleEvent(EVENTS.Crash,self.OnEventExit) +self:HandleEvent(EVENTS.Dead,self.OnEventExit) +end +function AI_ESCORT_DISPATCHER:OnEventExit(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +self:I({EscortAirbase=self.EscortAirbase}) +self:I({PlayerGroupName=PlayerGroupName}) +self:I({PlayerGroup=PlayerGroup}) +self:I({FirstGroup=self.CarrierSet:GetFirst()}) +self:I({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)}) +if self.CarrierSet:FindGroup(PlayerGroupName)then +if self.AI_Escorts[PlayerGroupName]then +self.AI_Escorts[PlayerGroupName]:Stop() +self.AI_Escorts[PlayerGroupName]=nil +end +end +end +function AI_ESCORT_DISPATCHER:OnEventBirth(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +self:I({EscortAirbase=self.EscortAirbase}) +self:I({PlayerGroupName=PlayerGroupName}) +self:I({PlayerGroup=PlayerGroup}) +self:I({FirstGroup=self.CarrierSet:GetFirst()}) +self:I({FindGroup=self.CarrierSet:FindGroup(PlayerGroupName)}) +if self.CarrierSet:FindGroup(PlayerGroupName)then +if not self.AI_Escorts[PlayerGroupName]then +local LeaderUnit=PlayerUnit +local EscortGroup=self.EscortSpawn:SpawnAtAirbase(self.EscortAirbase,SPAWN.Takeoff.Hot) +self:I({EscortGroup=EscortGroup}) +self:ScheduleOnce(1, +function(EscortGroup) +local EscortSet=SET_GROUP:New() +EscortSet:AddGroup(EscortGroup) +self.AI_Escorts[PlayerGroupName]=AI_ESCORT:New(LeaderUnit,EscortSet,self.EscortName,self.EscortBriefing) +self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0) +if EscortGroup:IsHelicopter()then +self.AI_Escorts[PlayerGroupName]:MenusHelicopters() +else +self.AI_Escorts[PlayerGroupName]:MenusAirplanes() +end +self.AI_Escorts[PlayerGroupName]:__Start(0.1) +end,EscortGroup +) +end +end +end +AI_ESCORT_DISPATCHER_REQUEST={ +ClassName="AI_ESCORT_DISPATCHER_REQUEST", +} +AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts={} +function AI_ESCORT_DISPATCHER_REQUEST:New(CarrierSet,EscortSpawn,EscortAirbase,EscortName,EscortBriefing) +local self=BASE:Inherit(self,FSM:New()) +self.CarrierSet=CarrierSet +self.EscortSpawn=EscortSpawn +self.EscortAirbase=EscortAirbase +self.EscortName=EscortName +self.EscortBriefing=EscortBriefing +self:SetStartState("Idle") +self:AddTransition("Monitoring","Monitor","Monitoring") +self:AddTransition("Idle","Start","Monitoring") +self:AddTransition("Monitoring","Stop","Idle") +function self.CarrierSet.OnAfterRemoved(CarrierSet,From,Event,To,CarrierName,Carrier) +self:F({Carrier=Carrier:GetName()}) +end +return self +end +function AI_ESCORT_DISPATCHER_REQUEST:onafterStart(From,Event,To) +self:HandleEvent(EVENTS.Birth) +self:HandleEvent(EVENTS.PlayerLeaveUnit,self.OnEventExit) +self:HandleEvent(EVENTS.Crash,self.OnEventExit) +self:HandleEvent(EVENTS.Dead,self.OnEventExit) +end +function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +if self.CarrierSet:FindGroup(PlayerGroupName)then +if self.AI_Escorts[PlayerGroupName]then +self.AI_Escorts[PlayerGroupName]:Stop() +self.AI_Escorts[PlayerGroupName]=nil +end +end +end +function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth(EventData) +local PlayerGroupName=EventData.IniGroupName +local PlayerGroup=EventData.IniGroup +local PlayerUnit=EventData.IniUnit +if self.CarrierSet:FindGroup(PlayerGroupName)then +if not self.AI_Escorts[PlayerGroupName]then +local LeaderUnit=PlayerUnit +self:ScheduleOnce(0.1, +function() +self.AI_Escorts[PlayerGroupName]=AI_ESCORT_REQUEST:New(LeaderUnit,self.EscortSpawn,self.EscortAirbase,self.EscortName,self.EscortBriefing) +self.AI_Escorts[PlayerGroupName]:FormationTrail(0,100,0) +if PlayerGroup:IsHelicopter()then +self.AI_Escorts[PlayerGroupName]:MenusHelicopters() +else +self.AI_Escorts[PlayerGroupName]:MenusAirplanes() +end +self.AI_Escorts[PlayerGroupName]:__Start(0.1) +end +) +end +end +end +AI_CARGO={ +ClassName="AI_CARGO", +Coordinate=nil, +Carrier_Cargo={}, +} +function AI_CARGO:New(Carrier,CargoSet) +local self=BASE:Inherit(self,FSM_CONTROLLABLE:New(Carrier)) +self.CargoSet=CargoSet +self.CargoCarrier=Carrier +self:SetStartState("Unloaded") +self:AddTransition("Unloaded","Pickup","*") +self:AddTransition("Loaded","Deploy","*") +self:AddTransition("*","Load","Boarding") +self:AddTransition("Boarding","Board","Boarding") +self:AddTransition("Loaded","Board","Loaded") +self:AddTransition("Boarding","Loaded","Boarding") +self:AddTransition("Boarding","PickedUp","Loaded") +self:AddTransition("Loaded","Unload","Unboarding") +self:AddTransition("Unboarding","Unboard","Unboarding") +self:AddTransition("Unboarding","Unloaded","Unboarding") +self:AddTransition("Unboarding","Deployed","Unloaded") +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +CarrierUnit:SetCargoBayWeightLimit() +end +self.Transporting=false +self.Relocating=false +return self +end +function AI_CARGO:IsTransporting() +return self.Transporting==true +end +function AI_CARGO:IsRelocating() +return self.Relocating==true +end +function AI_CARGO:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone) +self.Transporting=false +self.Relocating=true +end +function AI_CARGO:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone) +self.Relocating=false +self.Transporting=true +end +function AI_CARGO:onbeforeLoad(Carrier,From,Event,To,PickupZone) +self:F({Carrier,From,Event,To}) +local Boarding=false +local LoadInterval=2 +local LoadDelay=1 +local Carrier_List={} +local Carrier_Weight={} +if Carrier and Carrier:IsAlive()then +self.Carrier_Cargo={} +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +local CargoBayFreeWeight=CarrierUnit:GetCargoBayFreeWeight() +self:F({CargoBayFreeWeight=CargoBayFreeWeight}) +Carrier_List[#Carrier_List+1]=CarrierUnit +Carrier_Weight[CarrierUnit]=CargoBayFreeWeight +end +local Carrier_Count=#Carrier_List +local Carrier_Index=1 +local Loaded=false +for _,Cargo in UTILS.spairs(self.CargoSet:GetSet(),function(t,a,b)return t[a]:GetWeight()>t[b]:GetWeight()end)do +local Cargo=Cargo +self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()}) +for Carrier_Loop=1,#Carrier_List do +local CarrierUnit=Carrier_List[Carrier_Index] +Carrier_Index=Carrier_Index+1 +if Carrier_Index>Carrier_Count then +Carrier_Index=1 +end +if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then +if Cargo:IsInLoadRadius(CarrierUnit:GetCoordinate())then +self:F({"In radius",CarrierUnit:GetName()}) +local CargoWeight=Cargo:GetWeight() +local CarrierSpace=Carrier_Weight[CarrierUnit] +if CarrierSpace>CargoWeight then +Carrier:RouteStop() +Cargo:__Board(-LoadDelay,CarrierUnit) +self:__Board(LoadDelay,Cargo,CarrierUnit,PickupZone) +LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval +self.Carrier_Cargo[Cargo]=CarrierUnit +Boarding=true +Carrier_Weight[CarrierUnit]=Carrier_Weight[CarrierUnit]-CargoWeight +Loaded=true +break +else +self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space",tostring(CarrierUnit:GetName()),CargoWeight,CarrierSpace)) +end +end +end +end +end +if not Loaded==true then +self.Relocating=false +end +end +return Boarding +end +function AI_CARGO:onbeforeReload(Carrier,From,Event,To) +self:F({Carrier,From,Event,To}) +local Boarding=false +local LoadInterval=2 +local LoadDelay=1 +local Carrier_List={} +local Carrier_Weight={} +if Carrier and Carrier:IsAlive()then +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +Carrier_List[#Carrier_List+1]=CarrierUnit +end +local Carrier_Count=#Carrier_List +local Carrier_Index=1 +local Loaded=false +for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +self:F({IsUnLoaded=Cargo:IsUnLoaded(),IsDeployed=Cargo:IsDeployed(),Cargo:GetName(),Carrier:GetName()}) +for Carrier_Loop=1,#Carrier_List do +local CarrierUnit=Carrier_List[Carrier_Index] +Carrier_Index=Carrier_Index+1 +if Carrier_Index>Carrier_Count then +Carrier_Index=1 +end +if Cargo:IsUnLoaded()and not Cargo:IsDeployed()then +Carrier:RouteStop() +Cargo:__Board(-LoadDelay,CarrierUnit) +self:__Board(LoadDelay,Cargo,CarrierUnit) +LoadDelay=LoadDelay+Cargo:GetCount()*LoadInterval +self.Carrier_Cargo[Cargo]=CarrierUnit +Boarding=true +Loaded=true +end +end +end +if not Loaded==true then +self.Relocating=false +end +end +return Boarding +end +function AI_CARGO:onafterBoard(Carrier,From,Event,To,Cargo,CarrierUnit,PickupZone) +self:F({Carrier,From,Event,To,Cargo,CarrierUnit:GetName()}) +if Carrier and Carrier:IsAlive()and From=="Boarding"then +self:F({IsLoaded=Cargo:IsLoaded(),Cargo:GetName(),Carrier:GetName()}) +if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then +self:__Board(-10,Cargo,CarrierUnit,PickupZone) +return +end +end +self:__Loaded(0.1,Cargo,CarrierUnit,PickupZone) +end +function AI_CARGO:onafterLoaded(Carrier,From,Event,To,Cargo,PickupZone) +self:F({Carrier,From,Event,To}) +local Loaded=true +if Carrier and Carrier:IsAlive()then +for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +self:F({IsLoaded=Cargo:IsLoaded(),IsDestroyed=Cargo:IsDestroyed(),Cargo:GetName(),Carrier:GetName()}) +if not Cargo:IsLoaded()and not Cargo:IsDestroyed()then +Loaded=false +end +end +end +if Loaded then +self:__PickedUp(0.1,PickupZone) +end +end +function AI_CARGO:onafterPickedUp(Carrier,From,Event,To,PickupZone) +self:F({Carrier,From,Event,To}) +Carrier:RouteResume() +local HasCargo=false +if Carrier and Carrier:IsAlive()then +for Cargo,CarrierUnit in pairs(self.Carrier_Cargo)do +HasCargo=true +break +end +end +self.Relocating=false +if HasCargo then +self:F("Transporting") +self.Transporting=true +end +end +function AI_CARGO:onafterUnload(Carrier,From,Event,To,DeployZone,Defend) +self:F({Carrier,From,Event,To,DeployZone,Defend=Defend}) +local UnboardInterval=5 +local UnboardDelay=5 +if Carrier and Carrier:IsAlive()then +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +Carrier:RouteStop() +for _,Cargo in pairs(CarrierUnit:GetCargo())do +self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()}) +if Cargo:IsLoaded()then +Cargo:__UnBoard(UnboardDelay) +UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval +self:__Unboard(UnboardDelay,Cargo,CarrierUnit,DeployZone,Defend) +if not Defend==true then +Cargo:SetDeployed(true) +end +end +end +end +end +end +function AI_CARGO:onafterUnboard(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend}) +if Carrier and Carrier:IsAlive()and From=="Unboarding"then +if not Cargo:IsUnLoaded()then +self:__Unboard(10,Cargo,CarrierUnit,DeployZone,Defend) +return +end +end +self:Unloaded(Cargo,CarrierUnit,DeployZone,Defend) +end +function AI_CARGO:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +self:F({Carrier,From,Event,To,Cargo:GetName(),DeployZone=DeployZone,Defend=Defend}) +local AllUnloaded=true +if Carrier and Carrier:IsAlive()then +for _,CarrierUnit in pairs(Carrier:GetUnits())do +local CarrierUnit=CarrierUnit +local IsEmpty=CarrierUnit:IsCargoEmpty() +self:I({IsEmpty=IsEmpty}) +if not IsEmpty then +AllUnloaded=false +break +end +end +if AllUnloaded==true then +if DeployZone==true then +self.Carrier_Cargo={} +end +self.CargoCarrier=Carrier +end +end +if AllUnloaded==true then +self:__Deployed(5,DeployZone,Defend) +end +end +function AI_CARGO:onafterDeployed(Carrier,From,Event,To,DeployZone,Defend) +self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend}) +if not Defend==true then +self.Transporting=false +else +self:F("Defending") +end +end +AI_CARGO_APC={ +ClassName="AI_CARGO_APC", +Coordinate=nil, +} +function AI_CARGO_APC:New(APC,CargoSet,CombatRadius) +local self=BASE:Inherit(self,AI_CARGO:New(APC,CargoSet)) +self:AddTransition("*","Monitor","*") +self:AddTransition("*","Follow","Following") +self:AddTransition("*","Guard","Unloaded") +self:AddTransition("*","Home","*") +self:AddTransition("*","Reload","Boarding") +self:AddTransition("*","Destroyed","Destroyed") +self:SetCombatRadius(CombatRadius) +self:SetCarrier(APC) +return self +end +function AI_CARGO_APC:SetCarrier(CargoCarrier) +self.CargoCarrier=CargoCarrier +self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_APC",self) +CargoCarrier:HandleEvent(EVENTS.Dead) +function CargoCarrier:OnEventDead(EventData) +self:F({"dead"}) +local AICargoTroops=self:GetState(self,"AI_CARGO_APC") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius) +self.Coalition=self.CargoCarrier:GetCoalition() +self:SetControllable(CargoCarrier) +self:Guard() +return self +end +function AI_CARGO_APC:SetOffRoad(Offroad,Formation) +self:SetPickupOffRoad(Offroad,Formation) +self:SetDeployOffRoad(Offroad,Formation) +return self +end +function AI_CARGO_APC:SetPickupOffRoad(Offroad,Formation) +self.pickupOffroad=Offroad +self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +function AI_CARGO_APC:SetDeployOffRoad(Offroad,Formation) +self.deployOffroad=Offroad +self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +function AI_CARGO_APC:FindCarrier(Coordinate,Radius) +local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:F({NearUnit=NearUnit}) +if not NearUnit:GetState(NearUnit,"AI_CARGO_APC")then +local Attributes=NearUnit:GetDesc() +self:F({Desc=Attributes}) +if NearUnit:HasAttribute("Trucks")then +return NearUnit:GetGroup() +end +end +end +return nil +end +function AI_CARGO_APC:SetCombatRadius(CombatRadius) +self.CombatRadius=CombatRadius or 0 +if self.CombatRadius>0 then +self:__Monitor(-5) +end +return self +end +function AI_CARGO_APC:FollowToCarrier(Me,APCUnit,CargoGroup) +local InfantryGroup=CargoGroup:GetGroup() +self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()}) +if APCUnit:IsAlive()then +if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",APCUnit,25))then +Me:Guard() +else +self:F({InfantryGroup=InfantryGroup:GetName()}) +if InfantryGroup:IsAlive()then +self:F({InfantryGroup=InfantryGroup:GetName()}) +local Waypoints={} +local FromCoord=InfantryGroup:GetCoordinate() +local FromGround=FromCoord:WaypointGround(10,"Diamond") +self:F({FromGround=FromGround}) +table.insert(Waypoints,FromGround) +local ToCoord=APCUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5) +local ToGround=ToCoord:WaypointGround(10,"Diamond") +self:F({ToGround=ToGround}) +table.insert(Waypoints,ToGround) +local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_APC.FollowToCarrier",Me,APCUnit,CargoGroup) +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute) +InfantryGroup:Route(Waypoints,1) +end +end +end +end +function AI_CARGO_APC:onafterMonitor(APC,From,Event,To) +self:F({APC,From,Event,To,IsTransporting=self:IsTransporting()}) +if self.CombatRadius>0 then +if APC and APC:IsAlive()then +if self.CarrierCoordinate then +if self:IsTransporting()==true then +local Coordinate=APC:GetCoordinate() +if self:Is("Unloaded")or self:Is("Loaded")then +self.Zone:Scan({Object.Category.UNIT}) +if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then +if self:Is("Unloaded")then +self:Reload() +end +else +if self:Is("Loaded")then +self:__Unload(1,nil,true) +else +if self:Is("Unloaded")then +end +self:F("I am here"..self:GetCurrentState()) +if self:Is("Following")then +for Cargo,APCUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +local APCUnit=APCUnit +if Cargo:IsAlive()then +if not Cargo:IsNear(APCUnit,40)then +APCUnit:RouteStop() +self.CarrierStopped=true +else +if self.CarrierStopped then +if Cargo:IsNear(APCUnit,25)then +APCUnit:RouteResume() +self.CarrierStopped=nil +end +end +end +end +end +end +end +end +end +end +end +self.CarrierCoordinate=APC:GetCoordinate() +end +self:__Monitor(-5) +end +end +function AI_CARGO_APC:onafterFollow(APC,From,Event,To) +self:F({APC,From,Event,To}) +self:F("Follow") +if APC and APC:IsAlive()then +for Cargo,APCUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +if Cargo:IsUnLoaded()then +self:FollowToCarrier(self,APCUnit,Cargo) +APCUnit:RouteResume() +end +end +end +end +function AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone) +APC:F({"AI_CARGO_APC._Pickup:",APC:GetName()}) +if APC:IsAlive()then +self:Load(PickupZone) +end +end +function AI_CARGO_APC._Deploy(APC,self,Coordinate,DeployZone) +APC:F({"AI_CARGO_APC._Deploy:",APC}) +if APC:IsAlive()then +self:Unload(DeployZone) +end +end +function AI_CARGO_APC:onafterPickup(APC,From,Event,To,Coordinate,Speed,Height,PickupZone) +if APC and APC:IsAlive()then +if Coordinate then +self.RoutePickup=true +local _speed=Speed or APC:GetSpeedMax()*0.5 +local Waypoints={} +if self.pickupOffroad then +Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.pickupFormation) +Waypoints[2]=Coordinate:WaypointGround(_speed,self.pickupFormation,DCSTasks) +else +Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true) +end +local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Pickup",self,Coordinate,Speed,PickupZone) +local Waypoint=Waypoints[#Waypoints] +APC:SetTaskWaypoint(Waypoint,TaskFunction) +APC:Route(Waypoints,1) +else +AI_CARGO_APC._Pickup(APC,self,Coordinate,Speed,PickupZone) +end +self:GetParent(self,AI_CARGO_APC).onafterPickup(self,APC,From,Event,To,Coordinate,Speed,Height,PickupZone) +end +end +function AI_CARGO_APC:onafterDeploy(APC,From,Event,To,Coordinate,Speed,Height,DeployZone) +if APC and APC:IsAlive()then +self.RouteDeploy=true +local speedmax=APC:GetSpeedMax() +local _speed=Speed or speedmax*0.5 +_speed=math.min(_speed,speedmax) +local Waypoints={} +if self.deployOffroad then +Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed,self.deployFormation) +Waypoints[2]=Coordinate:WaypointGround(_speed,self.deployFormation,DCSTasks) +else +Waypoints=APC:TaskGroundOnRoad(Coordinate,_speed,ENUMS.Formation.Vehicle.OffRoad,true) +end +local TaskFunction=APC:TaskFunction("AI_CARGO_APC._Deploy",self,Coordinate,DeployZone) +local Waypoint=Waypoints[#Waypoints] +APC:SetTaskWaypoint(Waypoint,TaskFunction) +APC:Route(Waypoints,1) +self:GetParent(self,AI_CARGO_APC).onafterDeploy(self,APC,From,Event,To,Coordinate,Speed,Height,DeployZone) +end +end +function AI_CARGO_APC:onafterUnloaded(Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +self:F({Carrier,From,Event,To,DeployZone=DeployZone,Defend=Defend}) +self:GetParent(self,AI_CARGO_APC).onafterUnloaded(self,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone,Defend) +if Defend==true then +self.Zone:Scan({Object.Category.UNIT}) +if not self.Zone:IsAllInZoneOfCoalition(self.Coalition)then +local AttackUnits=self.Zone:GetScannedUnits() +local Move={} +local CargoGroup=Cargo.CargoObject +Move[#Move+1]=CargoGroup:GetCoordinate():WaypointGround(70,"Custom") +for UnitId,AttackUnit in pairs(AttackUnits)do +local MooseUnit=UNIT:Find(AttackUnit) +if MooseUnit:GetCoalition()~=CargoGroup:GetCoalition()then +Move[#Move+1]=MooseUnit:GetCoordinate():WaypointGround(70,"Line abreast") +self:F({MooseUnit=MooseUnit:GetName(),CargoGroup=CargoGroup:GetName()}) +end +end +CargoGroup:RoutePush(Move,0.1) +end +end +end +function AI_CARGO_APC:onafterDeployed(APC,From,Event,To,DeployZone,Defend) +self:F({APC,From,Event,To,DeployZone=DeployZone,Defend=Defend}) +self:__Guard(0.1) +self:GetParent(self,AI_CARGO_APC).onafterDeployed(self,APC,From,Event,To,DeployZone,Defend) +end +function AI_CARGO_APC:onafterHome(APC,From,Event,To,Coordinate,Speed,Height,HomeZone) +if APC and APC:IsAlive()~=nil then +self.RouteHome=true +Speed=Speed or APC:GetSpeedMax()*0.5 +local Waypoints=APC:TaskGroundOnRoad(Coordinate,Speed,"Line abreast",true) +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +APC:Route(Waypoints,1) +end +end +AI_CARGO_HELICOPTER={ +ClassName="AI_CARGO_HELICOPTER", +Coordinate=nil, +} +AI_CARGO_QUEUE={} +function AI_CARGO_HELICOPTER:New(Helicopter,CargoSet) +local self=BASE:Inherit(self,AI_CARGO:New(Helicopter,CargoSet)) +self.Zone=ZONE_GROUP:New(Helicopter:GetName(),Helicopter,300) +self:SetStartState("Unloaded") +self:AddTransition("Unloaded","Pickup","*") +self:AddTransition("Loaded","Deploy","*") +self:AddTransition("*","Loaded","Loaded") +self:AddTransition("Unboarding","Pickup","Unloaded") +self:AddTransition("Unloaded","Unboard","Unloaded") +self:AddTransition("Unloaded","Unloaded","Unloaded") +self:AddTransition("*","PickedUp","*") +self:AddTransition("*","Landed","*") +self:AddTransition("*","Queue","*") +self:AddTransition("*","Orbit","*") +self:AddTransition("*","Home","*") +self:AddTransition("*","Destroyed","Destroyed") +Helicopter:HandleEvent(EVENTS.Crash, +function(Helicopter,EventData) +AI_CARGO_QUEUE[Helicopter]=nil +end +) +Helicopter:HandleEvent(EVENTS.Land, +function(Helicopter,EventData) +self:ScheduleOnce(60, +function(Helicopter) +AI_CARGO_QUEUE[Helicopter]=nil +end,Helicopter +) +end +) +self:SetCarrier(Helicopter) +return self +end +function AI_CARGO_HELICOPTER:SetCarrier(Helicopter) +local AICargo=self +self.Helicopter=Helicopter +self.Helicopter:SetState(self.Helicopter,"AI_CARGO_HELICOPTER",self) +self.RoutePickup=false +self.RouteDeploy=false +Helicopter:HandleEvent(EVENTS.Dead) +Helicopter:HandleEvent(EVENTS.Hit) +Helicopter:HandleEvent(EVENTS.Land) +function Helicopter:OnEventDead(EventData) +local AICargoTroops=self:GetState(self,"AI_CARGO_HELICOPTER") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +function Helicopter:OnEventLand(EventData) +AICargo:Landed() +end +self.Coalition=self.Helicopter:GetCoalition() +self:SetControllable(Helicopter) +return self +end +function AI_CARGO_HELICOPTER:onafterLanded(Helicopter,From,Event,To) +self:F({From,Event,To}) +Helicopter:F({Name=Helicopter:GetName()}) +if Helicopter and Helicopter:IsAlive()then +self:F({Helicopter:GetName(),Height=Helicopter:GetHeight(true),Velocity=Helicopter:GetVelocityKMH()}) +if self.RoutePickup==true then +if Helicopter:GetHeight(true)<=5.5 and Helicopter:GetVelocityKMH()<10 then +self:Load(self.PickupZone) +self.RoutePickup=false +end +end +if self.RouteDeploy==true then +if Helicopter:GetHeight(true)<=5.5 and Helicopter:GetVelocityKMH()<10 then +self:Unload(self.DeployZone) +self.RouteDeploy=false +end +end +end +end +function AI_CARGO_HELICOPTER:onafterQueue(Helicopter,From,Event,To,Coordinate,Speed,DeployZone) +self:F({From,Event,To,Coordinate,Speed,DeployZone}) +local HelicopterInZone=false +if Helicopter and Helicopter:IsAlive()==true then +local Distance=Coordinate:DistanceFromPointVec2(Helicopter:GetCoordinate()) +if Distance>2000 then +self:__Queue(-10,Coordinate,Speed,DeployZone) +else +local ZoneFree=true +for Helicopter,ZoneQueue in pairs(AI_CARGO_QUEUE)do +local ZoneQueue=ZoneQueue +if ZoneQueue:IsCoordinateInZone(Coordinate)then +ZoneFree=false +end +end +self:F({ZoneFree=ZoneFree}) +if ZoneFree==true then +local ZoneQueue=ZONE_RADIUS:New(Helicopter:GetName(),Coordinate:GetVec2(),100) +AI_CARGO_QUEUE[Helicopter]=ZoneQueue +local Route={} +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir( +"RADIO", +POINT_VEC3.RoutePointType.TurningPoint, +POINT_VEC3.RoutePointAction.TurningPoint, +50, +true +) +Route[#Route+1]=WaypointTo +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +self.DeployZone=DeployZone +else +self:__Queue(-10,Coordinate,Speed,DeployZone) +end +end +else +AI_CARGO_QUEUE[Helicopter]=nil +end +end +function AI_CARGO_HELICOPTER:onafterOrbit(Helicopter,From,Event,To,Coordinate) +self:F({From,Event,To,Coordinate}) +if Helicopter and Helicopter:IsAlive()then +local Route={} +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,50,true) +Route[#Route+1]=WaypointTo +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,80),150,CoordinateTo:GetRandomCoordinateInRadius(800,500)) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +end +end +function AI_CARGO_HELICOPTER:onafterDeployed(Helicopter,From,Event,To,DeployZone) +self:F({From,Event,To,DeployZone=DeployZone}) +self:Orbit(Helicopter:GetCoordinate(),50) +self:ScheduleOnce(30, +function(Helicopter) +AI_CARGO_QUEUE[Helicopter]=nil +end,Helicopter +) +self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeployed(self,Helicopter,From,Event,To,DeployZone) +end +function AI_CARGO_HELICOPTER:onafterPickup(Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone) +self:F({Coordinate,Speed,Height,PickupZone}) +if Helicopter and Helicopter:IsAlive()~=nil then +Helicopter:Activate() +self.RoutePickup=true +Coordinate.y=Height +local _speed=Speed or Helicopter:GetSpeedMax()*0.5 +local Route={} +local CoordinateFrom=Helicopter:GetCoordinate() +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) +Route[#Route+1]=WaypointFrom +Route[#Route+1]=WaypointTo +Helicopter:WayPointInitialize(Route) +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,1) +self.PickupZone=PickupZone +self:GetParent(self,AI_CARGO_HELICOPTER).onafterPickup(self,Helicopter,From,Event,To,Coordinate,Speed,Height,PickupZone) +end +end +function AI_CARGO_HELICOPTER:_Deploy(AICargoHelicopter,Coordinate,DeployZone) +AICargoHelicopter:__Queue(-10,Coordinate,100,DeployZone) +end +function AI_CARGO_HELICOPTER:onafterDeploy(Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone) +self:F({From,Event,To,Coordinate,Speed,Height,DeployZone}) +if Helicopter and Helicopter:IsAlive()~=nil then +self.RouteDeploy=true +local Route={} +Coordinate.y=Height +local _speed=Speed or Helicopter:GetSpeedMax()*0.5 +local CoordinateFrom=Helicopter:GetCoordinate() +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) +Route[#Route+1]=WaypointFrom +Route[#Route+1]=WaypointFrom +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+50 +local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,_speed,true) +Route[#Route+1]=WaypointTo +Route[#Route+1]=WaypointTo +Helicopter:WayPointInitialize(Route) +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskFunction("AI_CARGO_HELICOPTER._Deploy",self,Coordinate,DeployZone) +Tasks[#Tasks+1]=Helicopter:TaskOrbitCircle(math.random(30,100),_speed,CoordinateTo:GetRandomCoordinateInRadius(800,500)) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +self:GetParent(self,AI_CARGO_HELICOPTER).onafterDeploy(self,Helicopter,From,Event,To,Coordinate,Speed,Height,DeployZone) +end +end +function AI_CARGO_HELICOPTER:onafterHome(Helicopter,From,Event,To,Coordinate,Speed,Height,HomeZone) +self:F({From,Event,To,Coordinate,Speed,Height}) +if Helicopter and Helicopter:IsAlive()~=nil then +self.RouteHome=true +local Route={} +Height=Height or 50 +Speed=Speed or Helicopter:GetSpeedMax()*0.5 +local CoordinateFrom=Helicopter:GetCoordinate() +local WaypointFrom=CoordinateFrom:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true) +Route[#Route+1]=WaypointFrom +local CoordinateTo=Coordinate +local landheight=CoordinateTo:GetLandHeight() +CoordinateTo.y=landheight+Height +local WaypointTo=CoordinateTo:WaypointAir("RADIO",POINT_VEC3.RoutePointType.TurningPoint,POINT_VEC3.RoutePointAction.TurningPoint,Speed,true) +Route[#Route+1]=WaypointTo +Helicopter:WayPointInitialize(Route) +local Tasks={} +Tasks[#Tasks+1]=Helicopter:TaskLandAtVec2(CoordinateTo:GetVec2()) +Route[#Route].task=Helicopter:TaskCombo(Tasks) +Route[#Route+1]=WaypointTo +Helicopter:Route(Route,0) +end +end +AI_CARGO_AIRPLANE={ +ClassName="AI_CARGO_AIRPLANE", +Coordinate=nil, +} +function AI_CARGO_AIRPLANE:New(Airplane,CargoSet) +local self=BASE:Inherit(self,AI_CARGO:New(Airplane,CargoSet)) +self:AddTransition("*","Landed","*") +self:AddTransition("*","Home","*") +self:AddTransition("*","Destroyed","Destroyed") +self:SetCarrier(Airplane) +return self +end +function AI_CARGO_AIRPLANE:SetCarrier(Airplane) +local AICargo=self +self.Airplane=Airplane +self.Airplane:SetState(self.Airplane,"AI_CARGO_AIRPLANE",self) +self.RoutePickup=false +self.RouteDeploy=false +Airplane:HandleEvent(EVENTS.Dead) +Airplane:HandleEvent(EVENTS.Hit) +Airplane:HandleEvent(EVENTS.EngineShutdown) +function Airplane:OnEventDead(EventData) +local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +function Airplane:OnEventHit(EventData) +local AICargoTroops=self:GetState(self,"AI_CARGO_AIRPLANE") +if AICargoTroops then +self:F({OnHitLoaded=AICargoTroops:Is("Loaded")}) +if AICargoTroops:Is("Loaded")or AICargoTroops:Is("Boarding")then +AICargoTroops:Unload() +end +end +end +function Airplane:OnEventEngineShutdown(EventData) +AICargo.Relocating=false +AICargo:Landed(self.Airplane) +end +self.Coalition=self.Airplane:GetCoalition() +self:SetControllable(Airplane) +return self +end +function AI_CARGO_AIRPLANE:FindCarrier(Coordinate,Radius) +local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:F({NearUnit=NearUnit}) +if not NearUnit:GetState(NearUnit,"AI_CARGO_AIRPLANE")then +local Attributes=NearUnit:GetDesc() +self:F({Desc=Attributes}) +if NearUnit:HasAttribute("Trucks")then +self:SetCarrier(NearUnit) +break +end +end +end +end +function AI_CARGO_AIRPLANE:onafterLanded(Airplane,From,Event,To) +self:F({Airplane,From,Event,To}) +if Airplane and Airplane:IsAlive()~=nil then +if self.RoutePickup==true then +self:Load(self.PickupZone) +end +if self.RouteDeploy==true then +self:Unload() +self.RouteDeploy=false +end +end +end +function AI_CARGO_AIRPLANE:onafterPickup(Airplane,From,Event,To,Coordinate,Speed,Height,PickupZone) +if Airplane and Airplane:IsAlive()then +local airbasepickup=Coordinate:GetClosestAirbase() +self.PickupZone=PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName()) +local ClosestAirbase,DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() +if Airplane:InAir()then +self.Airbase=nil +else +self.Airbase=ClosestAirbase +end +local Airbase=self.PickupZone:GetAirbase() +local Dist=Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate()) +if Airplane:InAir()or Dist>500 then +self:Route(Airplane,Airbase,Speed,Height) +self.Airbase=Airbase +self.RoutePickup=true +else +self.RoutePickup=true +self:Landed() +end +self:GetParent(self,AI_CARGO_AIRPLANE).onafterPickup(self,Airplane,From,Event,To,Coordinate,Speed,Height,self.PickupZone) +end +end +function AI_CARGO_AIRPLANE:onafterDeploy(Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone) +if Airplane and Airplane:IsAlive()~=nil then +local Airbase=Coordinate:GetClosestAirbase() +if DeployZone then +Airbase=DeployZone:GetAirbase() +end +if Airplane:IsAlive()==false then +Airplane:SetCommand({id='Start',params={}}) +end +self:Route(Airplane,Airbase,Speed,Height) +self.RouteDeploy=true +self.Airbase=Airbase +self:GetParent(self,AI_CARGO_AIRPLANE).onafterDeploy(self,Airplane,From,Event,To,Coordinate,Speed,Height,DeployZone) +end +end +function AI_CARGO_AIRPLANE:onafterUnload(Airplane,From,Event,To,DeployZone) +local UnboardInterval=10 +local UnboardDelay=10 +if Airplane and Airplane:IsAlive()then +for _,AirplaneUnit in pairs(Airplane:GetUnits())do +local Cargos=AirplaneUnit:GetCargo() +for CargoID,Cargo in pairs(Cargos)do +local Angle=180 +local CargoCarrierHeading=Airplane:GetHeading() +local CargoDeployHeading=((CargoCarrierHeading+Angle)>=360)and(CargoCarrierHeading+Angle-360)or(CargoCarrierHeading+Angle) +self:T({CargoCarrierHeading,CargoDeployHeading}) +local CargoDeployCoordinate=Airplane:GetPointVec2():Translate(150,CargoDeployHeading) +Cargo:__UnBoard(UnboardDelay,CargoDeployCoordinate) +UnboardDelay=UnboardDelay+UnboardInterval +Cargo:SetDeployed(true) +self:__Unboard(UnboardDelay,Cargo,AirplaneUnit,DeployZone) +end +end +end +end +function AI_CARGO_AIRPLANE:Route(Airplane,Airbase,Speed,Height,Uncontrolled) +if Airplane and Airplane:IsAlive()then +local Takeoff=SPAWN.Takeoff.Cold +local Template=Airplane:GetTemplate() +if Template==nil then +return +end +local Points={} +local AirbasePointVec2=Airbase:GetPointVec2() +local ToWaypoint=AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO,"Land","Landing",Speed or Airplane:GetSpeedMax()*0.8,true,Airbase) +if self.Airbase then +Template.route.points[2]=ToWaypoint +Airplane:RespawnAtCurrentAirbase(Template,Takeoff,Uncontrolled) +else +local GroupPoint=Airplane:GetVec2() +local FromWaypoint={} +FromWaypoint.x=GroupPoint.x +FromWaypoint.y=GroupPoint.y +FromWaypoint.type="Turning Point" +FromWaypoint.action="Turning Point" +FromWaypoint.speed=Airplane:GetSpeedMax()*0.8 +Points[1]=FromWaypoint +Points[2]=ToWaypoint +local PointVec3=Airplane:GetPointVec3() +Template.x=PointVec3.x +Template.y=PointVec3.z +Template.route.points=Points +local GroupSpawned=Airplane:Respawn(Template) +end +end +end +function AI_CARGO_AIRPLANE:onafterHome(Airplane,From,Event,To,Coordinate,Speed,Height,HomeZone) +if Airplane and Airplane:IsAlive()then +self.RouteHome=true +local HomeBase=HomeZone:GetAirbase() +self.Airbase=HomeBase +self:Route(Airplane,HomeBase,Speed,Height) +end +end +AI_CARGO_SHIP={ +ClassName="AI_CARGO_SHIP", +Coordinate=nil +} +function AI_CARGO_SHIP:New(Ship,CargoSet,CombatRadius,ShippingLane) +local self=BASE:Inherit(self,AI_CARGO:New(Ship,CargoSet)) +self:AddTransition("*","Monitor","*") +self:AddTransition("*","Destroyed","Destroyed") +self:AddTransition("*","Home","*") +self:SetCombatRadius(0) +self:SetShippingLane(ShippingLane) +self:SetCarrier(Ship) +return self +end +function AI_CARGO_SHIP:SetCarrier(CargoCarrier) +self.CargoCarrier=CargoCarrier +self.CargoCarrier:SetState(self.CargoCarrier,"AI_CARGO_SHIP",self) +CargoCarrier:HandleEvent(EVENTS.Dead) +function CargoCarrier:OnEventDead(EventData) +self:F({"dead"}) +local AICargoTroops=self:GetState(self,"AI_CARGO_SHIP") +self:F({AICargoTroops=AICargoTroops}) +if AICargoTroops then +self:F({}) +if not AICargoTroops:Is("Loaded")then +AICargoTroops:Destroyed() +end +end +end +self.Zone=ZONE_UNIT:New(self.CargoCarrier:GetName().."-Zone",self.CargoCarrier,self.CombatRadius) +self.Coalition=self.CargoCarrier:GetCoalition() +self:SetControllable(CargoCarrier) +return self +end +function AI_CARGO_SHIP:FindCarrier(Coordinate,Radius) +local CoordinateZone=ZONE_RADIUS:New("Zone",Coordinate:GetVec2(),Radius) +CoordinateZone:Scan({Object.Category.UNIT}) +for _,DCSUnit in pairs(CoordinateZone:GetScannedUnits())do +local NearUnit=UNIT:Find(DCSUnit) +self:F({NearUnit=NearUnit}) +if not NearUnit:GetState(NearUnit,"AI_CARGO_SHIP")then +local Attributes=NearUnit:GetDesc() +self:F({Desc=Attributes}) +if NearUnit:HasAttributes("Trucks")then +return NearUnit:GetGroup() +end +end +end +return nil +end +function AI_CARGO_SHIP:SetShippingLane(ShippingLane) +self.ShippingLane=ShippingLane +return self +end +function AI_CARGO_SHIP:SetCombatRadius(CombatRadius) +self.CombatRadius=CombatRadius or 0 +return self +end +function AI_CARGO_SHIP:FollowToCarrier(Me,ShipUnit,CargoGroup) +local InfantryGroup=CargoGroup:GetGroup() +self:F({self=self:GetClassNameAndID(),InfantryGroup=InfantryGroup:GetName()}) +if ShipUnit:IsAlive()then +if InfantryGroup:IsPartlyInZone(ZONE_UNIT:New("Radius",ShipUnit,1000))then +Me:Guard() +else +self:F({InfantryGroup=InfantryGroup:GetName()}) +if InfantryGroup:IsAlive()then +self:F({InfantryGroup=InfantryGroup:GetName()}) +local Waypoints={} +local FromCoord=InfantryGroup:GetCoordinate() +local FromGround=FromCoord:WaypointGround(10,"Diamond") +self:F({FromGround=FromGround}) +table.insert(Waypoints,FromGround) +local ToCoord=ShipUnit:GetCoordinate():GetRandomCoordinateInRadius(10,5) +local ToGround=ToCoord:WaypointGround(10,"Diamond") +self:F({ToGround=ToGround}) +table.insert(Waypoints,ToGround) +local TaskRoute=InfantryGroup:TaskFunction("AI_CARGO_SHIP.FollowToCarrier",Me,ShipUnit,CargoGroup) +self:F({Waypoints=Waypoints}) +local Waypoint=Waypoints[#Waypoints] +InfantryGroup:SetTaskWaypoint(Waypoint,TaskRoute) +InfantryGroup:Route(Waypoints,1) +end +end +end +end +function AI_CARGO_SHIP:onafterMonitor(Ship,From,Event,To) +self:F({Ship,From,Event,To,IsTransporting=self:IsTransporting()}) +if self.CombatRadius>0 then +if Ship and Ship:IsAlive()then +if self.CarrierCoordinate then +if self:IsTransporting()==true then +local Coordinate=Ship:GetCoordinate() +if self:Is("Unloaded")or self:Is("Loaded")then +self.Zone:Scan({Object.Category.UNIT}) +if self.Zone:IsAllInZoneOfCoalition(self.Coalition)then +if self:Is("Unloaded")then +self:Reload() +end +else +if self:Is("Loaded")then +self:__Unload(1,nil,true) +else +if self:Is("Unloaded")then +end +self:F("I am here"..self:GetCurrentState()) +if self:Is("Following")then +for Cargo,ShipUnit in pairs(self.Carrier_Cargo)do +local Cargo=Cargo +local ShipUnit=ShipUnit +if Cargo:IsAlive()then +if not Cargo:IsNear(ShipUnit,40)then +ShipUnit:RouteStop() +self.CarrierStopped=true +else +if self.CarrierStopped then +if Cargo:IsNear(ShipUnit,25)then +ShipUnit:RouteResume() +self.CarrierStopped=nil +end +end +end +end +end +end +end +end +end +end +end +self.CarrierCoordinate=Ship:GetCoordinate() +end +self:__Monitor(-5) +end +end +function AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone) +Ship:F({"AI_CARGO_Ship._Pickup:",Ship:GetName()}) +if Ship:IsAlive()then +self:Load(PickupZone) +end +end +function AI_CARGO_SHIP._Deploy(Ship,self,Coordinate,DeployZone) +Ship:F({"AI_CARGO_Ship._Deploy:",Ship}) +if Ship:IsAlive()then +self:Unload(DeployZone) +end +end +function AI_CARGO_SHIP:onafterPickup(Ship,From,Event,To,Coordinate,Speed,Height,PickupZone) +if Ship and Ship:IsAlive()then +AI_CARGO_SHIP._Pickup(Ship,self,Coordinate,Speed,PickupZone) +self:GetParent(self,AI_CARGO_SHIP).onafterPickup(self,Ship,From,Event,To,Coordinate,Speed,Height,PickupZone) +end +end +function AI_CARGO_SHIP:onafterDeploy(Ship,From,Event,To,Coordinate,Speed,Height,DeployZone) +if Ship and Ship:IsAlive()then +Speed=Speed or Ship:GetSpeedMax()*0.8 +local lane=self.ShippingLane +if lane then +local Waypoints={} +for i=1,#lane do +local coord=lane[i] +local Waypoint=coord:WaypointGround(_speed) +table.insert(Waypoints,Waypoint) +end +local TaskFunction=Ship:TaskFunction("AI_CARGO_SHIP._Deploy",self,Coordinate,DeployZone) +local Waypoint=Waypoints[#Waypoints] +Ship:SetTaskWaypoint(Waypoint,TaskFunction) +Ship:Route(Waypoints,1) +self:GetParent(self,AI_CARGO_SHIP).onafterDeploy(self,Ship,From,Event,To,Coordinate,Speed,Height,DeployZone) +else +self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") +end +end +end +function AI_CARGO_SHIP:onafterUnload(Ship,From,Event,To,DeployZone,Defend) +self:F({Ship,From,Event,To,DeployZone,Defend=Defend}) +local UnboardInterval=5 +local UnboardDelay=5 +if Ship and Ship:IsAlive()then +for _,ShipUnit in pairs(Ship:GetUnits())do +local ShipUnit=ShipUnit +Ship:RouteStop() +for _,Cargo in pairs(ShipUnit:GetCargo())do +self:F({Cargo=Cargo:GetName(),Isloaded=Cargo:IsLoaded()}) +if Cargo:IsLoaded()then +local unboardCoord=DeployZone:GetRandomPointVec2() +Cargo:__UnBoard(UnboardDelay,unboardCoord,1000) +UnboardDelay=UnboardDelay+Cargo:GetCount()*UnboardInterval +self:__Unboard(UnboardDelay,Cargo,ShipUnit,DeployZone,Defend) +if not Defend==true then +Cargo:SetDeployed(true) +end +end +end +end +end +end +function AI_CARGO_SHIP:onafterHome(Ship,From,Event,To,Coordinate,Speed,Height,HomeZone) +if Ship and Ship:IsAlive()then +self.RouteHome=true +Speed=Speed or Ship:GetSpeedMax()*0.8 +local lane=self.ShippingLane +if lane then +local Waypoints={} +for i=#lane,1,-1 do +local coord=lane[i] +local Waypoint=coord:WaypointGround(_speed) +table.insert(Waypoints,Waypoint) +end +local Waypoint=Waypoints[#Waypoints] +Ship:Route(Waypoints,1) +else +self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") +end +end +end +AI_CARGO_DISPATCHER={ +ClassName="AI_CARGO_DISPATCHER", +AI_Cargo={}, +PickupCargo={} +} +AI_CARGO_DISPATCHER.AI_Cargo={} +AI_CARGO_DISPATCHER.PickupCargo={} +function AI_CARGO_DISPATCHER:New(CarrierSet,CargoSet,PickupZoneSet,DeployZoneSet) +local self=BASE:Inherit(self,FSM:New()) +self.SetCarrier=CarrierSet +self.SetCargo=CargoSet +self.PickupZoneSet=PickupZoneSet +self.DeployZoneSet=DeployZoneSet +self:SetStartState("Idle") +self:AddTransition("Monitoring","Monitor","Monitoring") +self:AddTransition("Idle","Start","Monitoring") +self:AddTransition("Monitoring","Stop","Idle") +self:AddTransition("Monitoring","Pickup","Monitoring") +self:AddTransition("Monitoring","Load","Monitoring") +self:AddTransition("Monitoring","Loading","Monitoring") +self:AddTransition("Monitoring","Loaded","Monitoring") +self:AddTransition("Monitoring","PickedUp","Monitoring") +self:AddTransition("Monitoring","Transport","Monitoring") +self:AddTransition("Monitoring","Deploy","Monitoring") +self:AddTransition("Monitoring","Unload","Monitoring") +self:AddTransition("Monitoring","Unloading","Monitoring") +self:AddTransition("Monitoring","Unloaded","Monitoring") +self:AddTransition("Monitoring","Deployed","Monitoring") +self:AddTransition("Monitoring","Home","Monitoring") +self:SetMonitorTimeInterval(30) +self:SetDeployRadius(500,200) +self.PickupCargo={} +self.CarrierHome={} +function self.SetCarrier.OnAfterRemoved(SetCarrier,From,Event,To,CarrierName,Carrier) +self:F({Carrier=Carrier:GetName()}) +self.PickupCargo[Carrier]=nil +self.CarrierHome[Carrier]=nil +end +return self +end +function AI_CARGO_DISPATCHER:SetMonitorTimeInterval(MonitorTimeInterval) +self.MonitorTimeInterval=MonitorTimeInterval +return self +end +function AI_CARGO_DISPATCHER:SetHomeZone(HomeZone) +self.HomeZone=HomeZone +return self +end +function AI_CARGO_DISPATCHER:SetPickupRadius(OuterRadius,InnerRadius) +OuterRadius=OuterRadius or 0 +InnerRadius=InnerRadius or OuterRadius +self.PickupOuterRadius=OuterRadius +self.PickupInnerRadius=InnerRadius +return self +end +function AI_CARGO_DISPATCHER:SetPickupSpeed(MaxSpeed,MinSpeed) +MaxSpeed=MaxSpeed or 999 +MinSpeed=MinSpeed or MaxSpeed +self.PickupMinSpeed=MinSpeed +self.PickupMaxSpeed=MaxSpeed +return self +end +function AI_CARGO_DISPATCHER:SetDeployRadius(OuterRadius,InnerRadius) +OuterRadius=OuterRadius or 0 +InnerRadius=InnerRadius or OuterRadius +self.DeployOuterRadius=OuterRadius +self.DeployInnerRadius=InnerRadius +return self +end +function AI_CARGO_DISPATCHER:SetDeploySpeed(MaxSpeed,MinSpeed) +MaxSpeed=MaxSpeed or 999 +MinSpeed=MinSpeed or MaxSpeed +self.DeployMinSpeed=MinSpeed +self.DeployMaxSpeed=MaxSpeed +return self +end +function AI_CARGO_DISPATCHER:SetPickupHeight(MaxHeight,MinHeight) +MaxHeight=MaxHeight or 200 +MinHeight=MinHeight or MaxHeight +self.PickupMinHeight=MinHeight +self.PickupMaxHeight=MaxHeight +return self +end +function AI_CARGO_DISPATCHER:SetDeployHeight(MaxHeight,MinHeight) +MaxHeight=MaxHeight or 200 +MinHeight=MinHeight or MaxHeight +self.DeployMinHeight=MinHeight +self.DeployMaxHeight=MaxHeight +return self +end +function AI_CARGO_DISPATCHER:onafterMonitor() +self:F("Carriers") +self.SetCarrier:Flush() +for CarrierGroupName,Carrier in pairs(self.SetCarrier:GetSet())do +local Carrier=Carrier +if Carrier:IsAlive()~=nil then +local AI_Cargo=self.AI_Cargo[Carrier] +if not AI_Cargo then +self.AI_Cargo[Carrier]=self:AICargo(Carrier,self.SetCargo,self.CombatRadius) +AI_Cargo=self.AI_Cargo[Carrier] +function AI_Cargo.OnAfterPickup(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,PickupZone) +self:Pickup(CarrierGroup,Coordinate,Speed,Height,PickupZone) +end +function AI_Cargo.OnAfterLoad(AI_Cargo,CarrierGroup,From,Event,To,PickupZone) +self:Load(CarrierGroup,PickupZone) +end +function AI_Cargo.OnAfterBoard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone) +self:Loading(CarrierGroup,Cargo,CarrierUnit,PickupZone) +end +function AI_Cargo.OnAfterLoaded(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,PickupZone) +self:Loaded(CarrierGroup,Cargo,CarrierUnit,PickupZone) +end +function AI_Cargo.OnAfterPickedUp(AI_Cargo,CarrierGroup,From,Event,To,PickupZone) +self:PickedUp(CarrierGroup,PickupZone) +self:Transport(CarrierGroup) +end +function AI_Cargo.OnAfterDeploy(AI_Cargo,CarrierGroup,From,Event,To,Coordinate,Speed,Height,DeployZone) +self:Deploy(CarrierGroup,Coordinate,Speed,Height,DeployZone) +end +function AI_Cargo.OnAfterUnload(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone) +self:Unloading(Carrier,Cargo,CarrierUnit,DeployZone) +end +function AI_Cargo.OnAfterUnboard(AI_Cargo,CarrierGroup,From,Event,To,Cargo,CarrierUnit,DeployZone) +self:Unloading(CarrierGroup,Cargo,CarrierUnit,DeployZone) +end +function AI_Cargo.OnAfterUnloaded(AI_Cargo,Carrier,From,Event,To,Cargo,CarrierUnit,DeployZone) +self:Unloaded(Carrier,Cargo,CarrierUnit,DeployZone) +end +function AI_Cargo.OnAfterDeployed(AI_Cargo,Carrier,From,Event,To,DeployZone) +self:Deployed(Carrier,DeployZone) +end +function AI_Cargo.OnAfterHome(AI_Cargo,Carrier,From,Event,To,Coordinate,Speed,Height,HomeZone) +self:Home(Carrier,Coordinate,Speed,Height,HomeZone) +end +end +self:T({Carrier=CarrierGroupName,IsRelocating=AI_Cargo:IsRelocating(),IsTransporting=AI_Cargo:IsTransporting()}) +if AI_Cargo:IsRelocating()==false and AI_Cargo:IsTransporting()==false then +local PickupCargo=nil +local PickupZone=nil +self.SetCargo:Flush() +for CargoName,Cargo in UTILS.spairs(self.SetCargo:GetSet(),function(t,a,b)return t[a]:GetWeight()=Cargo:GetWeight()then +self.PickupCargo[Carrier]=CargoCoordinate +PickupCargo=Cargo +break +else +local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.", +tostring(Cargo:GetName()),Cargo:GetWeight(),LargestLoadCapacity,tostring(Carrier:GetName())) +self:I(text) +end +end +end +end +end +if PickupCargo then +self.CarrierHome[Carrier]=nil +local PickupCoordinate=PickupCargo:GetCoordinate():GetRandomCoordinateInRadius(self.PickupOuterRadius,self.PickupInnerRadius) +AI_Cargo:Pickup(PickupCoordinate,math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),PickupZone) +break +else +if self.HomeZone then +if not self.CarrierHome[Carrier]then +self.CarrierHome[Carrier]=true +AI_Cargo:Home(self.HomeZone:GetRandomPointVec2(),math.random(self.PickupMinSpeed,self.PickupMaxSpeed),math.random(self.PickupMinHeight,self.PickupMaxHeight),self.HomeZone) +end +end +end +end +end +end +self:__Monitor(self.MonitorTimeInterval) +end +function AI_CARGO_DISPATCHER:onafterStart(From,Event,To) +self:__Monitor(-1) +end +function AI_CARGO_DISPATCHER:onafterTransport(From,Event,To,Carrier,Cargo) +if self.DeployZoneSet then +if self.AI_Cargo[Carrier]:IsTransporting()==true then +local DeployZone=self.DeployZoneSet:GetRandomZone() +local DeployCoordinate=DeployZone:GetCoordinate():GetRandomCoordinateInRadius(self.DeployOuterRadius,self.DeployInnerRadius) +self.AI_Cargo[Carrier]:__Deploy(0.1,DeployCoordinate,math.random(self.DeployMinSpeed,self.DeployMaxSpeed),math.random(self.DeployMinHeight,self.DeployMaxHeight),DeployZone) +end +end +self:F({Carrier=Carrier:GetName(),PickupCargo=self.PickupCargo}) +self.PickupCargo[Carrier]=nil +end +AI_CARGO_DISPATCHER_APC={ +ClassName="AI_CARGO_DISPATCHER_APC", +} +function AI_CARGO_DISPATCHER_APC:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet,CombatRadius) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(APCSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetDeploySpeed(120,70) +self:SetPickupSpeed(120,70) +self:SetPickupRadius(0,0) +self:SetDeployRadius(0,0) +self:SetPickupHeight() +self:SetDeployHeight() +self:SetCombatRadius(CombatRadius) +return self +end +function AI_CARGO_DISPATCHER_APC:AICargo(APC,CargoSet) +local aicargoapc=AI_CARGO_APC:New(APC,CargoSet,self.CombatRadius) +aicargoapc:SetDeployOffRoad(self.deployOffroad,self.deployFormation) +aicargoapc:SetPickupOffRoad(self.pickupOffroad,self.pickupFormation) +return aicargoapc +end +function AI_CARGO_DISPATCHER_APC:SetCombatRadius(CombatRadius) +self.CombatRadius=CombatRadius or 0 +return self +end +function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad,Formation) +self:SetPickupOffRoad(Offroad,Formation) +self:SetDeployOffRoad(Offroad,Formation) +return self +end +function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad,Formation) +self.pickupOffroad=Offroad +self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad,Formation) +self.deployOffroad=Offroad +self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad +return self +end +AI_CARGO_DISPATCHER_HELICOPTER={ +ClassName="AI_CARGO_DISPATCHER_HELICOPTER", +} +function AI_CARGO_DISPATCHER_HELICOPTER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(HelicopterSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetPickupSpeed(350,150) +self:SetDeploySpeed(350,150) +self:SetPickupRadius(0,0) +self:SetDeployRadius(0,0) +self:SetPickupHeight(500,200) +self:SetDeployHeight(500,200) +return self +end +function AI_CARGO_DISPATCHER_HELICOPTER:AICargo(Helicopter,CargoSet) +return AI_CARGO_HELICOPTER:New(Helicopter,CargoSet) +end +AI_CARGO_DISPATCHER_AIRPLANE={ +ClassName="AI_CARGO_DISPATCHER_AIRPLANE", +} +function AI_CARGO_DISPATCHER_AIRPLANE:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(AirplaneSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetPickupSpeed(1200,600) +self:SetDeploySpeed(1200,600) +self:SetPickupRadius(0,0) +self:SetDeployRadius(0,0) +self:SetPickupHeight(8000,6000) +self:SetDeployHeight(8000,6000) +self:SetMonitorTimeInterval(600) +return self +end +function AI_CARGO_DISPATCHER_AIRPLANE:AICargo(Airplane,CargoSet) +return AI_CARGO_AIRPLANE:New(Airplane,CargoSet) +end +AI_CARGO_DISPATCHER_SHIP={ +ClassName="AI_CARGO_DISPATCHER_SHIP" +} +function AI_CARGO_DISPATCHER_SHIP:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet,ShippingLane) +local self=BASE:Inherit(self,AI_CARGO_DISPATCHER:New(ShipSet,CargoSet,PickupZoneSet,DeployZoneSet)) +self:SetPickupSpeed(60,10) +self:SetDeploySpeed(60,10) +self:SetPickupRadius(500,6000) +self:SetDeployRadius(500,6000) +self:SetPickupHeight(0,0) +self:SetDeployHeight(0,0) +self:SetShippingLane(ShippingLane) +self:SetMonitorTimeInterval(600) +return self +end +function AI_CARGO_DISPATCHER_SHIP:SetShippingLane(ShippingLane) +self.ShippingLane=ShippingLane +return self +end +function AI_CARGO_DISPATCHER_SHIP:AICargo(Ship,CargoSet) +return AI_CARGO_SHIP:New(Ship,CargoSet,0,self.ShippingLane) +end +do +ACT_ASSIGN={ +ClassName="ACT_ASSIGN", +} +function ACT_ASSIGN:New() +local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIGN")) +self:AddTransition("UnAssigned","Start","Waiting") +self:AddTransition("Waiting","Assign","Assigned") +self:AddTransition("Waiting","Reject","Rejected") +self:AddTransition("*","Fail","Failed") +self:AddEndState("Assigned") +self:AddEndState("Rejected") +self:AddEndState("Failed") +self:SetStartState("UnAssigned") +return self +end +end +do +ACT_ASSIGN_ACCEPT={ +ClassName="ACT_ASSIGN_ACCEPT", +} +function ACT_ASSIGN_ACCEPT:New(TaskBriefing) +local self=BASE:Inherit(self,ACT_ASSIGN:New()) +self.TaskBriefing=TaskBriefing +return self +end +function ACT_ASSIGN_ACCEPT:Init(FsmAssign) +self.TaskBriefing=FsmAssign.TaskBriefing +end +function ACT_ASSIGN_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To) +self:__Assign(1) +end +function ACT_ASSIGN_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup) +self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName()) +end +end +do +ACT_ASSIGN_MENU_ACCEPT={ +ClassName="ACT_ASSIGN_MENU_ACCEPT", +} +function ACT_ASSIGN_MENU_ACCEPT:New(TaskBriefing) +local self=BASE:Inherit(self,ACT_ASSIGN:New()) +self.TaskBriefing=TaskBriefing +return self +end +function ACT_ASSIGN_MENU_ACCEPT:Init(TaskBriefing) +self.TaskBriefing=TaskBriefing +return self +end +function ACT_ASSIGN_MENU_ACCEPT:onafterStart(ProcessUnit,Task,From,Event,To) +self:GetCommandCenter():MessageToGroup("Task "..self.Task:GetName().." has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!",ProcessUnit:GetGroup(),120) +local TaskGroup=ProcessUnit:GetGroup() +self.Menu=MENU_GROUP:New(TaskGroup,"Task "..self.Task:GetName().." CONFIRMATION") +self.MenuAcceptTask=MENU_GROUP_COMMAND:New(TaskGroup,"Accept task "..self.Task:GetName(),self.Menu,self.MenuAssign,self,TaskGroup) +self.MenuRejectTask=MENU_GROUP_COMMAND:New(TaskGroup,"Reject task "..self.Task:GetName(),self.Menu,self.MenuReject,self,TaskGroup) +self:__Reject(120,TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:MenuAssign(TaskGroup) +self:__Assign(-1,TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:MenuReject(TaskGroup) +self:__Reject(-1,TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:onafterAssign(ProcessUnit,Task,From,Event,To,TaskGroup) +self.Menu:Remove() +end +function ACT_ASSIGN_MENU_ACCEPT:onafterReject(ProcessUnit,Task,From,Event,To,TaskGroup) +self:F({TaskGroup=TaskGroup}) +self.Menu:Remove() +self.Task:RejectGroup(TaskGroup) +end +function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned(ProcessUnit,Task,From,Event,To,TaskGroup) +self.Task:Assign(ProcessUnit,ProcessUnit:GetPlayerName()) +end +end +do +ACT_ROUTE={ +ClassName="ACT_ROUTE", +} +function ACT_ROUTE:New() +local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ROUTE")) +self:AddTransition("*","Reset","None") +self:AddTransition("None","Start","Routing") +self:AddTransition("*","Report","*") +self:AddTransition("Routing","Route","Routing") +self:AddTransition("Routing","Pause","Pausing") +self:AddTransition("Routing","Arrive","Arrived") +self:AddTransition("*","Cancel","Cancelled") +self:AddTransition("Arrived","Success","Success") +self:AddTransition("*","Fail","Failed") +self:AddTransition("","","") +self:AddTransition("","","") +self:AddEndState("Arrived") +self:AddEndState("Failed") +self:AddEndState("Cancelled") +self:SetStartState("None") +self:SetRouteMode("C") +return self +end +function ACT_ROUTE:SetMenuCancel(MenuGroup,MenuText,ParentMenu,MenuTime,MenuTag) +self.CancelMenuGroupCommand=MENU_GROUP_COMMAND:New( +MenuGroup, +MenuText, +ParentMenu, +self.MenuCancel, +self +):SetTime(MenuTime):SetTag(MenuTag) +ParentMenu:SetTime(MenuTime) +ParentMenu:Remove(MenuTime,MenuTag) +return self +end +function ACT_ROUTE:SetRouteMode(RouteMode) +self.RouteMode=RouteMode +return self +end +function ACT_ROUTE:GetRouteText(Controllable) +local RouteText="" +local Coordinate=nil +if self.Coordinate then +Coordinate=self.Coordinate +end +if self.Zone then +Coordinate=self.Zone:GetPointVec3(self.Altitude) +Coordinate:SetHeading(self.Heading) +end +local Task=self:GetTask() +local CC=self:GetTask():GetMission():GetCommandCenter() +if CC then +if CC:IsModeWWII()then +local ShortestDistance=0 +local ShortestReferencePoint=nil +local ShortestReferenceName="" +self:F({CC.ReferencePoints}) +for ZoneName,Zone in pairs(CC.ReferencePoints)do +self:F({ZoneName=ZoneName}) +local Zone=Zone +local ZoneCoord=Zone:GetCoordinate() +local ZoneDistance=ZoneCoord:Get2DDistance(Coordinate) +self:F({ShortestDistance,ShortestReferenceName}) +if ShortestDistance==0 or ZoneDistance=self.DisplayInterval then +self:T({HasArrived=HasArrived}) +if not HasArrived then +self:Report() +end +self.DisplayCount=1 +else +self.DisplayCount=self.DisplayCount+1 +end +if HasArrived then +self:__Arrive(1) +else +self:__Route(1) +end +return HasArrived +end +return false +end +end +do +ACT_ROUTE_POINT={ +ClassName="ACT_ROUTE_POINT", +} +function ACT_ROUTE_POINT:New(Coordinate,Range) +local self=BASE:Inherit(self,ACT_ROUTE:New()) +self.Coordinate=Coordinate +self.Range=Range or 0 +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +return self +end +function ACT_ROUTE_POINT:Init(FsmRoute) +self.Coordinate=FsmRoute.Coordinate +self.Range=FsmRoute.Range or 0 +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +self:SetStartState("None") +end +function ACT_ROUTE_POINT:SetCoordinate(Coordinate) +self:F2({Coordinate}) +self.Coordinate=Coordinate +end +function ACT_ROUTE_POINT:GetCoordinate() +self:F2({self.Coordinate}) +return self.Coordinate +end +function ACT_ROUTE_POINT:SetRange(Range) +self:F2({Range}) +self.Range=Range or 10000 +end +function ACT_ROUTE_POINT:GetRange() +self:F2({self.Range}) +return self.Range +end +function ACT_ROUTE_POINT:onfuncHasArrived(ProcessUnit) +if ProcessUnit:IsAlive()then +local Distance=self.Coordinate:Get2DDistance(ProcessUnit:GetCoordinate()) +if Distance<=self.Range then +local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived." +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +return true +end +end +return false +end +function ACT_ROUTE_POINT:onafterReport(ProcessUnit,From,Event,To) +local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit) +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update) +end +end +do +ACT_ROUTE_ZONE={ +ClassName="ACT_ROUTE_ZONE", +} +function ACT_ROUTE_ZONE:New(Zone) +local self=BASE:Inherit(self,ACT_ROUTE:New()) +self.Zone=Zone +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +return self +end +function ACT_ROUTE_ZONE:Init(FsmRoute) +self.Zone=FsmRoute.Zone +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +end +function ACT_ROUTE_ZONE:SetZone(Zone,Altitude,Heading) +self.Zone=Zone +self.Altitude=Altitude +self.Heading=Heading +end +function ACT_ROUTE_ZONE:GetZone() +return self.Zone +end +function ACT_ROUTE_ZONE:onfuncHasArrived(ProcessUnit) +if ProcessUnit:IsInZone(self.Zone)then +local RouteText="Task \""..self:GetTask():GetName().."\", you have arrived within the zone." +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +end +return ProcessUnit:IsInZone(self.Zone) +end +function ACT_ROUTE_ZONE:onafterReport(ProcessUnit,From,Event,To) +self:F({ProcessUnit=ProcessUnit}) +local RouteText="Task \""..self:GetTask():GetName().."\", "..self:GetRouteText(ProcessUnit) +self:GetCommandCenter():MessageTypeToGroup(RouteText,ProcessUnit:GetGroup(),MESSAGE.Type.Update) +end +end +do +ACT_ACCOUNT={ +ClassName="ACT_ACCOUNT", +TargetSetUnit=nil, +} +function ACT_ACCOUNT:New() +local self=BASE:Inherit(self,FSM_PROCESS:New()) +self:AddTransition("Assigned","Start","Waiting") +self:AddTransition("*","Wait","Waiting") +self:AddTransition("*","Report","Report") +self:AddTransition("*","Event","Account") +self:AddTransition("Account","Player","AccountForPlayer") +self:AddTransition("Account","Other","AccountForOther") +self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"More","Wait") +self:AddTransition({"Account","AccountForPlayer","AccountForOther"},"NoMore","Accounted") +self:AddTransition("*","Fail","Failed") +self:AddEndState("Failed") +self:SetStartState("Assigned") +return self +end +function ACT_ACCOUNT:onafterStart(ProcessUnit,From,Event,To) +self:HandleEvent(EVENTS.Dead,self.onfuncEventDead) +self:HandleEvent(EVENTS.Crash,self.onfuncEventCrash) +self:HandleEvent(EVENTS.Hit) +self:__Wait(1) +end +function ACT_ACCOUNT:onenterWaiting(ProcessUnit,From,Event,To) +if self.DisplayCount>=self.DisplayInterval then +self:Report() +self.DisplayCount=1 +else +self.DisplayCount=self.DisplayCount+1 +end +return true +end +function ACT_ACCOUNT:onafterEvent(ProcessUnit,From,Event,To,Event) +self:__NoMore(1) +end +end +do +ACT_ACCOUNT_DEADS={ +ClassName="ACT_ACCOUNT_DEADS", +} +function ACT_ACCOUNT_DEADS:New() +local self=BASE:Inherit(self,ACT_ACCOUNT:New()) +self.DisplayInterval=30 +self.DisplayCount=30 +self.DisplayMessage=true +self.DisplayTime=10 +self.DisplayCategory="HQ" +return self +end +function ACT_ACCOUNT_DEADS:Init(FsmAccount) +self.Task=self:GetTask() +self.TaskName=self.Task:GetName() +end +function ACT_ACCOUNT_DEADS:onenterReport(ProcessUnit,Task,From,Event,To) +local MessageText="Your group with assigned "..self.TaskName.." task has "..Task.TargetSetUnit:GetUnitTypesText().." targets left to be destroyed." +self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +end +function ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit,Task,From,Event,To,EventData) +self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) +if Task.TargetSetUnit:FindUnit(EventData.IniUnitName)then +local PlayerName=ProcessUnit:GetPlayerName() +local PlayerHit=self.PlayerHits and self.PlayerHits[EventData.IniUnitName] +if PlayerHit==PlayerName then +self:Player(EventData) +else +self:Other(EventData) +end +end +end +function ACT_ACCOUNT_DEADS:onenterAccountForPlayer(ProcessUnit,Task,From,Event,To,EventData) +self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) +local TaskGroup=ProcessUnit:GetGroup() +Task.TargetSetUnit:Remove(EventData.IniUnitName) +local MessageText="You have destroyed a target.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed." +self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +local PlayerName=ProcessUnit:GetPlayerName() +Task:AddProgress(PlayerName,"Destroyed "..EventData.IniTypeName,timer.getTime(),1) +if Task.TargetSetUnit:Count()>0 then +self:__More(1) +else +self:__NoMore(1) +end +end +function ACT_ACCOUNT_DEADS:onenterAccountForOther(ProcessUnit,Task,From,Event,To,EventData) +self:T({ProcessUnit:GetName(),Task:GetName(),From,Event,To,EventData}) +local TaskGroup=ProcessUnit:GetGroup() +Task.TargetSetUnit:Remove(EventData.IniUnitName) +local MessageText="One of the task targets has been destroyed.\nYour group assigned with task "..self.TaskName.." has\n"..Task.TargetSetUnit:Count().." targets ( "..Task.TargetSetUnit:GetUnitTypesText().." ) left to be destroyed." +self:GetCommandCenter():MessageTypeToGroup(MessageText,ProcessUnit:GetGroup(),MESSAGE.Type.Information) +if Task.TargetSetUnit:Count()>0 then +self:__More(1) +else +self:__NoMore(1) +end +end +function ACT_ACCOUNT_DEADS:OnEventHit(EventData) +self:T({"EventDead",EventData}) +if EventData.IniPlayerName and EventData.TgtDCSUnitName then +self.PlayerHits=self.PlayerHits or{} +self.PlayerHits[EventData.TgtDCSUnitName]=EventData.IniPlayerName +end +end +function ACT_ACCOUNT_DEADS:onfuncEventDead(EventData) +self:T({"EventDead",EventData}) +if EventData.IniDCSUnit then +self:Event(EventData) +end +end +function ACT_ACCOUNT_DEADS:onfuncEventCrash(EventData) +self:T({"EventDead",EventData}) +if EventData.IniDCSUnit then +self:Event(EventData) +end +end +end +do +ACT_ASSIST={ +ClassName="ACT_ASSIST", +} +function ACT_ASSIST:New() +local self=BASE:Inherit(self,FSM_PROCESS:New("ACT_ASSIST")) +self:AddTransition("None","Start","AwaitSmoke") +self:AddTransition("AwaitSmoke","Next","Smoking") +self:AddTransition("Smoking","Next","AwaitSmoke") +self:AddTransition("*","Stop","Success") +self:AddTransition("*","Fail","Failed") +self:AddEndState("Failed") +self:AddEndState("Success") +self:SetStartState("None") +return self +end +function ACT_ASSIST:onafterStart(ProcessUnit,From,Event,To) +local ProcessGroup=ProcessUnit:GetGroup() +local MissionMenu=self:GetMission():GetMenu(ProcessGroup) +local function MenuSmoke(MenuParam) +local self=MenuParam.self +local SmokeColor=MenuParam.SmokeColor +self.SmokeColor=SmokeColor +self:__Next(1) +end +self.Menu=MENU_GROUP:New(ProcessGroup,"Target acquisition",MissionMenu) +self.MenuSmokeBlue=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop blue smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Blue}) +self.MenuSmokeGreen=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop green smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Green}) +self.MenuSmokeOrange=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Orange smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Orange}) +self.MenuSmokeRed=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop Red smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.Red}) +self.MenuSmokeWhite=MENU_GROUP_COMMAND:New(ProcessGroup,"Drop White smoke on targets",self.Menu,MenuSmoke,{self=self,SmokeColor=SMOKECOLOR.White}) +end +function ACT_ASSIST:onafterStop(ProcessUnit,From,Event,To) +self.Menu:Remove() +end +end +do +ACT_ASSIST_SMOKE_TARGETS_ZONE={ +ClassName="ACT_ASSIST_SMOKE_TARGETS_ZONE", +} +function ACT_ASSIST_SMOKE_TARGETS_ZONE:New(TargetSetUnit,TargetZone) +local self=BASE:Inherit(self,ACT_ASSIST:New()) +self.TargetSetUnit=TargetSetUnit +self.TargetZone=TargetZone +return self +end +function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(FsmSmoke) +self.TargetSetUnit=FsmSmoke.TargetSetUnit +self.TargetZone=FsmSmoke.TargetZone +end +function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init(TargetSetUnit,TargetZone) +self.TargetSetUnit=TargetSetUnit +self.TargetZone=TargetZone +return self +end +function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking(ProcessUnit,From,Event,To) +self.TargetSetUnit:ForEachUnit( +function(SmokeUnit) +if math.random(1,(100*self.TargetSetUnit:Count())/4)<=100 then +SCHEDULER:New(self, +function() +if SmokeUnit:IsAlive()then +SmokeUnit:Smoke(self.SmokeColor,150) +end +end,{},math.random(10,60) +) +end +end +) +end +end +COMMANDCENTER={ +ClassName="COMMANDCENTER", +CommandCenterName="", +CommandCenterCoalition=nil, +CommandCenterPositionable=nil, +Name="", +ReferencePoints={}, +ReferenceNames={}, +CommunicationMode="80", +} +COMMANDCENTER.AutoAssignMethods={ +["Random"]=1, +["Distance"]=2, +["Priority"]=3, +} +function COMMANDCENTER:New(CommandCenterPositionable,CommandCenterName) +local self=BASE:Inherit(self,BASE:New()) +self.CommandCenterPositionable=CommandCenterPositionable +self.CommandCenterName=CommandCenterName or CommandCenterPositionable:GetName() +self.CommandCenterCoalition=CommandCenterPositionable:GetCoalition() +self.Missions={} +self:SetAutoAssignTasks(false) +self:SetAutoAcceptTasks(true) +self:SetAutoAssignMethod(COMMANDCENTER.AutoAssignMethods.Distance) +self:SetFlashStatus(false) +self:HandleEvent(EVENTS.Birth, +function(self,EventData) +if EventData.IniObjectCategory==1 then +local EventGroup=GROUP:Find(EventData.IniDCSGroup) +if EventGroup and EventGroup:IsAlive()and self:HasGroup(EventGroup)then +local CommandCenterMenu=MENU_GROUP:New(EventGroup,self:GetText()) +local MenuReporting=MENU_GROUP:New(EventGroup,"Missions Reports",CommandCenterMenu) +local MenuMissionsSummary=MENU_GROUP_COMMAND:New(EventGroup,"Missions Status Report",MenuReporting,self.ReportSummary,self,EventGroup) +local MenuMissionsDetails=MENU_GROUP_COMMAND:New(EventGroup,"Missions Players Report",MenuReporting,self.ReportMissionsPlayers,self,EventGroup) +self:ReportSummary(EventGroup) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +local PlayerGroup=EventData.IniGroup +Mission:JoinUnit(PlayerUnit,PlayerGroup) +end +self:SetMenu() +end +end +end +) +self:HandleEvent(EVENTS.MissionEnd, +function(self,EventData) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +Mission:Stop() +end +end +) +self:HandleEvent(EVENTS.PlayerLeaveUnit, +function(self,EventData) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +if Mission:IsENGAGED()then +Mission:AbortUnit(PlayerUnit) +end +end +end +) +self:HandleEvent(EVENTS.Crash, +function(self,EventData) +local PlayerUnit=EventData.IniUnit +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +if Mission:IsENGAGED()then +Mission:CrashUnit(PlayerUnit) +end +end +end +) +self:SetMenu() +_SETTINGS:SetSystemMenu(CommandCenterPositionable) +self:SetCommandMenu() +return self +end +function COMMANDCENTER:GetName() +return self.CommandCenterName +end +function COMMANDCENTER:GetText() +return"Command Center ["..self.CommandCenterName.."]" +end +function COMMANDCENTER:GetShortText() +return"CC ["..self.CommandCenterName.."]" +end +function COMMANDCENTER:GetCoalition() +return self.CommandCenterCoalition +end +function COMMANDCENTER:GetPositionable() +return self.CommandCenterPositionable +end +function COMMANDCENTER:GetMissions() +return self.Missions or{} +end +function COMMANDCENTER:AddMission(Mission) +self.Missions[Mission]=Mission +return Mission +end +function COMMANDCENTER:RemoveMission(Mission) +self.Missions[Mission]=nil +return Mission +end +function COMMANDCENTER:SetReferenceZones(ReferenceZonePrefix) +local MatchPattern="(.*)#(.*)" +self:F({MatchPattern=MatchPattern}) +for ReferenceZoneName in pairs(_DATABASE.ZONENAMES)do +local ZoneName,ReferenceName=string.match(ReferenceZoneName,MatchPattern) +self:F({ZoneName=ZoneName,ReferenceName=ReferenceName}) +if ZoneName and ReferenceName and ZoneName==ReferenceZonePrefix then +self.ReferencePoints[ReferenceZoneName]=ZONE:New(ReferenceZoneName) +self.ReferenceNames[ReferenceZoneName]=ReferenceName +end +end +return self +end +function COMMANDCENTER:SetModeWWII() +self.CommunicationMode="WWII" +return self +end +function COMMANDCENTER:IsModeWWII() +return self.CommunicationMode=="WWII" +end +function COMMANDCENTER:SetMenu() +self:F2() +local MenuTime=timer.getTime() +for MissionID,Mission in pairs(self:GetMissions()or{})do +local Mission=Mission +Mission:SetMenu(MenuTime) +end +for MissionID,Mission in pairs(self:GetMissions()or{})do +Mission=Mission +Mission:RemoveMenu(MenuTime) +end +end +function COMMANDCENTER:GetMenu(TaskGroup) +local MenuTime=timer.getTime() +self.CommandCenterMenus=self.CommandCenterMenus or{} +local CommandCenterMenu +local CommandCenterText=self:GetText() +CommandCenterMenu=MENU_GROUP:New(TaskGroup,CommandCenterText):SetTime(MenuTime) +self.CommandCenterMenus[TaskGroup]=CommandCenterMenu +if self.AutoAssignTasks==false then +local AssignTaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,"Assign Task",CommandCenterMenu,self.AssignTask,self,TaskGroup):SetTime(MenuTime):SetTag("AutoTask") +end +CommandCenterMenu:Remove(MenuTime,"AutoTask") +return self.CommandCenterMenus[TaskGroup] +end +function COMMANDCENTER:AssignTask(TaskGroup) +local Tasks={} +local AssignPriority=99999999 +local AutoAssignMethod=self.AutoAssignMethod +for MissionID,Mission in pairs(self:GetMissions())do +local Mission=Mission +local MissionTasks=Mission:GetGroupTasks(TaskGroup) +for MissionTaskName,MissionTask in pairs(MissionTasks or{})do +local MissionTask=MissionTask +if MissionTask:IsStatePlanned()or MissionTask:IsStateReplanned()or MissionTask:IsStateAssigned()then +local TaskPriority=MissionTask:GetAutoAssignPriority(self.AutoAssignMethod,self,TaskGroup) +if TaskPriority Adding TASK ",MissionName=self:GetName(),TaskName=TaskName}) +self.Tasks[TaskName]=Task +self:GetCommandCenter():SetMenu() +return Task +end +function MISSION:RemoveTask(Task) +local TaskName=Task:GetTaskName() +self:I({"<== Removing TASK ",MissionName=self:GetName(),TaskName=TaskName}) +self:F(TaskName) +self.Tasks[TaskName]=self.Tasks[TaskName]or{n=0} +self.Tasks[TaskName]=nil +Task=nil +collectgarbage() +self:GetCommandCenter():SetMenu() +return nil +end +function MISSION:IsCOMPLETED() +return self:Is("COMPLETED") +end +function MISSION:IsIDLE() +return self:Is("IDLE") +end +function MISSION:IsENGAGED() +return self:Is("ENGAGED") +end +function MISSION:IsFAILED() +return self:Is("FAILED") +end +function MISSION:IsHOLD() +return self:Is("HOLD") +end +function MISSION:HasGroup(TaskGroup) +local Has=false +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +if Task:HasGroup(TaskGroup)then +Has=true +break +end +end +return Has +end +function MISSION:GetTasksRemaining() +local TasksRemaining=0 +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +if Task:IsStateSuccess()or Task:IsStateFailed()then +else +TasksRemaining=TasksRemaining+1 +end +end +return TasksRemaining +end +function MISSION:GetTaskTypes() +local TaskTypeList={} +local TasksRemaining=0 +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +local TaskType=Task:GetType() +TaskTypeList[TaskType]=TaskType +end +return TaskTypeList +end +function MISSION:AddPlayerName(PlayerName) +self.PlayerNames=self.PlayerNames or{} +self.PlayerNames[PlayerName]=PlayerName +return self +end +function MISSION:GetPlayerNames() +return self.PlayerNames +end +function MISSION:ReportBriefing() +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Mission Briefing Report',Name,Status)) +Report:Add(self.MissionBriefing) +return Report:Text() +end +function MISSION:ReportPlayersPerTask(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Players per Task Report',Name,Status)) +local PlayerList={} +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +local PlayerNames=Task:GetPlayerNames() +for PlayerName,PlayerGroup in pairs(PlayerNames)do +PlayerList[PlayerName]=Task:GetName() +end +end +for PlayerName,TaskName in pairs(PlayerList)do +Report:Add(string.format(' - Player (%s): Task "%s"',PlayerName,TaskName)) +end +return Report:Text() +end +function MISSION:ReportPlayersProgress(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Players per Task Progress Report',Name,Status)) +local PlayerList={} +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +local TaskName=Task:GetName() +local Goal=Task:GetGoal() +PlayerList[TaskName]=PlayerList[TaskName]or{} +if Goal then +local TotalContributions=Goal:GetTotalContributions() +local PlayerContributions=Goal:GetPlayerContributions() +self:F({TotalContributions=TotalContributions,PlayerContributions=PlayerContributions}) +for PlayerName,PlayerContribution in pairs(PlayerContributions)do +PlayerList[TaskName][PlayerName]=string.format('Player (%s): Task "%s": %d%%',PlayerName,TaskName,PlayerContributions[PlayerName]*100/TotalContributions) +end +else +PlayerList[TaskName]["_"]=string.format('Player (---): Task "%s": %d%%',TaskName,0) +end +end +for TaskName,TaskData in pairs(PlayerList)do +for PlayerName,TaskText in pairs(TaskData)do +Report:Add(string.format(' - %s',TaskText)) +end +end +return Report:Text() +end +function MISSION:MarkTargetLocations(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - All Tasks are marked on the map. Select a Task from the Mission Menu and Join the Task!!!',Name,Status)) +for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)" +Report:Add(string.format('%s - %s - Task Overview Report',Name,Status)) +for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)" +Report:Add(string.format('%s - %s - %s Tasks Report',Name,Status,TaskStatus)) +local Tasks=0 +for TaskID,Task in UTILS.spairs(self:GetTasks(),function(t,a,b)return t[a]:ReportOrder(ReportGroup)=8 then +break +end +end +return Report:Text() +end +function MISSION:ReportDetails(ReportGroup) +local Report=REPORT:New() +local Name=self:GetText() +local Status="<"..self:GetState()..">" +Report:Add(string.format('%s - %s - Task Detailed Report',Name,Status)) +local TasksRemaining=0 +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +Report:Add(string.rep("-",140)) +Report:Add(Task:ReportDetails(ReportGroup)) +end +return Report:Text() +end +function MISSION:GetTasks() +return self.Tasks or{} +end +function MISSION:GetGroupTasks(TaskGroup) +local Tasks={} +for TaskID,Task in pairs(self:GetTasks())do +local Task=Task +if Task:HasGroup(TaskGroup)then +Tasks[#Tasks+1]=Task +end +end +return Tasks +end +function MISSION:MenuReportBriefing(ReportGroup) +local Report=self:ReportBriefing() +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Briefing) +end +function MISSION:MenuMarkTargetLocations(ReportGroup) +local Report=self:MarkTargetLocations(ReportGroup) +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportTasksSummary(ReportGroup) +local Report=self:ReportSummary(ReportGroup) +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportTasksPerStatus(ReportGroup,TaskStatus) +local Report=self:ReportOverview(ReportGroup,TaskStatus) +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportPlayersPerTask(ReportGroup) +local Report=self:ReportPlayersPerTask() +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +function MISSION:MenuReportPlayersProgress(ReportGroup) +local Report=self:ReportPlayersProgress() +self:GetCommandCenter():MessageTypeToGroup(Report,ReportGroup,MESSAGE.Type.Overview) +end +TASK={ +ClassName="TASK", +TaskScheduler=nil, +ProcessClasses={}, +Processes={}, +Players=nil, +Scores={}, +Menu={}, +SetGroup=nil, +FsmTemplate=nil, +Mission=nil, +CommandCenter=nil, +TimeOut=0, +AssignedGroups={}, +} +function TASK:New(Mission,SetGroupAssign,TaskName,TaskType,TaskBriefing) +local self=BASE:Inherit(self,FSM_TASK:New(TaskName)) +self:SetStartState("Planned") +self:AddTransition("Planned","Assign","Assigned") +self:AddTransition("Assigned","AssignUnit","Assigned") +self:AddTransition("Assigned","Success","Success") +self:AddTransition("Assigned","Hold","Hold") +self:AddTransition("Assigned","Fail","Failed") +self:AddTransition({"Planned","Assigned"},"Abort","Aborted") +self:AddTransition("Assigned","Cancel","Cancelled") +self:AddTransition("Assigned","Goal","*") +self.Fsm={} +local Fsm=self:GetUnitProcess() +Fsm:SetStartState("Planned") +Fsm:AddProcess("Planned","Accept",ACT_ASSIGN_ACCEPT:New(self.TaskBriefing),{Assigned="Assigned",Rejected="Reject"}) +Fsm:AddTransition("Assigned","Assigned","*") +self:AddTransition("*","PlayerCrashed","*") +self:AddTransition("*","PlayerAborted","*") +self:AddTransition("*","PlayerRejected","*") +self:AddTransition("*","PlayerDead","*") +self:AddTransition({"Failed","Aborted","Cancelled"},"Replan","Planned") +self:AddTransition("*","TimeOut","Cancelled") +self:F("New TASK "..TaskName) +self.Processes={} +self.Mission=Mission +self.CommandCenter=Mission:GetCommandCenter() +self.SetGroup=SetGroupAssign +self:SetType(TaskType) +self:SetName(TaskName) +self:SetID(Mission:GetNextTaskID(self)) +self:SetBriefing(TaskBriefing) +self.TaskInfo=TASKINFO:New(self) +self.TaskProgress={} +return self +end +function TASK:GetUnitProcess(TaskUnit) +if TaskUnit then +return self:GetStateMachine(TaskUnit) +else +self.FsmTemplate=self.FsmTemplate or FSM_PROCESS:New() +return self.FsmTemplate +end +end +function TASK:SetUnitProcess(FsmTemplate) +self.FsmTemplate=FsmTemplate +end +function TASK:JoinUnit(PlayerUnit,PlayerGroup) +self:F({PlayerUnit=PlayerUnit,PlayerGroup=PlayerGroup}) +local PlayerUnitAdded=false +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStatePlanned()or self:IsStateReplanned()then +end +if self:IsStateAssigned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +self:F({IsGroupAssigned=IsGroupAssigned}) +if IsGroupAssigned then +self:AssignToUnit(PlayerUnit) +self:MessageToGroups(PlayerUnit:GetPlayerName().." joined Task "..self:GetName()) +end +end +end +return PlayerUnitAdded +end +function TASK:RejectGroup(PlayerGroup) +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStatePlanned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +if IsGroupAssigned then +local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() +self:GetMission():GetCommandCenter():MessageToGroup("Task "..self:GetName().." has been rejected! We will select another task.",PlayerGroup) +self:UnAssignFromGroup(PlayerGroup) +self:PlayerRejected(PlayerGroup:GetUnit(1)) +end +end +end +return self +end +function TASK:AbortGroup(PlayerGroup) +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStateAssigned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +if IsGroupAssigned then +local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() +self:UnAssignFromGroup(PlayerGroup) +PlayerGroups:Flush(self) +local IsRemaining=false +for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do +if self:IsGroupAssigned(AssignedGroup)==true then +IsRemaining=true +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +break +end +end +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +if IsRemaining==false then +self:Abort() +end +self:PlayerAborted(PlayerGroup:GetUnit(1)) +end +end +end +return self +end +function TASK:CrashGroup(PlayerGroup) +self:F({PlayerGroup=PlayerGroup}) +local PlayerGroups=self:GetGroups() +if PlayerGroups:IsIncludeObject(PlayerGroup)then +if self:IsStateAssigned()then +local IsGroupAssigned=self:IsGroupAssigned(PlayerGroup) +self:F({IsGroupAssigned=IsGroupAssigned}) +if IsGroupAssigned then +local PlayerName=PlayerGroup:GetUnit(1):GetPlayerName() +self:MessageToGroups(PlayerName.." crashed! ") +self:UnAssignFromGroup(PlayerGroup) +PlayerGroups:Flush(self) +local IsRemaining=false +for GroupName,AssignedGroup in pairs(PlayerGroups:GetSet()or{})do +if self:IsGroupAssigned(AssignedGroup)==true then +IsRemaining=true +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +break +end +end +self:F({Task=self:GetName(),IsRemaining=IsRemaining}) +if IsRemaining==false then +self:Abort() +end +self:PlayerCrashed(PlayerGroup:GetUnit(1)) +end +end +end +return self +end +function TASK:GetMission() +return self.Mission +end +function TASK:GetGroups() +return self.SetGroup +end +function TASK:AddGroups(GroupSet) +GroupSet=GroupSet or SET_GROUP:New() +self.SetGroup:ForEachGroup( +function(GroupItem) +GroupSet:Add(GroupItem:GetName(),GroupItem) +end +) +return GroupSet +end +do +function TASK:IsGroupAssigned(TaskGroup) +local TaskGroupName=TaskGroup:GetName() +if self.AssignedGroups[TaskGroupName]then +return true +end +return false +end +function TASK:SetGroupAssigned(TaskGroup) +local TaskName=self:GetName() +local TaskGroupName=TaskGroup:GetName() +self.AssignedGroups[TaskGroupName]=TaskGroup +self:F(string.format("Task %s is assigned to %s",TaskName,TaskGroupName)) +self:GetMission():SetGroupAssigned(TaskGroup) +local SetAssignedGroups=self:GetGroups() +return self +end +function TASK:ClearGroupAssignment(TaskGroup) +local TaskName=self:GetName() +local TaskGroupName=TaskGroup:GetName() +self.AssignedGroups[TaskGroupName]=nil +self:GetMission():ClearGroupAssignment(TaskGroup) +local SetAssignedGroups=self:GetGroups() +SetAssignedGroups:ForEachGroup( +function(AssignedGroup) +if self:IsGroupAssigned(AssignedGroup)then +else +end +end +) +return self +end +end +do +function TASK:SetAssignMethod(AcceptClass) +local ProcessTemplate=self:GetUnitProcess() +ProcessTemplate:SetProcess("Planned","Accept",AcceptClass) +end +function TASK:AssignToGroup(TaskGroup) +self:F(TaskGroup:GetName()) +local TaskGroupName=TaskGroup:GetName() +local Mission=self:GetMission() +local CommandCenter=Mission:GetCommandCenter() +self:SetGroupAssigned(TaskGroup) +local TaskUnits=TaskGroup:GetUnits() +for UnitID,UnitData in pairs(TaskUnits)do +local TaskUnit=UnitData +local PlayerName=TaskUnit:GetPlayerName() +self:F(PlayerName) +if PlayerName~=nil and PlayerName~=""then +self:AssignToUnit(TaskUnit) +CommandCenter:MessageToGroup( +string.format('Task "%s": Briefing for player (%s):\n%s', +self:GetName(), +PlayerName, +self:GetBriefing() +),TaskGroup +) +end +end +CommandCenter:SetMenu() +self:MenuFlashTaskStatus(TaskGroup,self:GetMission():GetCommandCenter().FlashStatus) +return self +end +function TASK:UnAssignFromGroup(TaskGroup) +self:F2({TaskGroup=TaskGroup:GetName()}) +self:ClearGroupAssignment(TaskGroup) +local TaskUnits=TaskGroup:GetUnits() +for UnitID,UnitData in pairs(TaskUnits)do +local TaskUnit=UnitData +local PlayerName=TaskUnit:GetPlayerName() +if PlayerName~=nil and PlayerName~=""then +self:UnAssignFromUnit(TaskUnit) +end +end +local Mission=self:GetMission() +local CommandCenter=Mission:GetCommandCenter() +CommandCenter:SetMenu() +self:MenuFlashTaskStatus(TaskGroup,false) +end +end +function TASK:HasGroup(FindGroup) +local SetAttackGroup=self:GetGroups() +return SetAttackGroup:FindGroup(FindGroup:GetName()) +end +function TASK:AssignToUnit(TaskUnit) +self:F(TaskUnit:GetName()) +local FsmTemplate=self:GetUnitProcess() +local FsmUnit=self:SetStateMachine(TaskUnit,FsmTemplate:Copy(TaskUnit,self)) +FsmUnit:SetStartState("Planned") +FsmUnit:Accept() +return self +end +function TASK:UnAssignFromUnit(TaskUnit) +self:F(TaskUnit:GetName()) +self:RemoveStateMachine(TaskUnit) +self:RemoveTaskControlMenu(TaskUnit) +return self +end +function TASK:SetTimeOut(Timer) +self:F(Timer) +self.TimeOut=Timer +self:__TimeOut(self.TimeOut) +return self +end +function TASK:MessageToGroups(Message) +self:F({Message=Message}) +local Mission=self:GetMission() +local CC=Mission:GetCommandCenter() +for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do +TaskGroup=TaskGroup +if TaskGroup:IsAlive()==true then +CC:MessageToGroup(Message,TaskGroup,TaskGroup:GetName()) +end +end +end +function TASK:SendBriefingToAssignedGroups() +self:F2() +for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()then +if self:IsGroupAssigned(TaskGroup)then +TaskGroup:Message(self.TaskBriefing,60) +end +end +end +end +function TASK:UnAssignFromGroups() +self:F2() +for TaskGroupName,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()==true then +if self:IsGroupAssigned(TaskGroup)then +self:UnAssignFromGroup(TaskGroup) +end +end +end +end +function TASK:HasAliveUnits() +self:F() +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()==true then +if self:IsStateAssigned()then +if self:IsGroupAssigned(TaskGroup)then +for TaskUnitID,TaskUnit in pairs(TaskGroup:GetUnits())do +if TaskUnit:IsAlive()then +self:T({HasAliveUnits=true}) +return true +end +end +end +end +end +end +self:T({HasAliveUnits=false}) +return false +end +function TASK:SetMenu(MenuTime) +self:F({self:GetName(),MenuTime}) +for TaskGroupID,TaskGroupData in pairs(self.SetGroup:GetSet())do +local TaskGroup=TaskGroupData +if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then +local Mission=self:GetMission() +local MissionMenu=Mission:GetMenu(TaskGroup) +if MissionMenu then +self:SetMenuForGroup(TaskGroup,MenuTime) +end +end +end +end +function TASK:SetMenuForGroup(TaskGroup,MenuTime) +if self:IsStatePlanned()or self:IsStateAssigned()then +self:SetPlannedMenuForGroup(TaskGroup,MenuTime) +if self:IsGroupAssigned(TaskGroup)then +self:SetAssignedMenuForGroup(TaskGroup,MenuTime) +end +end +end +function TASK:SetPlannedMenuForGroup(TaskGroup,MenuTime) +self:F(TaskGroup:GetName()) +local Mission=self:GetMission() +local MissionName=Mission:GetName() +local MissionMenu=Mission:GetMenu(TaskGroup) +local TaskType=self:GetType() +local TaskPlayerCount=self:GetPlayerCount() +local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount) +local TaskText=string.format("%s",self:GetName()) +local TaskName=string.format("%s",self:GetName()) +self.MenuPlanned=self.MenuPlanned or{} +self.MenuPlanned[TaskGroup]=MENU_GROUP_DELAYED:New(TaskGroup,"Join Planned Task",MissionMenu,Mission.MenuReportTasksPerStatus,Mission,TaskGroup,"Planned"):SetTime(MenuTime):SetTag("Tasking") +local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskType,self.MenuPlanned[TaskGroup]):SetTime(MenuTime):SetTag("Tasking") +local TaskTypeMenu=MENU_GROUP_DELAYED:New(TaskGroup,TaskText,TaskTypeMenu):SetTime(MenuTime):SetTag("Tasking") +if not Mission:IsGroupAssigned(TaskGroup)then +local JoinTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Join Task"),TaskTypeMenu,self.MenuAssignToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +local MarkTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Mark Task Location on Map"),TaskTypeMenu,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +end +local ReportTaskMenu=MENU_GROUP_COMMAND_DELAYED:New(TaskGroup,string.format("Report Task Details"),TaskTypeMenu,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +return self +end +function TASK:SetAssignedMenuForGroup(TaskGroup,MenuTime) +self:F({TaskGroup:GetName(),MenuTime}) +local TaskType=self:GetType() +local TaskPlayerCount=self:GetPlayerCount() +local TaskPlayerString=string.format(" (%dp)",TaskPlayerCount) +local TaskText=string.format("%s%s",self:GetName(),TaskPlayerString) +local TaskName=string.format("%s",self:GetName()) +for UnitName,TaskUnit in pairs(TaskGroup:GetPlayerUnits())do +local TaskUnit=TaskUnit +if TaskUnit then +local MenuControl=self:GetTaskControlMenu(TaskUnit) +local TaskControl=MENU_GROUP:New(TaskGroup,"Control Task",MenuControl):SetTime(MenuTime):SetTag("Tasking") +if self:IsStateAssigned()then +local TaskMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Abort Task"),TaskControl,self.MenuTaskAbort,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +end +local MarkMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Mark Task Location on Map"),TaskControl,self.MenuMarkToGroup,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +local TaskTypeMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Report Task Details"),TaskControl,self.MenuTaskStatus,self,TaskGroup):SetTime(MenuTime):SetTag("Tasking") +if not self.FlashTaskStatus then +local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,true):SetTime(MenuTime):SetTag("Tasking") +else +local TaskFlashStatusMenu=MENU_GROUP_COMMAND:New(TaskGroup,string.format("Stop Flash Task Details"),TaskControl,self.MenuFlashTaskStatus,self,TaskGroup,nil):SetTime(MenuTime):SetTag("Tasking") +end +end +end +return self +end +function TASK:RemoveMenu(MenuTime) +self:F({self:GetName(),MenuTime}) +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if TaskGroup:IsAlive()==true then +local TaskGroup=TaskGroup +if TaskGroup:IsAlive()==true and TaskGroup:GetPlayerNames()then +self:RefreshMenus(TaskGroup,MenuTime) +end +end +end +end +function TASK:RefreshMenus(TaskGroup,MenuTime) +self:F({TaskGroup:GetName(),MenuTime}) +local Mission=self:GetMission() +local MissionName=Mission:GetName() +local MissionMenu=Mission:GetMenu(TaskGroup) +local TaskName=self:GetName() +self.MenuPlanned=self.MenuPlanned or{} +local PlannedMenu=self.MenuPlanned[TaskGroup] +self.MenuAssigned=self.MenuAssigned or{} +local AssignedMenu=self.MenuAssigned[TaskGroup] +if PlannedMenu then +self.MenuPlanned[TaskGroup]=PlannedMenu:Remove(MenuTime,"Tasking") +PlannedMenu:Set() +end +if AssignedMenu then +self.MenuAssigned[TaskGroup]=AssignedMenu:Remove(MenuTime,"Tasking") +AssignedMenu:Set() +end +end +function TASK:RemoveAssignedMenuForGroup(TaskGroup) +self:F() +local Mission=self:GetMission() +local MissionName=Mission:GetName() +local MissionMenu=Mission:GetMenu(TaskGroup) +if MissionMenu then +MissionMenu:RemoveSubMenus() +end +end +function TASK:MenuAssignToGroup(TaskGroup) +self:F("Join Task menu selected") +self:AssignToGroup(TaskGroup) +end +function TASK:MenuMarkToGroup(TaskGroup) +self:F() +self:UpdateTaskInfo(self.DetectedItem) +local TargetCoordinates=self.TaskInfo:GetData("Coordinates") +if TargetCoordinates then +for TargetCoordinateID,TargetCoordinate in pairs(TargetCoordinates)do +local Report=REPORT:New():SetIndent(0) +self.TaskInfo:Report(Report,"M",TaskGroup,self) +local MarkText=Report:Text(", ") +self:F({Coordinate=TargetCoordinate,MarkText=MarkText}) +TargetCoordinate:MarkToGroup(MarkText,TaskGroup) +end +else +local TargetCoordinate=self.TaskInfo:GetData("Coordinate") +if TargetCoordinate then +local Report=REPORT:New():SetIndent(0) +self.TaskInfo:Report(Report,"M",TaskGroup,self) +local MarkText=Report:Text(", ") +self:F({Coordinate=TargetCoordinate,MarkText=MarkText}) +TargetCoordinate:MarkToGroup(MarkText,TaskGroup) +end +end +end +function TASK:MenuTaskStatus(TaskGroup) +if TaskGroup:IsAlive()then +local ReportText=self:ReportDetails(TaskGroup) +self:T(ReportText) +self:GetMission():GetCommandCenter():MessageTypeToGroup(ReportText,TaskGroup,MESSAGE.Type.Detailed) +end +end +function TASK:MenuFlashTaskStatus(TaskGroup,Flash) +self.FlashTaskStatus=Flash +if self.FlashTaskStatus then +self.FlashTaskScheduler,self.FlashTaskScheduleID=SCHEDULER:New(self,self.MenuTaskStatus,{TaskGroup},0,60) +else +if self.FlashTaskScheduler then +self.FlashTaskScheduler:Stop(self.FlashTaskScheduleID) +self.FlashTaskScheduler=nil +self.FlashTaskScheduleID=nil +end +end +end +function TASK:MenuTaskAbort(TaskGroup) +self:AbortGroup(TaskGroup) +end +function TASK:GetTaskName() +return self.TaskName +end +function TASK:GetTaskBriefing() +return self.TaskBriefing +end +function TASK:GetProcessTemplate(ProcessName) +local ProcessTemplate=self.ProcessClasses[ProcessName] +return ProcessTemplate +end +function TASK:FailProcesses(TaskUnitName) +for ProcessID,ProcessData in pairs(self.Processes[TaskUnitName])do +local Process=ProcessData +Process.Fsm:Fail() +end +end +function TASK:SetStateMachine(TaskUnit,Fsm) +self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil,Fsm:GetClassNameAndID()}) +self.Fsm[TaskUnit]=Fsm +return Fsm +end +function TASK:GetStateMachine(TaskUnit) +self:F2({TaskUnit,self.Fsm[TaskUnit]~=nil}) +return self.Fsm[TaskUnit] +end +function TASK:RemoveStateMachine(TaskUnit) +self:F({TaskUnit=TaskUnit:GetName(),HasFsm=(self.Fsm[TaskUnit]~=nil)}) +if self.Fsm[TaskUnit]then +self.Fsm[TaskUnit]:Remove() +self.Fsm[TaskUnit]=nil +end +collectgarbage() +self:F("Garbage Collected, Processes should be finalized now ...") +end +function TASK:HasStateMachine(TaskUnit) +self:F({TaskUnit,self.Fsm[TaskUnit]~=nil}) +return(self.Fsm[TaskUnit]~=nil) +end +function TASK:GetScoring() +return self.Mission:GetScoring() +end +function TASK:GetTaskIndex() +local TaskType=self:GetType() +local TaskName=self:GetName() +return TaskType.."."..TaskName +end +function TASK:SetName(TaskName) +self.TaskName=TaskName +end +function TASK:GetName() +return self.TaskName +end +function TASK:SetType(TaskType) +self.TaskType=TaskType +end +function TASK:GetType() +return self.TaskType +end +function TASK:SetID(TaskID) +self.TaskID=TaskID +end +function TASK:GetID() +return self.TaskID +end +function TASK:StateSuccess() +self:SetState(self,"State","Success") +return self +end +function TASK:IsStateSuccess() +return self:Is("Success") +end +function TASK:StateFailed() +self:SetState(self,"State","Failed") +return self +end +function TASK:IsStateFailed() +return self:Is("Failed") +end +function TASK:StatePlanned() +self:SetState(self,"State","Planned") +return self +end +function TASK:IsStatePlanned() +return self:Is("Planned") +end +function TASK:StateAborted() +self:SetState(self,"State","Aborted") +return self +end +function TASK:IsStateAborted() +return self:Is("Aborted") +end +function TASK:StateCancelled() +self:SetState(self,"State","Cancelled") +return self +end +function TASK:IsStateCancelled() +return self:Is("Cancelled") +end +function TASK:StateAssigned() +self:SetState(self,"State","Assigned") +return self +end +function TASK:IsStateAssigned() +return self:Is("Assigned") +end +function TASK:StateHold() +self:SetState(self,"State","Hold") +return self +end +function TASK:IsStateHold() +return self:Is("Hold") +end +function TASK:StateReplanned() +self:SetState(self,"State","Replanned") +return self +end +function TASK:IsStateReplanned() +return self:Is("Replanned") +end +function TASK:GetStateString() +return self:GetState(self,"State") +end +function TASK:SetBriefing(TaskBriefing) +self:F(TaskBriefing) +self.TaskBriefing=TaskBriefing +return self +end +function TASK:GetBriefing() +return self.TaskBriefing +end +function TASK:onenterAssigned(From,Event,To,PlayerUnit,PlayerName) +if From~="Assigned"then +local PlayerNames=self:GetPlayerNames() +local PlayerText=REPORT:New() +for PlayerName,TaskName in pairs(PlayerNames)do +PlayerText:Add(PlayerName) +end +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is assigned to players "..PlayerText:Text(",")..". Good Luck!") +self:SetGoalTotal() +if self.Dispatcher then +self:F("Firing Assign event ") +self.Dispatcher:Assign(self,PlayerUnit,PlayerName) +end +self:GetMission():__Start(1) +self:__Goal(-10,PlayerUnit,PlayerName) +self:SetMenu() +self:F({"--> Task Assigned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"--> Task Player Names",PlayerNames=PlayerNames}) +end +end +function TASK:onenterSuccess(From,Event,To) +self:F({"<-> Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"<-> Task Player Names",PlayerNames=self:GetPlayerNames()}) +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." is successful! Good job!") +self:UnAssignFromGroups() +self:GetMission():__MissionGoals(1) +end +function TASK:onenterAborted(From,Event,To) +self:F({"<-- Task Aborted",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"<-- Task Player Names",PlayerNames=self:GetPlayerNames()}) +if From~="Aborted"then +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been aborted! Task may be replanned.") +self:__Replan(5) +self:SetMenu() +end +end +function TASK:onenterCancelled(From,Event,To) +self:F({"<-- Task Cancelled",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"<-- Player Names",PlayerNames=self:GetPlayerNames()}) +if From~="Cancelled"then +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has been cancelled! The tactical situation has changed.") +self:UnAssignFromGroups() +self:SetMenu() +end +end +function TASK:onafterReplan(From,Event,To) +self:F({"Task Replanned",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()}) +self:GetMission():GetCommandCenter():MessageToCoalition("Replanning Task "..self:GetName()..".") +self:SetMenu() +end +function TASK:onenterFailed(From,Event,To) +self:F({"Task Failed",TaskName=self:GetName(),Mission=self:GetMission():GetName()}) +self:F({"Task Player Names",PlayerNames=self:GetPlayerNames()}) +self:GetMission():GetCommandCenter():MessageToCoalition("Task "..self:GetName().." has failed!") +self:UnAssignFromGroups() +end +function TASK:onstatechange(From,Event,To) +if self:IsTrace()then +end +if self.Scores[To]then +local Scoring=self:GetScoring() +if Scoring then +self:F({self.Scores[To].ScoreText,self.Scores[To].Score}) +Scoring:_AddMissionScore(self.Mission,self.Scores[To].ScoreText,self.Scores[To].Score) +end +end +end +function TASK:onenterPlanned(From,Event,To) +if not self.TimeOut==0 then +self.__TimeOut(self.TimeOut) +end +end +function TASK:onbeforeTimeOut(From,Event,To) +if From=="Planned"then +self:RemoveMenu() +return true +end +return false +end +do +function TASK:SetGoal(Goal) +self.Goal=Goal +end +function TASK:GetGoal() +return self.Goal +end +function TASK:SetDispatcher(Dispatcher) +self.Dispatcher=Dispatcher +end +function TASK:SetDetection(Detection,DetectedItem) +self:F({DetectedItem,Detection}) +self.Detection=Detection +self.DetectedItem=DetectedItem +end +end +do +function TASK:ReportSummary(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Report=REPORT:New() +Report:Add("Task "..self:GetName()) +Report:Add("State: <"..self:GetState()..">") +self.TaskInfo:Report(Report,"S",ReportGroup,self) +return Report:Text(', ') +end +function TASK:ReportOverview(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local TaskName=self:GetName() +local Report=REPORT:New() +self.TaskInfo:Report(Report,"O",ReportGroup,self) +return Report:Text() +end +function TASK:GetPlayerCount() +local PlayerCount=0 +for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do +local PlayerGroup=PlayerGroup +if PlayerGroup:IsAlive()==true then +if self:IsGroupAssigned(PlayerGroup)then +local PlayerNames=PlayerGroup:GetPlayerNames() +PlayerCount=PlayerCount+((PlayerNames)and#PlayerNames or 0) +end +end +end +return PlayerCount +end +function TASK:GetPlayerNames() +local PlayerNameMap={} +for TaskGroupID,PlayerGroup in pairs(self:GetGroups():GetSet())do +local PlayerGroup=PlayerGroup +if PlayerGroup:IsAlive()==true then +if self:IsGroupAssigned(PlayerGroup)then +local PlayerNames=PlayerGroup:GetPlayerNames() +for PlayerNameID,PlayerName in pairs(PlayerNames or{})do +PlayerNameMap[PlayerName]=PlayerGroup +end +end +end +end +return PlayerNameMap +end +function TASK:ReportDetails(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Report=REPORT:New():SetIndent(3) +local Name=self:GetName() +local Status="<"..self:GetState()..">" +Report:Add("Task "..Name.." - "..Status.." - Detailed Report") +local PlayerNames=self:GetPlayerNames() +local PlayerReport=REPORT:New() +for PlayerName,PlayerGroup in pairs(PlayerNames)do +PlayerReport:Add("Players group "..PlayerGroup:GetCallsign()..": "..PlayerName) +end +local Players=PlayerReport:Text() +if Players~=""then +Report:AddIndent("Players assigned:","-") +Report:AddIndent(Players) +end +self.TaskInfo:Report(Report,"D",ReportGroup,self) +return Report:Text() +end +end +do +function TASK:AddProgress(PlayerName,ProgressText,ProgressTime,ProgressPoints) +self.TaskProgress=self.TaskProgress or{} +self.TaskProgress[ProgressTime]=self.TaskProgress[ProgressTime]or{} +self.TaskProgress[ProgressTime].PlayerName=PlayerName +self.TaskProgress[ProgressTime].ProgressText=ProgressText +self.TaskProgress[ProgressTime].ProgressPoints=ProgressPoints +self:GetMission():AddPlayerName(PlayerName) +return self +end +function TASK:GetPlayerProgress(PlayerName) +local ProgressPlayer=0 +for ProgressTime,ProgressData in pairs(self.TaskProgress)do +if PlayerName==ProgressData.PlayerName then +ProgressPlayer=ProgressPlayer+ProgressData.ProgressPoints +end +end +return ProgressPlayer +end +function TASK:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountPlayer","Player "..PlayerName.." has achieved progress.",Score) +return self +end +function TASK:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","The task is a success!",Score) +return self +end +function TASK:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The task is a failure!",Penalty) +return self +end +end +do +function TASK:InitTaskControlMenu(TaskUnit) +self.TaskControlMenuTime=timer.getTime() +return self.TaskControlMenuTime +end +function TASK:GetTaskControlMenu(TaskUnit,TaskName) +TaskName=TaskName or"" +local TaskGroup=TaskUnit:GetGroup() +local TaskPlayerCount=TaskGroup:GetPlayerCount() +if TaskPlayerCount<=1 then +self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control"):SetTime(self.TaskControlMenuTime) +else +self.TaskControlMenu=MENU_GROUP:New(TaskUnit:GetGroup(),"Task "..self:GetName().." control for "..TaskUnit:GetPlayerName()):SetTime(self.TaskControlMenuTime) +end +return self.TaskControlMenu +end +function TASK:RemoveTaskControlMenu(TaskUnit) +if self.TaskControlMenu then +self.TaskControlMenu:Remove() +self.TaskControlMenu=nil +end +end +function TASK:RefreshTaskControlMenu(TaskUnit,MenuTime,MenuTag) +if self.TaskControlMenu then +self.TaskControlMenu:Remove(MenuTime,MenuTag) +end +end +end +TASKINFO={ +ClassName="TASKINFO", +} +TASKINFO.Detail="" +function TASKINFO:New(Task) +local self=BASE:Inherit(self,BASE:New()) +self.Task=Task +self.VolatileInfo=SET_BASE:New() +self.PersistentInfo=SET_BASE:New() +self.Info=self.VolatileInfo +return self +end +function TASKINFO:AddInfo(Key,Data,Order,Detail,Keep,ShowKey,Type) +self.VolatileInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type}) +if Keep==true then +self.PersistentInfo:Add(Key,{Data=Data,Order=Order,Detail=Detail,ShowKey=ShowKey,Type=Type}) +end +return self +end +function TASKINFO:GetInfo(Key) +local Object=self:Get(Key) +return Object.Data,Object.Order,Object.Detail +end +function TASKINFO:GetData(Key) +local Object=self.Info:Get(Key) +return Object and Object.Data +end +function TASKINFO:AddText(Key,Text,Order,Detail,Keep) +self:AddInfo(Key,Text,Order,Detail,Keep) +return self +end +function TASKINFO:AddTaskName(Order,Detail,Keep) +self:AddInfo("TaskName",self.Task:GetName(),Order,Detail,Keep) +return self +end +function TASKINFO:AddCoordinate(Coordinate,Order,Detail,Keep,ShowKey,Name) +self:AddInfo(Name or"Coordinate",Coordinate,Order,Detail,Keep,ShowKey,"Coordinate") +return self +end +function TASKINFO:GetCoordinate(Name) +return self:GetData(Name or"Coordinate") +end +function TASKINFO:AddCoordinates(Coordinates,Order,Detail,Keep) +self:AddInfo("Coordinates",Coordinates,Order,Detail,Keep) +return self +end +function TASKINFO:AddThreat(ThreatText,ThreatLevel,Order,Detail,Keep) +self:AddInfo("Threat"," ["..string.rep("■",ThreatLevel)..string.rep("□",10-ThreatLevel).."]:"..ThreatText,Order,Detail,Keep) +return self +end +function TASKINFO:GetThreat() +self:GetInfo("Threat") +return self +end +function TASKINFO:AddTargetCount(TargetCount,Order,Detail,Keep) +self:AddInfo("Counting",string.format("%d",TargetCount),Order,Detail,Keep) +return self +end +function TASKINFO:AddTargets(TargetCount,TargetTypes,Order,Detail,Keep) +self:AddInfo("Targets",string.format("%d of %s",TargetCount,TargetTypes),Order,Detail,Keep) +return self +end +function TASKINFO:GetTargets() +self:GetInfo("Targets") +return self +end +function TASKINFO:AddQFEAtCoordinate(Coordinate,Order,Detail,Keep) +self:AddInfo("QFE",Coordinate,Order,Detail,Keep) +return self +end +function TASKINFO:AddTemperatureAtCoordinate(Coordinate,Order,Detail,Keep) +self:AddInfo("Temperature",Coordinate,Order,Detail,Keep) +return self +end +function TASKINFO:AddWindAtCoordinate(Coordinate,Order,Detail,Keep) +self:AddInfo("Wind",Coordinate,Order,Detail,Keep) +return self +end +function TASKINFO:AddCargo(Cargo,Order,Detail,Keep) +self:AddInfo("Cargo",Cargo,Order,Detail,Keep) +return self +end +function TASKINFO:AddCargoSet(SetCargo,Order,Detail,Keep) +local CargoReport=REPORT:New() +CargoReport:Add("") +SetCargo:ForEachCargo( +function(Cargo) +CargoReport:Add(string.format(' - %s (%s) %s - status %s ',Cargo:GetName(),Cargo:GetType(),Cargo:GetTransportationMethod(),Cargo:GetCurrentState())) +end +) +self:AddInfo("Cargo",CargoReport:Text(),Order,Detail,Keep) +return self +end +function TASKINFO:Report(Report,Detail,ReportGroup,Task) +local Line=0 +local LineReport=REPORT:New() +if not self.Task:IsStatePlanned()and not self.Task:IsStateAssigned()then +self.Info=self.PersistentInfo +end +for Key,Data in UTILS.spairs(self.Info.Set,function(t,a,b)return t[a].Order0 then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterHasSEAD() +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2G_DISPATCHER:EvaluateCAS(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local GroundUnitCount=DetectedSet:HasGroundUnits() +local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local RadarCount=DetectedSet:HasSEAD() +if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==true then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2G_DISPATCHER:EvaluateBAI(DetectedItem,FriendlyCoalition) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local GroundUnitCount=DetectedSet:HasGroundUnits() +local FriendliesNearBy=self.Detection:IsFriendliesNearBy(DetectedItem,Unit.Category.GROUND_UNIT) +local RadarCount=DetectedSet:HasSEAD() +if RadarCount==0 and GroundUnitCount>0 and FriendliesNearBy==false then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2G_DISPATCHER:RemoveTask(TaskIndex) +self.Mission:RemoveTask(self.Tasks[TaskIndex]) +self.Tasks[TaskIndex]=nil +end +function TASK_A2G_DISPATCHER:EvaluateRemoveTask(Mission,Task,TaskIndex,DetectedItemChanged) +if Task then +if(Task:IsStatePlanned()and DetectedItemChanged==true)or Task:IsStateCancelled()then +self:RemoveTask(TaskIndex) +end +end +return Task +end +function TASK_A2G_DISPATCHER:ProcessDetected(Detection) +self:F() +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local Mission=self.Mission +if Mission:IsIDLE()or Mission:IsENGAGED()then +local TaskReport=REPORT:New() +for TaskIndex,TaskData in pairs(self.Tasks)do +local Task=TaskData +if Task:IsStatePlanned()then +local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex) +if not DetectedItem then +local TaskText=Task:GetName() +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if self.FlashNewTask then +Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2G task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup) +end +end +Task=self:RemoveTask(TaskIndex) +end +end +end +for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do +local DetectedItem=DetectedItem +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local DetectedItemID=DetectedItem.ID +local TaskIndex=DetectedItem.Index +local DetectedItemChanged=DetectedItem.Changed +self:F({DetectedItemChanged=DetectedItemChanged,DetectedItemID=DetectedItemID,TaskIndex=TaskIndex}) +local Task=self.Tasks[TaskIndex] +if Task then +if Task:IsStateAssigned()then +if DetectedItemChanged==true then +local TargetsReport=REPORT:New() +local TargetSetUnit=self:EvaluateSEAD(DetectedItem) +if TargetSetUnit then +if Task:IsInstanceOf(TASK_A2G_SEAD)then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +TargetsReport:Add(Detection:GetChangeText(DetectedItem)) +else +Task:Cancel() +end +else +local TargetSetUnit=self:EvaluateCAS(DetectedItem) +if TargetSetUnit then +if Task:IsInstanceOf(TASK_A2G_CAS)then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +TargetsReport:Add(Detection:GetChangeText(DetectedItem)) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +local TargetSetUnit=self:EvaluateBAI(DetectedItem) +if TargetSetUnit then +if Task:IsInstanceOf(TASK_A2G_BAI)then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +TargetsReport:Add(Detection:GetChangeText(DetectedItem)) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +end +end +end +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +local TargetsText=TargetsReport:Text(", ") +if(Mission:IsGroupAssigned(TaskGroup))and TargetsText~=""and self.FlashNewTask then +Mission:GetCommandCenter():MessageToGroup(string.format("Task %s has change of targets:\n %s",Task:GetName(),TargetsText),TaskGroup) +end +end +end +end +end +if Task then +if Task:IsStatePlanned()then +if DetectedItemChanged==true then +if Task:IsInstanceOf(TASK_A2G_SEAD)then +local TargetSetUnit=self:EvaluateSEAD(DetectedItem) +if TargetSetUnit then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +if Task:IsInstanceOf(TASK_A2G_CAS)then +local TargetSetUnit=self:EvaluateCAS(DetectedItem) +if TargetSetUnit then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +if Task:IsInstanceOf(TASK_A2G_BAI)then +local TargetSetUnit=self:EvaluateBAI(DetectedItem) +if TargetSetUnit then +Task:SetTargetSetUnit(TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +else +Task:Cancel() +Task=self:RemoveTask(TaskIndex) +end +end +end +end +end +end +if not Task then +local TargetSetUnit=self:EvaluateSEAD(DetectedItem) +if TargetSetUnit then +Task=TASK_A2G_SEAD:New(Mission,self.SetGroup,string.format("SEAD.%03d",DetectedItemID),TargetSetUnit) +DetectedItem.DesignateMenuName=string.format("SEAD.%03d",DetectedItemID) +Task:SetDetection(Detection,DetectedItem) +end +if not Task then +local TargetSetUnit=self:EvaluateCAS(DetectedItem) +if TargetSetUnit then +Task=TASK_A2G_CAS:New(Mission,self.SetGroup,string.format("CAS.%03d",DetectedItemID),TargetSetUnit) +DetectedItem.DesignateMenuName=string.format("CAS.%03d",DetectedItemID) +Task:SetDetection(Detection,DetectedItem) +end +if not Task then +local TargetSetUnit=self:EvaluateBAI(DetectedItem,self.Mission:GetCommandCenter():GetPositionable():GetCoalition()) +if TargetSetUnit then +Task=TASK_A2G_BAI:New(Mission,self.SetGroup,string.format("BAI.%03d",DetectedItemID),TargetSetUnit) +DetectedItem.DesignateMenuName=string.format("BAI.%03d",DetectedItemID) +Task:SetDetection(Detection,DetectedItem) +end +end +end +if Task then +self.Tasks[TaskIndex]=Task +Task:SetTargetZone(DetectedZone) +Task:SetDispatcher(self) +Task:UpdateTaskInfo(DetectedItem) +Mission:AddTask(Task) +function Task.OnEnterSuccess(Task,From,Event,To) +self:Success(Task) +end +function Task.OnEnterCancelled(Task,From,Event,To) +self:Cancelled(Task) +end +function Task.OnEnterFailed(Task,From,Event,To) +self:Failed(Task) +end +function Task.OnEnterAborted(Task,From,Event,To) +self:Aborted(Task) +end +TaskReport:Add(Task:GetName()) +else +self:F("This should not happen") +end +end +Detection:AcceptChanges(DetectedItem) +end +Mission:GetCommandCenter():SetMenu() +local TaskText=TaskReport:Text(", ") +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and self.FlashNewTask then +Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup) +end +end +end +return true +end +end +do +TASK_A2G={ +ClassName="TASK_A2G", +} +function TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskType,TaskBriefing) +local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing)) +self:F() +self.TargetSetUnit=TargetSetUnit +self.TaskType=TaskType +local Fsm=self:GetUnitProcess() +Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous") +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous") +Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{}) +Fsm:AddTransition("Engaging","RouteToTarget","Engaging") +Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{}) +Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{}) +Fsm:AddTransition("Engaging","RouteToTargets","Engaging") +Fsm:AddTransition("Rejected","Reject","Aborted") +Fsm:AddTransition("Failed","Fail","Failed") +function Fsm:onafterAssigned(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:RouteToRendezVous() +end +function Fsm:onafterRouteToRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetRendezVousZone(TaskUnit)then +self:__RouteToRendezVousZone(0.1) +else +if Task:GetRendezVousCoordinate(TaskUnit)then +self:__RouteToRendezVousPoint(0.1) +else +self:__ArriveAtRendezVous(0.1) +end +end +end +function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:__Engage(0.1) +end +function Fsm:onafterEngage(TaskUnit,Task) +self:F({self}) +self:__Account(0.1) +self:__RouteToTarget(0.1) +self:__RouteToTargets(-10) +end +function Fsm:onafterRouteToTarget(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetTargetZone(TaskUnit)then +self:__RouteToTargetZone(0.1) +else +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +local Coordinate=TargetUnit:GetPointVec3() +self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetY(),Coordinate:GetZ()}) +Task:SetTargetCoordinate(Coordinate,TaskUnit) +end +self:__RouteToTargetPoint(0.1) +end +end +function Fsm:onafterRouteToTargets(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit) +end +self:__RouteToTargets(-10) +end +return self +end +function TASK_A2G:SetTargetSetUnit(TargetSetUnit) +self.TargetSetUnit=TargetSetUnit +end +function TASK_A2G:GetPlannedMenuText() +return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" +end +function TASK_A2G:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +ActRouteRendezVous:SetCoordinate(RendezVousCoordinate) +ActRouteRendezVous:SetRange(RendezVousRange) +end +function TASK_A2G:GetRendezVousCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange() +end +function TASK_A2G:SetRendezVousZone(RendezVousZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +ActRouteRendezVous:SetZone(RendezVousZone) +end +function TASK_A2G:GetRendezVousZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +return ActRouteRendezVous:GetZone() +end +function TASK_A2G:SetTargetCoordinate(TargetCoordinate,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +ActRouteTarget:SetCoordinate(TargetCoordinate) +end +function TASK_A2G:GetTargetCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +return ActRouteTarget:GetCoordinate() +end +function TASK_A2G:SetTargetZone(TargetZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +ActRouteTarget:SetZone(TargetZone) +end +function TASK_A2G:GetTargetZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +return ActRouteTarget:GetZone() +end +function TASK_A2G:SetGoalTotal() +self.GoalTotal=self.TargetSetUnit:Count() +end +function TASK_A2G:GetGoalTotal() +return self.GoalTotal +end +function TASK_A2G:ReportOrder(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate) +return Distance +end +function TASK_A2G:onafterGoal(TaskUnit,From,Event,To) +local TargetSetUnit=self.TargetSetUnit +if TargetSetUnit:Count()==0 then +self:Success() +end +self:__Goal(-10) +end +function TASK_A2G:UpdateTaskInfo(DetectedItem) +if self:IsStatePlanned()or self:IsStateAssigned()then +local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate() +self.TaskInfo:AddTaskName(0,"MSOD") +self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD") +local ThreatLevel,ThreatText +if DetectedItem then +ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem) +else +ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G() +end +self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true) +if self.Detection then +local DetectedItemsCount=self.TargetSetUnit:Count() +local ReportTypes=REPORT:New() +local TargetTypes={} +for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do +local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit) +if not TargetTypes[TargetType]then +TargetTypes[TargetType]=TargetType +ReportTypes:Add(TargetType) +end +end +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true) +else +local DetectedItemsCount=self.TargetSetUnit:Count() +local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames() +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true) +end +self.TaskInfo:AddQFEAtCoordinate(TargetCoordinate,30,"MOD") +self.TaskInfo:AddTemperatureAtCoordinate(TargetCoordinate,31,"MD") +self.TaskInfo:AddWindAtCoordinate(TargetCoordinate,32,"MD") +end +end +function TASK_A2G:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup) +if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then +return math.random(1,9) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate()) +self:F({Distance=Distance}) +return math.floor(Distance) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then +return 1 +end +return 0 +end +end +do +TASK_A2G_SEAD={ +ClassName="TASK_A2G_SEAD", +} +function TASK_A2G_SEAD:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"SEAD",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing( +TaskBriefing or +"Execute a Suppression of Enemy Air Defenses." +) +return self +end +function TASK_A2G_SEAD:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has SEADed a target.",Score) +return self +end +function TASK_A2G_SEAD:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All radar emitting targets have been successfully SEADed!",Score) +return self +end +function TASK_A2G_SEAD:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The SEADing has failed!",Penalty) +return self +end +end +do +TASK_A2G_BAI={ +ClassName="TASK_A2G_BAI", +} +function TASK_A2G_BAI:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"BAI",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing( +TaskBriefing or +"Execute a Battlefield Air Interdiction of a group of enemy targets." +) +return self +end +function TASK_A2G_BAI:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Battlefield Air Interdiction (BAI).",Score) +return self +end +function TASK_A2G_BAI:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!",Score) +return self +end +function TASK_A2G_BAI:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The Battlefield Air Interdiction (BAI) has failed!",Penalty) +return self +end +end +do +TASK_A2G_CAS={ +ClassName="TASK_A2G_CAS", +} +function TASK_A2G_CAS:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2G:New(Mission,SetGroup,TaskName,TargetSetUnit,"CAS",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing( +TaskBriefing or +"Execute a Close Air Support for a group of enemy targets. ".. +"Beware of friendlies at the vicinity! " +) +return self +end +function TASK_A2G_CAS:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has destroyed a target in Close Air Support (CAS).",Score) +return self +end +function TASK_A2G_CAS:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully destroyed! The Close Air Support (CAS) was a success!",Score) +return self +end +function TASK_A2G_CAS:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The Close Air Support (CAS) has failed!",Penalty) +return self +end +end +do +TASK_A2A_DISPATCHER={ +ClassName="TASK_A2A_DISPATCHER", +Mission=nil, +Detection=nil, +Tasks={}, +SweepZones={}, +} +function TASK_A2A_DISPATCHER:New(Mission,SetGroup,Detection) +local self=BASE:Inherit(self,DETECTION_MANAGER:New(SetGroup,Detection)) +self.Detection=Detection +self.Mission=Mission +self.FlashNewTask=false +self.Detection:FilterCategories(Unit.Category.AIRPLANE,Unit.Category.HELICOPTER) +self.Detection:InitDetectRadar(true) +self.Detection:SetRefreshTimeInterval(30) +self:AddTransition("Started","Assign","Started") +self:__Start(5) +return self +end +function TASK_A2A_DISPATCHER:SetEngageRadius(EngageRadius) +self.Detection:SetFriendliesRange(EngageRadius or 100000) +return self +end +function TASK_A2A_DISPATCHER:SetSendMessages(onoff) +self.FlashNewTask=onoff +end +function TASK_A2A_DISPATCHER:EvaluateINTERCEPT(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +if DetectedItem.IsDetected==true then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2A_DISPATCHER:EvaluateSWEEP(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +if DetectedItem.IsDetected==false then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2A_DISPATCHER:EvaluateENGAGE(DetectedItem) +self:F({DetectedItem.ItemID}) +local DetectedSet=DetectedItem.Set +local DetectedZone=DetectedItem.Zone +local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem) +if PlayersCount>0 and DetectedItem.IsDetected==true then +local TargetSetUnit=SET_UNIT:New() +TargetSetUnit:SetDatabase(DetectedSet) +TargetSetUnit:FilterOnce() +return TargetSetUnit +end +return nil +end +function TASK_A2A_DISPATCHER:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,DetectedItemIndex,DetectedItemChanged) +if Task then +if Task:IsStatePlanned()then +local TaskName=Task:GetName() +local TaskType=TaskName:match("(%u+)%.%d+") +self:T2({TaskType=TaskType}) +local Remove=false +local IsPlayers=Detection:IsPlayersNearBy(DetectedItem) +if TaskType=="ENGAGE"then +if IsPlayers==false then +Remove=true +end +end +if TaskType=="INTERCEPT"then +if IsPlayers==true then +Remove=true +end +if DetectedItem.IsDetected==false then +Remove=true +end +end +if TaskType=="SWEEP"then +if DetectedItem.IsDetected==true then +Remove=true +end +end +local DetectedSet=DetectedItem.Set +if DetectedSet:Count()==0 then +Remove=true +end +if DetectedItemChanged==true or Remove then +Task=self:RemoveTask(DetectedItemIndex) +end +end +end +return Task +end +function TASK_A2A_DISPATCHER:GetFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local FriendlyUnitsNearBy=self.Detection:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE) +local FriendlyTypes={} +local FriendliesCount=0 +if FriendlyUnitsNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for FriendlyUnitName,FriendlyUnitData in pairs(FriendlyUnitsNearBy)do +local FriendlyUnit=FriendlyUnitData +if FriendlyUnit:IsAirPlane()then +local FriendlyUnitThreatLevel=FriendlyUnit:GetThreatLevel() +FriendliesCount=FriendliesCount+1 +local FriendlyType=FriendlyUnit:GetTypeName() +FriendlyTypes[FriendlyType]=FriendlyTypes[FriendlyType]and(FriendlyTypes[FriendlyType]+1)or 1 +if DetectedTreatLevel0 then +for FriendlyType,FriendlyTypeCount in pairs(FriendlyTypes)do +FriendlyTypesReport:Add(string.format("%d of %s",FriendlyTypeCount,FriendlyType)) +end +else +FriendlyTypesReport:Add("-") +end +return FriendliesCount,FriendlyTypesReport +end +function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy(DetectedItem) +local DetectedSet=DetectedItem.Set +local PlayersNearBy=self.Detection:GetPlayersNearBy(DetectedItem) +local PlayerTypes={} +local PlayersCount=0 +if PlayersNearBy then +local DetectedTreatLevel=DetectedSet:CalculateThreatLevelA2G() +for PlayerUnitName,PlayerUnitData in pairs(PlayersNearBy)do +local PlayerUnit=PlayerUnitData +local PlayerName=PlayerUnit:GetPlayerName() +if PlayerUnit:IsAirPlane()and PlayerName~=nil then +local FriendlyUnitThreatLevel=PlayerUnit:GetThreatLevel() +PlayersCount=PlayersCount+1 +local PlayerType=PlayerUnit:GetTypeName() +PlayerTypes[PlayerName]=PlayerType +if DetectedTreatLevel0 then +for PlayerName,PlayerType in pairs(PlayerTypes)do +PlayerTypesReport:Add(string.format('"%s" in %s',PlayerName,PlayerType)) +end +else +PlayerTypesReport:Add("-") +end +return PlayersCount,PlayerTypesReport +end +function TASK_A2A_DISPATCHER:RemoveTask(TaskIndex) +self.Mission:RemoveTask(self.Tasks[TaskIndex]) +self.Tasks[TaskIndex]=nil +end +function TASK_A2A_DISPATCHER:ProcessDetected(Detection) +self:F() +local AreaMsg={} +local TaskMsg={} +local ChangeMsg={} +local Mission=self.Mission +if Mission:IsIDLE()or Mission:IsENGAGED()then +local TaskReport=REPORT:New() +for TaskIndex,TaskData in pairs(self.Tasks)do +local Task=TaskData +if Task:IsStatePlanned()then +local DetectedItem=Detection:GetDetectedItemByIndex(TaskIndex) +if not DetectedItem then +local TaskText=Task:GetName() +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +Mission:GetCommandCenter():MessageToGroup(string.format("Obsolete A2A task %s for %s removed.",TaskText,Mission:GetShortText()),TaskGroup) +end +Task=self:RemoveTask(TaskIndex) +end +end +end +for DetectedItemID,DetectedItem in pairs(Detection:GetDetectedItems())do +local DetectedItem=DetectedItem +local DetectedSet=DetectedItem.Set +local DetectedCount=DetectedSet:Count() +local DetectedZone=DetectedItem.Zone +local DetectedID=DetectedItem.ID +local TaskIndex=DetectedItem.Index +local DetectedItemChanged=DetectedItem.Changed +local Task=self.Tasks[TaskIndex] +Task=self:EvaluateRemoveTask(Mission,Task,Detection,DetectedItem,TaskIndex,DetectedItemChanged) +if not Task and DetectedCount>0 then +local TargetSetUnit=self:EvaluateENGAGE(DetectedItem) +if TargetSetUnit then +Task=TASK_A2A_ENGAGE:New(Mission,self.SetGroup,string.format("ENGAGE.%03d",DetectedID),TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +local TargetSetUnit=self:EvaluateINTERCEPT(DetectedItem) +if TargetSetUnit then +Task=TASK_A2A_INTERCEPT:New(Mission,self.SetGroup,string.format("INTERCEPT.%03d",DetectedID),TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +else +local TargetSetUnit=self:EvaluateSWEEP(DetectedItem) +if TargetSetUnit then +Task=TASK_A2A_SWEEP:New(Mission,self.SetGroup,string.format("SWEEP.%03d",DetectedID),TargetSetUnit) +Task:SetDetection(Detection,DetectedItem) +Task:UpdateTaskInfo(DetectedItem) +end +end +end +if Task then +self.Tasks[TaskIndex]=Task +Task:SetTargetZone(DetectedZone,DetectedItem.Coordinate.y,DetectedItem.Coordinate.Heading) +Task:SetDispatcher(self) +Mission:AddTask(Task) +function Task.OnEnterSuccess(Task,From,Event,To) +self:Success(Task) +end +function Task.OnEnterCancelled(Task,From,Event,To) +self:Cancelled(Task) +end +function Task.OnEnterFailed(Task,From,Event,To) +self:Failed(Task) +end +function Task.OnEnterAborted(Task,From,Event,To) +self:Aborted(Task) +end +TaskReport:Add(Task:GetName()) +else +self:F("This should not happen") +end +end +if Task then +local FriendliesCount,FriendliesReport=self:GetFriendliesNearBy(DetectedItem,Unit.Category.AIRPLANE) +Task.TaskInfo:AddText("Friendlies",string.format("%d ( %s )",FriendliesCount,FriendliesReport:Text(",")),40,"MOD") +local PlayersCount,PlayersReport=self:GetPlayerFriendliesNearBy(DetectedItem) +Task.TaskInfo:AddText("Players",string.format("%d ( %s )",PlayersCount,PlayersReport:Text(",")),40,"MOD") +end +Detection:AcceptChanges(DetectedItem) +end +Mission:GetCommandCenter():SetMenu() +local TaskText=TaskReport:Text(", ") +for TaskGroupID,TaskGroup in pairs(self.SetGroup:GetSet())do +if(not Mission:IsGroupAssigned(TaskGroup))and TaskText~=""and(self.FlashNewTask)then +Mission:GetCommandCenter():MessageToGroup(string.format("%s has tasks %s. Subscribe to a task using the radio menu.",Mission:GetShortText(),TaskText),TaskGroup) +end +end +end +return true +end +end +do +TASK_A2A={ +ClassName="TASK_A2A", +} +function TASK_A2A:New(Mission,SetAttack,TaskName,TargetSetUnit,TaskType,TaskBriefing) +local self=BASE:Inherit(self,TASK:New(Mission,SetAttack,TaskName,TaskType,TaskBriefing)) +self:F() +self.TargetSetUnit=TargetSetUnit +self.TaskType=TaskType +local Fsm=self:GetUnitProcess() +Fsm:AddTransition("Assigned","RouteToRendezVous","RoutingToRendezVous") +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddProcess("RoutingToRendezVous","RouteToRendezVousZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtRendezVous"}) +Fsm:AddTransition({"Arrived","RoutingToRendezVous"},"ArriveAtRendezVous","ArrivedAtRendezVous") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"Engage","Engaging") +Fsm:AddTransition({"ArrivedAtRendezVous","HoldingAtRendezVous"},"HoldAtRendezVous","HoldingAtRendezVous") +Fsm:AddProcess("Engaging","Account",ACT_ACCOUNT_DEADS:New(),{}) +Fsm:AddTransition("Engaging","RouteToTarget","Engaging") +Fsm:AddProcess("Engaging","RouteToTargetZone",ACT_ROUTE_ZONE:New(),{}) +Fsm:AddProcess("Engaging","RouteToTargetPoint",ACT_ROUTE_POINT:New(),{}) +Fsm:AddTransition("Engaging","RouteToTargets","Engaging") +Fsm:AddTransition("Rejected","Reject","Aborted") +Fsm:AddTransition("Failed","Fail","Failed") +function Fsm:OnLeaveAssigned(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:SelectAction() +end +function Fsm:onafterRouteToRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetRendezVousZone(TaskUnit)then +self:__RouteToRendezVousZone(0.1) +else +if Task:GetRendezVousCoordinate(TaskUnit)then +self:__RouteToRendezVousPoint(0.1) +else +self:__ArriveAtRendezVous(0.1) +end +end +end +function Fsm:OnAfterArriveAtRendezVous(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:__Engage(0.1) +end +function Fsm:onafterEngage(TaskUnit,Task) +self:F({self}) +self:__Account(0.1) +self:__RouteToTarget(0.1) +self:__RouteToTargets(-10) +end +function Fsm:onafterRouteToTarget(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Task:GetTargetZone(TaskUnit)then +self:__RouteToTargetZone(0.1) +else +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +local Coordinate=TargetUnit:GetPointVec3() +self:T({TargetCoordinate=Coordinate,Coordinate:GetX(),Coordinate:GetAlt(),Coordinate:GetZ()}) +Task:SetTargetCoordinate(Coordinate,TaskUnit) +end +self:__RouteToTargetPoint(0.1) +end +end +function Fsm:onafterRouteToTargets(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +local TargetUnit=Task.TargetSetUnit:GetFirst() +if TargetUnit then +Task:SetTargetCoordinate(TargetUnit:GetCoordinate(),TaskUnit) +end +self:__RouteToTargets(-10) +end +return self +end +function TASK_A2A:SetTargetSetUnit(TargetSetUnit) +self.TargetSetUnit=TargetSetUnit +end +function TASK_A2A:GetPlannedMenuText() +return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" +end +function TASK_A2A:SetRendezVousCoordinate(RendezVousCoordinate,RendezVousRange,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +ActRouteRendezVous:SetCoordinate(RendezVousCoordinate) +ActRouteRendezVous:SetRange(RendezVousRange) +end +function TASK_A2A:GetRendezVousCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousPoint") +return ActRouteRendezVous:GetCoordinate(),ActRouteRendezVous:GetRange() +end +function TASK_A2A:SetRendezVousZone(RendezVousZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +ActRouteRendezVous:SetZone(RendezVousZone) +end +function TASK_A2A:GetRendezVousZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteRendezVous=ProcessUnit:GetProcess("RoutingToRendezVous","RouteToRendezVousZone") +return ActRouteRendezVous:GetZone() +end +function TASK_A2A:SetTargetCoordinate(TargetCoordinate,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +ActRouteTarget:SetCoordinate(TargetCoordinate) +end +function TASK_A2A:GetTargetCoordinate(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetPoint") +return ActRouteTarget:GetCoordinate() +end +function TASK_A2A:SetTargetZone(TargetZone,Altitude,Heading,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +ActRouteTarget:SetZone(TargetZone,Altitude,Heading) +end +function TASK_A2A:GetTargetZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +return ActRouteTarget:GetZone() +end +function TASK_A2A:SetGoalTotal() +self.GoalTotal=self.TargetSetUnit:Count() +end +function TASK_A2A:GetGoalTotal() +return self.GoalTotal +end +function TASK_A2A:ReportOrder(ReportGroup) +self:UpdateTaskInfo(self.DetectedItem) +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=ReportGroup:GetCoordinate():Get2DDistance(Coordinate) +return Distance +end +function TASK_A2A:onafterGoal(TaskUnit,From,Event,To) +local TargetSetUnit=self.TargetSetUnit +if TargetSetUnit:Count()==0 then +self:Success() +end +self:__Goal(-10) +end +function TASK_A2A:UpdateTaskInfo(DetectedItem) +if self:IsStatePlanned()or self:IsStateAssigned()then +local TargetCoordinate=DetectedItem and self.Detection:GetDetectedItemCoordinate(DetectedItem)or self.TargetSetUnit:GetFirst():GetCoordinate() +self.TaskInfo:AddTaskName(0,"MSOD") +self.TaskInfo:AddCoordinate(TargetCoordinate,1,"SOD") +local ThreatLevel,ThreatText +if DetectedItem then +ThreatLevel,ThreatText=self.Detection:GetDetectedItemThreatLevel(DetectedItem) +else +ThreatLevel,ThreatText=self.TargetSetUnit:CalculateThreatLevelA2G() +end +self.TaskInfo:AddThreat(ThreatText,ThreatLevel,10,"MOD",true) +if self.Detection then +local DetectedItemsCount=self.TargetSetUnit:Count() +local ReportTypes=REPORT:New() +local TargetTypes={} +for TargetUnitName,TargetUnit in pairs(self.TargetSetUnit:GetSet())do +local TargetType=self.Detection:GetDetectedUnitTypeName(TargetUnit) +if not TargetTypes[TargetType]then +TargetTypes[TargetType]=TargetType +ReportTypes:Add(TargetType) +end +end +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,ReportTypes:Text(", "),20,"D",true) +else +local DetectedItemsCount=self.TargetSetUnit:Count() +local DetectedItemsTypes=self.TargetSetUnit:GetTypeNames() +self.TaskInfo:AddTargetCount(DetectedItemsCount,11,"O",true) +self.TaskInfo:AddTargets(DetectedItemsCount,DetectedItemsTypes,20,"D",true) +end +end +end +function TASK_A2A:GetAutoAssignPriority(AutoAssignMethod,CommandCenter,TaskGroup) +if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then +return math.random(1,9) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then +local Coordinate=self.TaskInfo:GetData("Coordinate") +local Distance=Coordinate:Get2DDistance(CommandCenter:GetPositionable():GetCoordinate()) +return math.floor(Distance) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then +return 1 +end +return 0 +end +end +do +TASK_A2A_INTERCEPT={ +ClassName="TASK_A2A_INTERCEPT", +} +function TASK_A2A_INTERCEPT:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"INTERCEPT",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing( +TaskBriefing or +"Intercept incoming intruders.\n" +) +return self +end +function TASK_A2A_INTERCEPT:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has intercepted a target.",Score) +return self +end +function TASK_A2A_INTERCEPT:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully intercepted!",Score) +return self +end +function TASK_A2A_INTERCEPT:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The intercept has failed!",Penalty) +return self +end +end +do +TASK_A2A_SWEEP={ +ClassName="TASK_A2A_SWEEP", +} +function TASK_A2A_SWEEP:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"SWEEP",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing( +TaskBriefing or +"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" +) +return self +end +function TASK_A2A_SWEEP:onafterGoal(TaskUnit,From,Event,To) +local TargetSetUnit=self.TargetSetUnit +if TargetSetUnit:Count()==0 then +self:Success() +end +self:__Goal(-10) +end +function TASK_A2A_SWEEP:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has sweeped a target.",Score) +return self +end +function TASK_A2A_SWEEP:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully sweeped!",Score) +return self +end +function TASK_A2A_SWEEP:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The sweep has failed!",Penalty) +return self +end +end +do +TASK_A2A_ENGAGE={ +ClassName="TASK_A2A_ENGAGE", +} +function TASK_A2A_ENGAGE:New(Mission,SetGroup,TaskName,TargetSetUnit,TaskBriefing) +local self=BASE:Inherit(self,TASK_A2A:New(Mission,SetGroup,TaskName,TargetSetUnit,"ENGAGE",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:SetBriefing( +TaskBriefing or +"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" +) +return self +end +function TASK_A2A_ENGAGE:SetScoreOnProgress(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","AccountForPlayer","Player "..PlayerName.." has engaged and destroyed a target.",Score) +return self +end +function TASK_A2A_ENGAGE:SetScoreOnSuccess(PlayerName,Score,TaskUnit) +self:F({PlayerName,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success","All targets have been successfully engaged!",Score) +return self +end +function TASK_A2A_ENGAGE:SetScoreOnFail(PlayerName,Penalty,TaskUnit) +self:F({PlayerName,Penalty,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed","The target engagement has failed!",Penalty) +return self +end +end +do +TASK_CARGO={ +ClassName="TASK_CARGO", +} +function TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,TaskType,TaskBriefing) +local self=BASE:Inherit(self,TASK:New(Mission,SetGroup,TaskName,TaskType,TaskBriefing)) +self:F({Mission,SetGroup,TaskName,SetCargo,TaskType}) +self.SetCargo=SetCargo +self.TaskType=TaskType +self.SmokeColor=SMOKECOLOR.Red +self.CargoItemCount={} +self.CargoLimit=10 +self.DeployZones={} +self:AddTransition("*","CargoDeployed","*") +self:AddTransition("*","CargoPickedUp","*") +local Fsm=self:GetUnitProcess() +Fsm:AddTransition({"Planned","Assigned","Cancelled","WaitingForCommand","ArrivedAtPickup","ArrivedAtDeploy","Boarded","UnBoarded","Loaded","UnLoaded","Landed","Boarding"},"SelectAction","*") +Fsm:AddTransition("*","RouteToPickup","RoutingToPickup") +Fsm:AddProcess("RoutingToPickup","RouteToPickupPoint",ACT_ROUTE_POINT:New(),{Arrived="ArriveAtPickup",Cancelled="CancelRouteToPickup"}) +Fsm:AddTransition("Arrived","ArriveAtPickup","ArrivedAtPickup") +Fsm:AddTransition("Cancelled","CancelRouteToPickup","Cancelled") +Fsm:AddTransition("*","RouteToDeploy","RoutingToDeploy") +Fsm:AddProcess("RoutingToDeploy","RouteToDeployZone",ACT_ROUTE_ZONE:New(),{Arrived="ArriveAtDeploy",Cancelled="CancelRouteToDeploy"}) +Fsm:AddTransition("Arrived","ArriveAtDeploy","ArrivedAtDeploy") +Fsm:AddTransition("Cancelled","CancelRouteToDeploy","Cancelled") +Fsm:AddTransition({"ArrivedAtPickup","ArrivedAtDeploy","Landing"},"Land","Landing") +Fsm:AddTransition("Landing","Landed","Landed") +Fsm:AddTransition("*","PrepareBoarding","AwaitBoarding") +Fsm:AddTransition("AwaitBoarding","Board","Boarding") +Fsm:AddTransition("Boarding","Boarded","Boarded") +Fsm:AddTransition("*","Load","Loaded") +Fsm:AddTransition("*","PrepareUnBoarding","AwaitUnBoarding") +Fsm:AddTransition("AwaitUnBoarding","UnBoard","UnBoarding") +Fsm:AddTransition("UnBoarding","UnBoarded","UnBoarded") +Fsm:AddTransition("*","Unload","Unloaded") +Fsm:AddTransition("*","Planned","Planned") +Fsm:AddTransition("Deployed","Success","Success") +Fsm:AddTransition("Rejected","Reject","Aborted") +Fsm:AddTransition("Failed","Fail","Failed") +function Fsm:OnAfterAssigned(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:SelectAction() +end +function Fsm:onafterSelectAction(TaskUnit,Task) +local TaskUnitName=TaskUnit:GetName() +local MenuTime=Task:InitTaskControlMenu(TaskUnit) +local MenuControl=Task:GetTaskControlMenu(TaskUnit) +Task.SetCargo:ForEachCargo( +function(Cargo) +if Cargo:IsAlive()then +local TaskGroup=TaskUnit:GetGroup() +if Cargo:IsUnLoaded()then +local CargoBayFreeWeight=TaskUnit:GetCargoBayFreeWeight() +local CargoWeight=Cargo:GetWeight() +self:F({CargoBayFreeWeight=CargoBayFreeWeight}) +if CargoBayFreeWeight>CargoWeight then +if Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then +local NotInDeployZones=true +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if Cargo:IsInZone(DeployZone)then +NotInDeployZones=false +end +end +if NotInDeployZones then +if not TaskUnit:InAir()then +if Cargo:CanBoard()==true then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +Cargo:Report("Ready for boarding.","board",TaskUnit:GetGroup()) +local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuBoardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +Cargo:Report("Board at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup().."."),"reporting",TaskUnit:GetGroup()) +end +else +if Cargo:CanLoad()==true then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +Cargo:Report("Ready for loading.","load",TaskUnit:GetGroup()) +local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +Cargo:Report("Load at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup()).." within "..Cargo.NearRadius..".","reporting",TaskUnit:GetGroup()) +end +else +if Cargo:CanSlingload()==true then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +Cargo:Report("Ready for sling loading.","slingload",TaskUnit:GetGroup()) +local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuLoadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +Cargo:Report("Slingload at "..Cargo:GetCoordinate():ToString(TaskUnit:GetGroup())..".","reporting",TaskUnit:GetGroup()) +end +end +end +end +else +Cargo:ReportResetAll(TaskUnit:GetGroup()) +end +end +else +if not Cargo:IsDeployed()==true then +local RouteToPickupMenu=MENU_GROUP:New(TaskGroup,"Route to pickup cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +Cargo:ReportResetAll(TaskUnit:GetGroup()) +if Cargo:CanBoard()==true then +if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +local BoardMenu=MENU_GROUP:New(TaskGroup,"Board cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,BoardMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +else +if Cargo:CanLoad()==true then +if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +local LoadMenu=MENU_GROUP:New(TaskGroup,"Load cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,LoadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +else +if Cargo:CanSlingload()==true then +if not Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +local SlingloadMenu=MENU_GROUP:New(TaskGroup,"Slingload cargo",RouteToPickupMenu):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,SlingloadMenu,self.MenuRouteToPickup,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +end +end +end +end +end +end +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if Cargo:IsInZone(DeployZone)then +Task:I({CargoIsDeployed=Task.CargoDeployed and"true"or"false"}) +if Cargo:IsDeployed()==false then +Cargo:SetDeployed(true) +Task:I({CargoIsAlive=Cargo:IsAlive()and"true"or"false"}) +if Cargo:IsAlive()then +Task:CargoDeployed(TaskUnit,Cargo,DeployZone) +end +end +end +end +end +if Cargo:IsLoaded()==true and Cargo:IsLoadedInCarrier(TaskUnit)==true then +if not TaskUnit:InAir()then +if Cargo:CanUnboard()==true then +local UnboardMenu=MENU_GROUP:New(TaskGroup,"Unboard cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnboardMenu,self.MenuUnboardCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +else +if Cargo:CanUnload()==true then +local UnloadMenu=MENU_GROUP:New(TaskGroup,"Unload cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),Cargo.Name,UnloadMenu,self.MenuUnloadCargo,self,Cargo):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +end +end +end +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if not Cargo:IsInZone(DeployZone)then +local RouteToDeployMenu=MENU_GROUP:New(TaskGroup,"Route to deploy cargo",MenuControl):SetTime(MenuTime):SetTag("Cargo") +MENU_GROUP_COMMAND:New(TaskUnit:GetGroup(),"Zone "..DeployZoneName,RouteToDeployMenu,self.MenuRouteToDeploy,self,DeployZone):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() +end +end +end +end +) +Task:RefreshTaskControlMenu(TaskUnit,MenuTime,"Cargo") +self:__SelectAction(-1) +end +function Fsm:OnLeaveWaitingForCommand(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +end +function Fsm:MenuBoardCargo(Cargo) +self:__PrepareBoarding(1.0,Cargo) +end +function Fsm:MenuLoadCargo(Cargo) +self:__Load(1.0,Cargo) +end +function Fsm:MenuUnboardCargo(Cargo,DeployZone) +self:__PrepareUnBoarding(1.0,Cargo,DeployZone) +end +function Fsm:MenuUnloadCargo(Cargo,DeployZone) +self:__Unload(1.0,Cargo,DeployZone) +end +function Fsm:MenuRouteToPickup(Cargo) +self:__RouteToPickup(1.0,Cargo) +end +function Fsm:MenuRouteToDeploy(DeployZone) +self:__RouteToDeploy(1.0,DeployZone) +end +function Fsm:onafterRouteToPickup(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Cargo:IsAlive()then +self.Cargo=Cargo +Task:SetCargoPickup(self.Cargo,TaskUnit) +self:__RouteToPickupPoint(-0.1) +end +end +function Fsm:onafterArriveAtPickup(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if self.Cargo:IsAlive()then +if TaskUnit:IsAir()then +Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup()) +self:__Land(-0.1,"Pickup") +else +self:__SelectAction(-0.1) +end +end +end +function Fsm:onafterCancelRouteToPickup(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to Cargo "..self.Cargo:GetName(),TaskUnit:GetGroup()) +self:__SelectAction(-0.1) +end +function Fsm:onafterRouteToDeploy(TaskUnit,Task,From,Event,To,DeployZone) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +self:F(DeployZone) +self.DeployZone=DeployZone +Task:SetDeployZone(self.DeployZone,TaskUnit) +self:__RouteToDeployZone(-0.1) +end +function Fsm:onafterArriveAtDeploy(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if TaskUnit:IsAir()then +Task:GetMission():GetCommandCenter():MessageToGroup("Land",TaskUnit:GetGroup()) +self:__Land(-0.1,"Deploy") +else +self:__SelectAction(-0.1) +end +end +function Fsm:onafterCancelRouteToDeploy(TaskUnit,Task) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +Task:GetMission():GetCommandCenter():MessageToGroup("Cancelled routing to deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup()) +self:__SelectAction(-0.1) +end +function Fsm:onafterLand(TaskUnit,Task,From,Event,To,Action) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Action=="Pickup"then +if self.Cargo:IsAlive()then +if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then +if TaskUnit:InAir()then +self:__Land(-10,Action) +else +Task:GetMission():GetCommandCenter():MessageToGroup("Landed at pickup location...",TaskUnit:GetGroup()) +self:__Landed(-0.1,Action) +end +else +self:__RouteToPickup(-0.1,self.Cargo) +end +end +else +if TaskUnit:IsAlive()then +if TaskUnit:IsInZone(self.DeployZone)then +if TaskUnit:InAir()then +self:__Land(-10,Action) +else +Task:GetMission():GetCommandCenter():MessageToGroup("Landed at deploy zone "..self.DeployZone:GetName(),TaskUnit:GetGroup()) +self:__Landed(-0.1,Action) +end +else +self:__RouteToDeploy(-0.1,self.Cargo) +end +end +end +end +function Fsm:onafterLanded(TaskUnit,Task,From,Event,To,Action) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Action=="Pickup"then +if self.Cargo:IsAlive()then +if self.Cargo:IsInReportRadius(TaskUnit:GetPointVec2())then +if TaskUnit:InAir()then +self:__Land(-0.1,Action) +else +self:__SelectAction(-0.1) +end +else +self:__RouteToPickup(-0.1,self.Cargo) +end +end +else +if TaskUnit:IsAlive()then +if TaskUnit:IsInZone(self.DeployZone)then +if TaskUnit:InAir()then +self:__Land(-10,Action) +else +self:__SelectAction(-0.1) +end +else +self:__RouteToDeploy(-0.1,self.Cargo) +end +end +end +end +function Fsm:onafterPrepareBoarding(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +if Cargo and Cargo:IsAlive()then +self:__Board(-0.1,Cargo) +end +end +function Fsm:onafterBoard(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID()}) +function Cargo:OnEnterLoaded(From,Event,To,TaskUnit,TaskProcess) +self:F({From,Event,To,TaskUnit,TaskProcess}) +TaskProcess:__Boarded(0.1,self) +end +if Cargo:IsAlive()then +if Cargo:IsInLoadRadius(TaskUnit:GetPointVec2())then +if TaskUnit:InAir()then +else +Cargo:MessageToGroup("Boarding ...",TaskUnit:GetGroup()) +if not Cargo:IsBoarding()then +Cargo:Board(TaskUnit,nil,self) +end +end +else +end +end +end +function Fsm:onafterBoarded(TaskUnit,Task,From,Event,To,Cargo) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +Cargo:MessageToGroup("Boarded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) +self:__Load(-0.1,Cargo) +end +function Fsm:onafterLoad(TaskUnit,Task,From,Event,To,Cargo) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +if not Cargo:IsLoaded()then +Cargo:Load(TaskUnit) +end +Cargo:MessageToGroup("Loaded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) +TaskUnit:AddCargo(Cargo) +Task:CargoPickedUp(TaskUnit,Cargo) +self:SelectAction(-1) +end +function Fsm:onafterPrepareUnBoarding(TaskUnit,Task,From,Event,To,Cargo) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo}) +self.Cargo=Cargo +self.DeployZone=nil +if Cargo:IsAlive()then +for DeployZoneName,DeployZone in pairs(Task.DeployZones)do +if Cargo:IsInZone(DeployZone)then +self.DeployZone=DeployZone +break +end +end +self:__UnBoard(-0.1,Cargo,self.DeployZone) +end +end +function Fsm:onafterUnBoard(TaskUnit,Task,From,Event,To,Cargo,DeployZone) +self:F({TaskUnit=TaskUnit,Task=Task and Task:GetClassNameAndID(),From,Event,To,Cargo,DeployZone}) +function self.Cargo:OnEnterUnLoaded(From,Event,To,DeployZone,TaskProcess) +self:F({From,Event,To,DeployZone,TaskProcess}) +TaskProcess:__UnBoarded(-0.1) +end +if self.Cargo:IsAlive()then +self.Cargo:MessageToGroup("UnBoarding ...",TaskUnit:GetGroup()) +if DeployZone then +self.Cargo:UnBoard(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +else +self.Cargo:UnBoard(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +end +end +end +function Fsm:onafterUnBoarded(TaskUnit,Task) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +self.Cargo:MessageToGroup("UnBoarded cargo "..self.Cargo:GetName(),TaskUnit:GetGroup()) +self:Unload(self.Cargo) +end +function Fsm:onafterUnload(TaskUnit,Task,From,Event,To,Cargo,DeployZone) +local TaskUnitName=TaskUnit:GetName() +self:F({TaskUnit=TaskUnitName,Task=Task and Task:GetClassNameAndID()}) +if not Cargo:IsUnLoaded()then +if DeployZone then +Cargo:UnLoad(DeployZone:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +else +Cargo:UnLoad(TaskUnit:GetCoordinate():GetRandomCoordinateInRadius(25,10),400,self) +end +end +TaskUnit:RemoveCargo(Cargo) +Cargo:MessageToGroup("Unloaded cargo "..Cargo:GetName(),TaskUnit:GetGroup()) +self:Planned() +self:__SelectAction(1) +end +return self +end +function TASK_CARGO:SetCargoLimit(CargoLimit) +self.CargoLimit=CargoLimit +return self +end +function TASK_CARGO:SetSmokeColor(SmokeColor) +if SmokeColor==nil then +self.SmokeColor=SMOKECOLOR.Red +elseif type(SmokeColor)=="number"then +self:F2(SmokeColor) +if SmokeColor>0 and SmokeColor<=5 then +self.SmokeColor=SMOKECOLOR.SmokeColor +end +end +end +function TASK_CARGO:GetSmokeColor() +return self.SmokeColor +end +function TASK_CARGO:GetPlannedMenuText() +return self:GetStateString().." - "..self:GetTaskName().." ( "..self.TargetSetUnit:GetUnitTypesText().." )" +end +function TASK_CARGO:GetCargoSet() +return self.SetCargo +end +function TASK_CARGO:GetDeployZones() +return self.DeployZones +end +function TASK_CARGO:SetCargoPickup(Cargo,TaskUnit) +self:F({Cargo,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local MenuTime=self:InitTaskControlMenu(TaskUnit) +local MenuControl=self:GetTaskControlMenu(TaskUnit) +local ActRouteCargo=ProcessUnit:GetProcess("RoutingToPickup","RouteToPickupPoint") +ActRouteCargo:Reset() +ActRouteCargo:SetCoordinate(Cargo:GetCoordinate()) +ActRouteCargo:SetRange(Cargo:GetLoadRadius()) +ActRouteCargo:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Cargo "..Cargo:GetName(),MenuControl,MenuTime,"Cargo") +ActRouteCargo:Start() +return self +end +function TASK_CARGO:SetDeployZone(DeployZone,TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local MenuTime=self:InitTaskControlMenu(TaskUnit) +local MenuControl=self:GetTaskControlMenu(TaskUnit) +local ActRouteDeployZone=ProcessUnit:GetProcess("RoutingToDeploy","RouteToDeployZone") +ActRouteDeployZone:Reset() +ActRouteDeployZone:SetZone(DeployZone) +ActRouteDeployZone:SetMenuCancel(TaskUnit:GetGroup(),"Cancel Routing to Deploy Zone"..DeployZone:GetName(),MenuControl,MenuTime,"Cargo") +ActRouteDeployZone:Start() +return self +end +function TASK_CARGO:AddDeployZone(DeployZone,TaskUnit) +self.DeployZones[DeployZone:GetName()]=DeployZone +return self +end +function TASK_CARGO:RemoveDeployZone(DeployZone,TaskUnit) +self.DeployZones[DeployZone:GetName()]=nil +return self +end +function TASK_CARGO:SetDeployZones(DeployZones,TaskUnit) +for DeployZoneID,DeployZone in pairs(DeployZones or{})do +self.DeployZones[DeployZone:GetName()]=DeployZone +end +return self +end +function TASK_CARGO:GetTargetZone(TaskUnit) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +local ActRouteTarget=ProcessUnit:GetProcess("Engaging","RouteToTargetZone") +return ActRouteTarget:GetZone() +end +function TASK_CARGO:SetScoreOnProgress(Text,Score,TaskUnit) +self:F({Text,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScoreProcess("Engaging","Account","Account",Text,Score) +return self +end +function TASK_CARGO:SetScoreOnSuccess(Text,Score,TaskUnit) +self:F({Text,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Success",Text,Score) +return self +end +function TASK_CARGO:SetScoreOnFail(Text,Penalty,TaskUnit) +self:F({Text,Score,TaskUnit}) +local ProcessUnit=self:GetUnitProcess(TaskUnit) +ProcessUnit:AddScore("Failed",Text,Penalty) +return self +end +function TASK_CARGO:SetGoalTotal() +self.GoalTotal=self.SetCargo:Count() +end +function TASK_CARGO:GetGoalTotal() +return self.GoalTotal +end +function TASK_CARGO:UpdateTaskInfo() +if self:IsStatePlanned()or self:IsStateAssigned()then +self.TaskInfo:AddTaskName(0,"MSOD") +self.TaskInfo:AddCargoSet(self.SetCargo,10,"SOD",true) +local Coordinates={} +for CargoName,Cargo in pairs(self.SetCargo:GetSet())do +local Cargo=Cargo +if not Cargo:IsLoaded()then +Coordinates[#Coordinates+1]=Cargo:GetCoordinate() +end +end +self.TaskInfo:AddCoordinates(Coordinates,1,"M") +end +end +function TASK_CARGO:ReportOrder(ReportGroup) +return 0 +end +function TASK_CARGO:GetAutoAssignPriority(AutoAssignMethod,TaskGroup) +if AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Random then +return math.random(1,9) +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Distance then +return 0 +elseif AutoAssignMethod==COMMANDCENTER.AutoAssignMethods.Priority then +return 1 +end +return 0 +end +end +do +TASK_CARGO_TRANSPORT={ +ClassName="TASK_CARGO_TRANSPORT", +} +function TASK_CARGO_TRANSPORT:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing) +local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"Transport",TaskBriefing)) +self:F() +Mission:AddTask(self) +local Fsm=self:GetUnitProcess() +local CargoReport=REPORT:New("Transport Cargo. The following cargo needs to be transported including initial positions:") +SetCargo:ForEachCargo( +function(Cargo) +local CargoType=Cargo:GetType() +local CargoName=Cargo:GetName() +local CargoCoordinate=Cargo:GetCoordinate() +CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS())) +end +) +self:SetBriefing( +TaskBriefing or +CargoReport:Text() +) +return self +end +function TASK_CARGO_TRANSPORT:ReportOrder(ReportGroup) +return 0 +end +function TASK_CARGO_TRANSPORT:IsAllCargoTransported() +local CargoSet=self:GetCargoSet() +local Set=CargoSet:GetSet() +local DeployZones=self:GetDeployZones() +local CargoDeployed=true +for CargoID,CargoData in pairs(Set)do +local Cargo=CargoData +self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()}) +if Cargo:IsDeployed()then +else +CargoDeployed=false +end +end +self:F({CargoDeployed=CargoDeployed}) +return CargoDeployed +end +function TASK_CARGO_TRANSPORT:onafterGoal(TaskUnit,From,Event,To) +local CargoSet=self.CargoSet +if self:IsAllCargoTransported()then +self:Success() +end +self:__Goal(-10) +end +end +do +TASK_CARGO_CSAR={ +ClassName="TASK_CARGO_CSAR", +} +function TASK_CARGO_CSAR:New(Mission,SetGroup,TaskName,SetCargo,TaskBriefing) +local self=BASE:Inherit(self,TASK_CARGO:New(Mission,SetGroup,TaskName,SetCargo,"CSAR",TaskBriefing)) +self:F() +Mission:AddTask(self) +self:AddTransition("*","CargoPickedUp","*") +self:AddTransition("*","CargoDeployed","*") +self:F({CargoDeployed=self.CargoDeployed~=nil and"true"or"false"}) +local Fsm=self:GetUnitProcess() +local CargoReport=REPORT:New("Rescue a downed pilot from the following position:") +SetCargo:ForEachCargo( +function(Cargo) +local CargoType=Cargo:GetType() +local CargoName=Cargo:GetName() +local CargoCoordinate=Cargo:GetCoordinate() +CargoReport:Add(string.format('- "%s" (%s) at %s',CargoName,CargoType,CargoCoordinate:ToStringMGRS())) +end +) +self:SetBriefing( +TaskBriefing or +CargoReport:Text() +) +return self +end +function TASK_CARGO_CSAR:ReportOrder(ReportGroup) +return 0 +end +function TASK_CARGO_CSAR:IsAllCargoTransported() +local CargoSet=self:GetCargoSet() +local Set=CargoSet:GetSet() +local DeployZones=self:GetDeployZones() +local CargoDeployed=true +for CargoID,CargoData in pairs(Set)do +local Cargo=CargoData +self:F({Cargo=Cargo:GetName(),CargoDeployed=Cargo:IsDeployed()}) +if Cargo:IsDeployed()then +else +CargoDeployed=false +end +end +self:F({CargoDeployed=CargoDeployed}) +return CargoDeployed +end +function TASK_CARGO_CSAR:onafterGoal(TaskUnit,From,Event,To) +local CargoSet=self.CargoSet +if self:IsAllCargoTransported()then +self:Success() +end +self:__Goal(-10) +end +end +do +TASK_CARGO_DISPATCHER={ +ClassName="TASK_CARGO_DISPATCHER", +Mission=nil, +Tasks={}, +CSAR={}, +CSARSpawned=0, +Transport={}, +TransportCount=0, +} +function TASK_CARGO_DISPATCHER:New(Mission,SetGroup) +local self=BASE:Inherit(self,TASK_MANAGER:New(SetGroup)) +self.Mission=Mission +self:AddTransition("Started","Assign","Started") +self:AddTransition("Started","CargoPickedUp","Started") +self:AddTransition("Started","CargoDeployed","Started") +self:SetCSARRadius() +self:__StartTasks(5) +self.MaxCSAR=nil +self.CountCSAR=0 +self:HandleEvent(EVENTS.Ejection) +return self +end +function TASK_CARGO_DISPATCHER:SetCSARZones(SetZonesCSAR) +self.SetZonesCSAR=SetZonesCSAR +end +function TASK_CARGO_DISPATCHER:SetMaxCSAR(MaxCSAR) +self.MaxCSAR=MaxCSAR +end +function TASK_CARGO_DISPATCHER:OnEventEjection(EventData) +self:F({EventData=EventData}) +if self.CSARTasks==true then +local CSARCoordinate=EventData.IniUnit:GetCoordinate() +local CSARCoalition=EventData.IniUnit:GetCoalition() +local CSARCountry=EventData.IniUnit:GetCountry() +local CSARHeading=EventData.IniUnit:GetHeading() +if CSARCoalition==self.Mission:GetCommandCenter():GetCoalition()then +if not self.SetZonesCSAR or(self.SetZonesCSAR and self.SetZonesCSAR:IsCoordinateInZone(CSARCoordinate))then +if not self.MaxCSAR or(self.MaxCSAR and self.CountCSAR